Commits

Ruslan Osmanov  committed 0e4dec9 Merge

Merge branch 'http-ssl'

  • Participants
  • Parent commits b5cc276, c6d38f8

Comments (0)

Files changed (11)

File classes/http.c

 }
 /* }}} */
 
+#if LIBEVENT_VERSION_NUMBER >= 0x02010000 && defined(HAVE_EVENT_OPENSSL_LIB)
+/*{{{ _bev_ssl_callback
+ *
+ * This callback is responsible for creating a new SSL connection
+ * and wrapping it in an OpenSSL bufferevent.  This is the way
+ * we implement an https server instead of a plain old http server.
+ * (borrowed from https://github.com/ppelleti/https-example/blob/master/https-server.c)
+ */
+static struct bufferevent* _bev_ssl_callback(struct event_base *base, void *arg) {
+	struct bufferevent* bev;
+	SSL_CTX *ctx = (SSL_CTX *) arg;
+
+	bev = bufferevent_openssl_socket_new(base,
+			-1,
+			SSL_new(ctx),
+			BUFFEREVENT_SSL_ACCEPTING,
+			BEV_OPT_CLOSE_ON_FREE);
+	return bev;
+}
+/*}}}*/
+#endif
+
 /* }}} */
 
 /* {{{  _php_event_free_http_cb */
 }
 /* }}} */
 
-/* {{{ proto EventHttp EventHttp::__construct(EventBase base);
+/* {{{ proto EventHttp EventHttp::__construct(EventBase base[, EventSslContext ctx = NULL]);
  * Creates new http server object.
  */
 PHP_METHOD(EventHttp, __construct)
 	php_event_http_t *http;
 	struct evhttp    *http_ptr;
 
+#if LIBEVENT_VERSION_NUMBER >= 0x02010000 && defined(HAVE_EVENT_OPENSSL_LIB)
+	php_event_ssl_context_t *ectx;
+	zval                    *zctx = NULL;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|O!",
+				&zbase, php_event_base_ce,
+				&zctx, php_event_ssl_context_ce) == FAILURE) {
+		return;
+	}
+#else
 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O",
 				&zbase, php_event_base_ce) == FAILURE) {
 		return;
 	}
+#endif
 
 	PHP_EVENT_REQUIRE_BASE_BY_REF(zbase);
 
 	http->fcc     = NULL;
 	http->data    = NULL;
 	http->cb_head = NULL;
+
+
+#if LIBEVENT_VERSION_NUMBER >= 0x02010000 && defined(HAVE_EVENT_OPENSSL_LIB)
+	if (zctx) {
+		PHP_EVENT_FETCH_SSL_CONTEXT(ectx, zctx);
+		PHP_EVENT_ASSERT(ectx->ctx);
+		evhttp_set_bevcb(http_ptr, _bev_ssl_callback, ectx->ctx);
+	}
+#endif
 }
 /* }}} */
 

File classes/http_connection.c

 		SSL_set_ex_data(ssl, php_event_ssl_data_index, ectx);
 
 #ifdef HAVE_EVENT_PTHREADS_LIB
-		options = BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE;
+		options = BEV_OPT_DEFER_CALLBACKS | BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE;
 #else
-		options = BEV_OPT_CLOSE_ON_FREE;
+		options = BEV_OPT_DEFER_CALLBACKS | BEV_OPT_CLOSE_ON_FREE;
 #endif
 
 		bevent = bufferevent_openssl_socket_new(b->base, -1, ssl, BUFFEREVENT_SSL_CONNECTING, options);

File classes/http_request.c

 	pfci->no_separation  = 1;
 
 	/* Tell Libevent that we will free the request ourselves(evhttp_request_free in the free-storage handler)*/
-	evhttp_request_own(http_req->ptr);
+	/*evhttp_request_own(http_req->ptr);*/
 
 	if (zend_call_function(pfci, pfcc TSRMLS_CC) == SUCCESS && retval_ptr) {
 		zval_ptr_dtor(&retval_ptr);
 
 	/* Tell Libevent that we will free the request ourselves(evhttp_request_free in the free-storage handler)
 	 * XXX Not sure if it's really needed here though. */
-	evhttp_request_own(req);
+	/*evhttp_request_own(req);*/
 	http_req->ptr = req;
 
 	if (zarg) {

File examples/http_request.php

 			}
 		}
 	}
-	
+
 	$base->exit(NULL);
 }
 

File examples/https.php

+<?php
+/*
+ * Simple HTTPS server.
+ *
+ * 1) Run the server: `php examples/https.php 9999`
+ * 2) Test it: `php examples/ssl-connection.php 9999`
+ */
+
+function _http_dump($req, $data) {
+	static $counter      = 0;
+	static $max_requests = 200;
+
+	if (++$counter >= $max_requests)  {
+		echo "Counter reached max requests $max_requests. Exiting\n";
+		exit();
+	}
+
+	echo __METHOD__, " called\n";
+	echo "request:"; var_dump($req);
+	echo "data:"; var_dump($data);
+
+	echo "\n===== DUMP =====\n";
+	echo "Command:", $req->getCommand(), PHP_EOL;
+	echo "URI:", $req->getUri(), PHP_EOL;
+	echo "Input headers:"; var_dump($req->getInputHeaders());
+	echo "Output headers:"; var_dump($req->getOutputHeaders());
+
+	echo "\n >> Sending reply ...";
+	$req->sendReply(200, "OK");
+	echo "OK\n";
+
+	$buf = $req->getInputBuffer();
+	echo "\n >> Reading input buffer (", $buf->length, ") ...\n";
+	while ($s = $buf->read(1024)) {
+		echo $s;
+	}
+	echo "\nNo more data in the buffer\n";
+}
+
+function _http_about($req) {
+	echo __METHOD__, PHP_EOL;
+	echo "URI: ", $req->getUri(), PHP_EOL;
+	echo "\n >> Sending reply ...";
+	$req->sendReply(200, "OK");
+	echo "OK\n";
+}
+
+function _http_default($req, $data) {
+	echo __METHOD__, PHP_EOL;
+	echo "URI: ", $req->getUri(), PHP_EOL;
+	echo "\n >> Sending reply ...";
+	$req->sendReply(200, "OK");
+	echo "OK\n";
+}
+
+function _http_400($req) {
+	$req->sendError(400);
+}
+
+function _init_ssl() {
+	$local_cert = __DIR__."/ssl-echo-server/cert.pem";
+	$local_pk   = __DIR__."/ssl-echo-server/privkey.pem";
+
+	$ctx = new EventSslContext(EventSslContext::SSLv3_SERVER_METHOD, array (
+		EventSslContext::OPT_LOCAL_CERT  => $local_cert,
+		EventSslContext::OPT_LOCAL_PK    => $local_pk,
+		//EventSslContext::OPT_PASSPHRASE  => "test",
+		EventSslContext::OPT_ALLOW_SELF_SIGNED => true,
+	));
+
+	return $ctx;
+}
+
+$port = 9999;
+if ($argc > 1) {
+	$port = (int) $argv[1];
+}
+if ($port <= 0 || $port > 65535) {
+	exit("Invalid port");
+}
+$ip = '0.0.0.0';
+
+$base = new EventBase();
+$ctx  = _init_ssl();
+$http = new EventHttp($base, $ctx);
+$http->setAllowedMethods(EventHttpRequest::CMD_GET | EventHttpRequest::CMD_POST);
+
+$http->setCallback("/dump", "_http_dump", array(4, 8));
+$http->setCallback("/about", "_http_about");
+$http->setCallback("/err400", "_http_400");
+$http->setDefaultCallback("_http_default", "custom data value");
+
+$http->bind($ip, $port);
+$base->dispatch();

File examples/ssl-connection.php

+<?php
+/*
+ * Sample OpenSSL client.
+ *
+ * Usage:
+ * 1) Launch a server, e.g.:
+ * $ php examples/https.php 9999
+ *
+ * 2) Launch the client in another terminal:
+ * $ php examples/ssl-connection.php 9999
+ */
+
+function _request_handler($req, $base) {
+	echo __FUNCTION__, PHP_EOL;
+
+	if (is_null($req)) {
+		echo "Timed out\n";
+	} else {
+		$response_code = $req->getResponseCode();
+
+		if ($response_code == 0) {
+			echo "Connection refused\n";
+		} elseif ($response_code != 200) {
+			echo "Unexpected response: $response_code\n";
+		} else {
+			echo "Success: $response_code\n";
+			$buf = $req->getInputBuffer();
+			echo "Body:\n";
+			while ($s = $buf->readLine(EventBuffer::EOL_ANY)) {
+				echo $s, PHP_EOL;
+			}
+		}
+	}
+
+	$base->exit(NULL);
+}
+
+function _init_ssl() {
+	$ctx = new EventSslContext(EventSslContext::SSLv3_CLIENT_METHOD, array ());
+
+	return $ctx;
+}
+
+
+// Allow to override the port
+$port = 9999;
+if ($argc > 1) {
+	$port = (int) $argv[1];
+}
+if ($port <= 0 || $port > 65535) {
+	exit("Invalid port\n");
+}
+$host = '127.0.0.1';
+
+$ctx = _init_ssl();
+if (!$ctx) {
+	trigger_error("Failed creating SSL context", E_USER_ERROR);
+}
+
+$base = new EventBase();
+if (!$base) {
+	trigger_error("Failed to initialize event base", E_USER_ERROR);
+}
+
+$conn = new EventHttpConnection($base, NULL, $host, $port, $ctx);
+$conn->setTimeout(50);
+
+$req = new EventHttpRequest("_request_handler", $base);
+$req->addHeader("Host", $host, EventHttpRequest::OUTPUT_HEADER);
+$buf = $req->getOutputBuffer();
+$buf->add("<html>HTML TEST</html>");
+//$req->addHeader("Content-Length", $buf->length, EventHttpRequest::OUTPUT_HEADER);
+//$req->addHeader("Connection", "close", EventHttpRequest::OUTPUT_HEADER);
+$conn->makeRequest($req, EventHttpRequest::CMD_POST, "/dump");
+
+$base->dispatch();
+echo "END\n";
+?>

File examples/ssl-echo-server/client.php

 		$ctx = new EventSslContext(EventSslContext::SSLv3_CLIENT_METHOD, array (
  			EventSslContext::OPT_LOCAL_CERT  => $local_cert,
  			EventSslContext::OPT_LOCAL_PK    => $local_pk,
- 			//EventSslContext::OPT_PASSPHRASE  => "echo server",
+             //EventSslContext::OPT_PASSPHRASE  => "test",
  			EventSslContext::OPT_ALLOW_SELF_SIGNED => true,
 		));
 

File examples/ssl-echo-server/server.php

 	// This callback is invoked when some even occurs on the event listener,
 	// e.g. connection closed, or an error occured
 	function ssl_event_cb($bev, $events, $ctx) {
+		echo __METHOD__, PHP_EOL;
 		if ($events & EventBufferEvent::ERROR) {
+			fprintf(STDERR, "Error! Events: 0x%x\n", $events);
 			// Fetch errors from the SSL error stack
 			while ($err = $bev->sslError()) {
 				fprintf(STDERR, "Bufferevent error %s.\n", $err);
 		$ctx = new EventSslContext(EventSslContext::SSLv3_SERVER_METHOD, array (
  			EventSslContext::OPT_LOCAL_CERT  => $local_cert,
  			EventSslContext::OPT_LOCAL_PK    => $local_pk,
- 			//EventSslContext::OPT_PASSPHRASE  => "echo server",
+ 			EventSslContext::OPT_PASSPHRASE  => "test",
  			EventSslContext::OPT_VERIFY_PEER => true,
  			EventSslContext::OPT_ALLOW_SELF_SIGNED => true,
 		));
     <email>remi@php.net</email>
     <active>yes</active>
   </contributor>
-  <date>2014-04-20</date>
+  <date>2014-04-21</date>
   <!--{{{ Current version -->
   <version>
     <release>1.10.0</release>
   <notes><![CDATA[
     Issue #3: Segmentation fault on EventHttpRequest->free() (Bitbucket's tracker).
     Add: EventHttpConnection::__construct() now optionally accepts EventSslContext argument (issue #5).
+    Add: EventHttp::__construct() now accepts EventSslContext object as argument.
   ]]></notes>
   <!--}}}-->
   <!--{{{ Contents -->
         <file role="doc" name="http_request.php"/>
         <file role="doc" name="httpv0client.php"/>
         <file role="doc" name="httpv0client2.php"/>
+        <file role="doc" name="https.php"/>
         <file role="doc" name="listener.php"/>
         <file role="doc" name="misc.php"/>
         <file role="doc" name="signal.php"/>
         <file role="doc" name="sslfilter.php"/>
+        <file role="doc" name="ssl-connection.php"/>
         <file role="doc" name="timer.php"/>
         <file role="doc" name="uppercase_proxy.php"/>
       </dir>
       <notes><![CDATA[
         Issue #3: Segmentation fault on EventHttpRequest->free() (Bitbucket's tracker).
         Add: EventHttpConnection::__construct() now optionally accepts EventSslContext argument (Issue #5).
+        Add: EventHttp::__construct() now accepts EventSslContext object as argument.
         ]]></notes>
     </release>
     <!--}}}-->
 		http_req->data = NULL;
 	}
 
+#if 0
+	/*
+	Libevent cleans up http_req->ptr despite the ownership of the pointer
+	(evhttp_request_own()). So we'll get SEGFAULT here if we call
+	evhttp_request_free().
+	*/
+
 	if (!http_req->internal && http_req->ptr) {
 		evhttp_request_free(http_req->ptr);
 		http_req->ptr = NULL;
 	}
+#endif
 
 	event_generic_object_free_storage(ptr TSRMLS_CC);
 }
 
 ZEND_BEGIN_ARG_INFO_EX(arginfo_event_http__construct, 0, 0, 1)
 	ZEND_ARG_INFO(1, base)
+	ZEND_ARG_INFO(0, ctx)
 ZEND_END_ARG_INFO();