update response headers
[m6w6/ext-http] / php_http_client_curl.c
index b261a34112b4f93a0f1f78d5828b135fa585d037..566e523b9154b67803abb8a11d67d58818820d91 100644 (file)
@@ -60,19 +60,11 @@ typedef struct php_http_client_curl_handler {
        php_resource_factory_t *rf;
        php_http_client_t *client;
        php_http_client_progress_state_t progress;
-
        php_http_client_enqueue_t queue;
 
        struct {
-               php_http_message_parser_t *parser;
-               php_http_message_t *message;
-               php_http_buffer_t *buffer;
-       } request;
-
-       struct {
-               php_http_message_parser_t *parser;
-               php_http_message_t *message;
-               php_http_buffer_t *buffer;
+               php_http_buffer_t headers;
+               php_http_message_body_t *body;
        } response;
 
        struct {
@@ -247,7 +239,6 @@ static curlioerr php_http_curle_ioctl_callback(CURL *ch, curliocmd cmd, void *ct
 static int php_http_curle_raw_callback(CURL *ch, curl_infotype type, char *data, size_t length, void *ctx)
 {
        php_http_client_curl_handler_t *h = ctx;
-       unsigned flags = 0;
 
        /* catch progress */
        switch (type) {
@@ -282,7 +273,7 @@ static int php_http_curle_raw_callback(CURL *ch, curl_infotype type, char *data,
                        } else if (php_memnstr(data, ZEND_STRL("Operation timed out"), data + length)) {
                                h->progress.info = "timeout";
                        } else {
-#if PHP_DEBUG
+#if 0
                                h->progress.info = data;
                                data[length - 1] = '\0';
 #endif
@@ -304,32 +295,6 @@ static int php_http_curle_raw_callback(CURL *ch, curl_infotype type, char *data,
                default:
                        break;
        }
-       /* process data */
-       switch (type) {
-               case CURLINFO_HEADER_IN:
-               case CURLINFO_DATA_IN:
-                       php_http_buffer_append(h->response.buffer, data, length);
-
-                       if (h->options.redirects) {
-                               flags |= PHP_HTTP_MESSAGE_PARSER_EMPTY_REDIRECTS;
-                       }
-
-                       if (PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE == php_http_message_parser_parse(h->response.parser, h->response.buffer, flags, &h->response.message)) {
-                               return -1;
-                       }
-                       break;
-
-               case CURLINFO_HEADER_OUT:
-               case CURLINFO_DATA_OUT:
-                       php_http_buffer_append(h->request.buffer, data, length);
-
-                       if (PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE == php_http_message_parser_parse(h->request.parser, h->request.buffer, flags, &h->request.message)) {
-                               return -1;
-                       }
-                       break;
-               default:
-                       break;
-       }
 
 #if 0
        /* debug */
@@ -339,9 +304,18 @@ static int php_http_curle_raw_callback(CURL *ch, curl_infotype type, char *data,
        return 0;
 }
 
-static int php_http_curle_dummy_callback(char *data, size_t n, size_t l, void *s)
+static int php_http_curle_header_callback(char *data, size_t n, size_t l, void *arg)
 {
-       return n*l;
+       php_http_client_curl_handler_t *h = arg;
+
+       return php_http_buffer_append(&h->response.headers, data, n * l);
+}
+
+static int php_http_curle_body_callback(char *data, size_t n, size_t l, void *arg)
+{
+       php_http_client_curl_handler_t *h = arg;
+
+       return php_http_message_body_append(h->response.body, data, n*l);
 }
 
 static STATUS php_http_curle_get_info(CURL *ch, HashTable *info)
@@ -539,9 +513,11 @@ static STATUS php_http_curle_get_info(CURL *ch, HashTable *info)
                        case CURLSSLBACKEND_NSS:
                                backend = "nss";
                                break;
+#if !PHP_HTTP_CURL_VERSION(7,39,0)
                        case CURLSSLBACKEND_QSOSSL:
                                backend = "qsossl";
                                break;
+#endif
                        case CURLSSLBACKEND_GSKIT:
                                backend = "gskit";
                                break;
@@ -615,6 +591,50 @@ static int compare_queue(php_http_client_enqueue_t *e, void *handle)
        return handle == ((php_http_client_curl_handler_t *) e->opaque)->handle;
 }
 
+static php_http_message_t *php_http_curlm_responseparser(php_http_client_curl_handler_t *h TSRMLS_DC)
+{
+       php_http_message_t *response;
+       php_http_header_parser_t parser;
+       zval *zh;
+
+       response = php_http_message_init(NULL, 0, h->response.body TSRMLS_CC);
+       php_http_header_parser_init(&parser TSRMLS_CC);
+       php_http_header_parser_parse(&parser, &h->response.headers, PHP_HTTP_HEADER_PARSER_CLEANUP, &response->hdrs, (php_http_info_callback_t) php_http_message_info_callback, (void *) &response);
+       php_http_header_parser_dtor(&parser);
+
+       /* move body to right message */
+       if (response->body != h->response.body) {
+               php_http_message_t *ptr = response;
+
+               while (ptr->parent) {
+                       ptr = ptr->parent;
+               }
+               response->body = ptr->body;
+               ptr->body = NULL;
+       }
+       php_http_message_body_addref(h->response.body);
+
+       /* let's update the response headers */
+       if ((zh = php_http_message_header(response, ZEND_STRL("Content-Length"), 1))) {
+               zend_hash_update(&response->hdrs, "X-Original-Content-Length", sizeof("X-Original-Content-Length"), &zh, sizeof(zval *), NULL);
+       }
+       if ((zh = php_http_message_header(response, ZEND_STRL("Transfer-Encoding"), 0))) {
+               zend_hash_update(&response->hdrs, "X-Original-Transfer-Encoding", sizeof("X-Original-Transfer-Encoding"), (void *) &zh, sizeof(zval *), NULL);
+               zend_hash_del(&response->hdrs, "Transfer-Encoding", sizeof("Transfer-Encoding"));
+       }
+       if ((zh = php_http_message_header(response, ZEND_STRL("Content-Range"), 0))) {
+               zend_hash_update(&response->hdrs, "X-Original-Content-Range", sizeof("X-Original-Content-Range"), &zh, sizeof(zval *), NULL);
+               zend_hash_del(&response->hdrs, "Content-Range", sizeof("Content-Range"));
+       }
+       if ((zh = php_http_message_header(response, ZEND_STRL("Content-Encoding"), 0))) {
+               zend_hash_update(&response->hdrs, "X-Original-Content-Encoding", sizeof("X-Original-Content-Encoding"), &zh, sizeof(zval *), NULL);
+               zend_hash_del(&response->hdrs, "Content-Encoding", sizeof("Content-Encoding"));
+       }
+       php_http_message_update_headers(response);
+
+       return response;
+}
+
 static void php_http_curlm_responsehandler(php_http_client_t *context)
 {
        int remaining = 0;
@@ -633,8 +653,12 @@ static void php_http_curlm_responsehandler(php_http_client_t *context)
 
                        if ((enqueue = php_http_client_enqueued(context, msg->easy_handle, compare_queue))) {
                                php_http_client_curl_handler_t *handler = enqueue->opaque;
+                               php_http_message_t *response = php_http_curlm_responseparser(handler TSRMLS_CC);
 
-                               context->callback.response.func(context->callback.response.arg, context, &handler->queue, &handler->request.message, &handler->response.message);
+                               if (response) {
+                                       context->callback.response.func(context->callback.response.arg, context, &handler->queue, &response);
+                                       php_http_message_free(&response);
+                               }
                        }
                }
        } while (remaining);
@@ -903,7 +927,7 @@ static STATUS php_http_curle_option_set_lastmodified(php_http_option_t *opt, zva
                                return FAILURE;
                        }
                } else {
-                       if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_TIMEVALUE, (long) PHP_HTTP_G->env.request.time + Z_LVAL_P(val))) {
+                       if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_TIMEVALUE, (long) sapi_get_request_time(TSRMLS_C) + Z_LVAL_P(val))) {
                                return FAILURE;
                        }
                }
@@ -1494,6 +1518,9 @@ static STATUS php_http_client_curl_handler_reset(php_http_client_curl_handler_t
        }
 
        curl_easy_setopt(ch, CURLOPT_URL, NULL);
+       curl_easy_setopt(ch, CURLOPT_CUSTOMREQUEST, NULL);
+       curl_easy_setopt(ch, CURLOPT_HTTPGET, 1L);
+       curl_easy_setopt(ch, CURLOPT_NOBODY, 0L);
        /* libcurl < 7.19.6 does not clear auth info with USERPWD set to NULL */
 #if PHP_HTTP_CURL_VERSION(7,19,1)
        curl_easy_setopt(ch, CURLOPT_PROXYUSERNAME, NULL);
@@ -1539,12 +1566,8 @@ static php_http_client_curl_handler_t *php_http_client_curl_handler_init(php_htt
        handler->rf = rf;
        handler->client = h;
        handler->handle = handle;
-       handler->request.buffer = php_http_buffer_init(NULL);
-       handler->request.parser = php_http_message_parser_init(NULL TSRMLS_CC);
-       handler->request.message = php_http_message_init(NULL, 0, NULL TSRMLS_CC);
-       handler->response.buffer = php_http_buffer_init(NULL);
-       handler->response.parser = php_http_message_parser_init(NULL TSRMLS_CC);
-       handler->response.message = php_http_message_init(NULL, 0, NULL TSRMLS_CC);
+       handler->response.body = php_http_message_body_init(NULL, NULL TSRMLS_CC);
+       php_http_buffer_init(&handler->response.headers);
        php_http_buffer_init(&handler->options.cookies);
        php_http_buffer_init(&handler->options.ranges);
        zend_hash_init(&handler->options.cache, 0, NULL, ZVAL_PTR_DTOR, 0);
@@ -1557,8 +1580,8 @@ static php_http_client_curl_handler_t *php_http_client_curl_handler_init(php_htt
        curl_easy_setopt(handle, CURLOPT_AUTOREFERER, 1L);
        curl_easy_setopt(handle, CURLOPT_VERBOSE, 1L);
        curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0L);
-       curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, NULL);
-       curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, php_http_curle_dummy_callback);
+       curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, php_http_curle_header_callback);
+       curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, php_http_curle_body_callback);
        curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, php_http_curle_raw_callback);
        curl_easy_setopt(handle, CURLOPT_READFUNCTION, php_http_curle_read_callback);
        curl_easy_setopt(handle, CURLOPT_IOCTLFUNCTION, php_http_curle_ioctl_callback);
@@ -1570,6 +1593,8 @@ static php_http_client_curl_handler_t *php_http_client_curl_handler_init(php_htt
        curl_easy_setopt(handle, CURLOPT_PROGRESSDATA, handler);
 #endif
        curl_easy_setopt(handle, CURLOPT_DEBUGDATA, handler);
+       curl_easy_setopt(handle, CURLOPT_WRITEDATA, handler);
+       curl_easy_setopt(handle, CURLOPT_HEADERDATA, handler);
 
        php_http_client_curl_handler_reset(handler);
 
@@ -1593,7 +1618,7 @@ static STATUS php_http_client_curl_handler_prepare(php_http_client_curl_handler_
        if (storage->url) {
                pefree(storage->url, 1);
        }
-       storage->url = pestrdup(PHP_HTTP_INFO(msg).request.url, 1);
+       php_http_url_to_string(PHP_HTTP_INFO(msg).request.url, &storage->url, NULL, 1);
        curl_easy_setopt(curl->handle, CURLOPT_URL, storage->url);
 
        /* request method */
@@ -1629,15 +1654,25 @@ static STATUS php_http_client_curl_handler_prepare(php_http_client_curl_handler_
        php_http_message_update_headers(msg);
        if (zend_hash_num_elements(&msg->hdrs)) {
                php_http_array_hashkey_t header_key = php_http_array_hashkey_init(0);
-               zval **header_val;
+               zval **header_val, *header_cpy;
                HashPosition pos;
                php_http_buffer_t header;
+#if !PHP_HTTP_CURL_VERSION(7,23,0)
+               zval **ct = NULL;
+
+               zend_hash_find(&msg->hdrs, ZEND_STRS("Content-Length"), (void *) &ct);
+#endif
 
                php_http_buffer_init(&header);
                FOREACH_HASH_KEYVAL(pos, &msg->hdrs, header_key, header_val) {
                        if (header_key.type == HASH_KEY_IS_STRING) {
-                               zval *header_cpy = php_http_ztyp(IS_STRING, *header_val);
-
+#if !PHP_HTTP_CURL_VERSION(7,23,0)
+                               /* avoid duplicate content-length header */
+                               if (ct && *ct == *header_val) {
+                                       continue;
+                               }
+#endif
+                               header_cpy = php_http_ztyp(IS_STRING, *header_val);
                                php_http_buffer_appendf(&header, "%s: %s", header_key.str, Z_STRVAL_P(header_cpy));
                                php_http_buffer_fix(&header);
                                curl->options.headers = curl_slist_append(curl->options.headers, header.data);
@@ -1675,10 +1710,8 @@ static STATUS php_http_client_curl_handler_prepare(php_http_client_curl_handler_
        return SUCCESS;
 }
 
-static void php_http_client_curl_handler_dtor(php_http_client_curl_handler_t *handler)
+static void php_http_client_curl_handler_clear(php_http_client_curl_handler_t *handler)
 {
-       TSRMLS_FETCH_FROM_CTX(handler->client->ts);
-
        curl_easy_setopt(handler->handle, CURLOPT_NOPROGRESS, 1L);
 #if PHP_HTTP_CURL_VERSION(7,32,0)
        curl_easy_setopt(handler->handle, CURLOPT_XFERINFOFUNCTION, NULL);
@@ -1687,16 +1720,19 @@ static void php_http_client_curl_handler_dtor(php_http_client_curl_handler_t *ha
 #endif
        curl_easy_setopt(handler->handle, CURLOPT_VERBOSE, 0L);
        curl_easy_setopt(handler->handle, CURLOPT_DEBUGFUNCTION, NULL);
+}
+
+static void php_http_client_curl_handler_dtor(php_http_client_curl_handler_t *handler)
+{
+       TSRMLS_FETCH_FROM_CTX(handler->client->ts);
+
+       php_http_client_curl_handler_clear(handler);
 
        php_resource_factory_handle_dtor(handler->rf, handler->handle TSRMLS_CC);
        php_resource_factory_free(&handler->rf);
 
-       php_http_message_parser_free(&handler->request.parser);
-       php_http_message_free(&handler->request.message);
-       php_http_buffer_free(&handler->request.buffer);
-       php_http_message_parser_free(&handler->response.parser);
-       php_http_message_free(&handler->response.message);
-       php_http_buffer_free(&handler->response.buffer);
+       php_http_message_body_free(&handler->response.body);
+       php_http_buffer_dtor(&handler->response.headers);
        php_http_buffer_dtor(&handler->options.ranges);
        php_http_buffer_dtor(&handler->options.cookies);
        zend_hash_destroy(&handler->options.cache);
@@ -1764,38 +1800,29 @@ static void queue_dtor(php_http_client_enqueue_t *e)
        php_http_client_curl_handler_dtor(handler);
 }
 
-static php_resource_factory_t *create_rf(const char *url TSRMLS_DC)
+static php_resource_factory_t *create_rf(php_http_url_t *url TSRMLS_DC)
 {
-       php_url *purl;
+       php_persistent_handle_factory_t *pf;
        php_resource_factory_t *rf = NULL;
+       char *id_str = NULL;
+       size_t id_len;
 
-       if (!url || !*url) {
+       if (!url || (!url->host && !url->path)) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot request empty URL");
                return NULL;
        }
 
-       purl = php_url_parse(url);
+       id_len = spprintf(&id_str, 0, "%s:%d", STR_PTR(url->host), url->port ? url->port : 80);
 
-       if (!purl) {
-               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not parse URL '%s'", url);
-               return NULL;
+       pf = php_persistent_handle_concede(NULL, ZEND_STRL("http\\Client\\Curl\\Request"), id_str, id_len, NULL, NULL TSRMLS_CC);
+       if (pf) {
+               rf = php_resource_factory_init(NULL, php_persistent_handle_get_resource_factory_ops(), pf, (void (*)(void*)) php_persistent_handle_abandon);
        } else {
-               char *id_str = NULL;
-               size_t id_len = spprintf(&id_str, 0, "%s:%d", STR_PTR(purl->host), purl->port ? purl->port : 80);
-               php_persistent_handle_factory_t *pf = php_persistent_handle_concede(NULL, ZEND_STRL("http\\Client\\Curl\\Request"), id_str, id_len, NULL, NULL TSRMLS_CC);
-
-               if (pf) {
-                       rf = php_resource_factory_init(NULL, php_persistent_handle_get_resource_factory_ops(), pf, (void (*)(void*)) php_persistent_handle_abandon);
-               }
-
-               php_url_free(purl);
-               efree(id_str);
-       }
-
-       if (!rf) {
                rf = php_resource_factory_init(NULL, &php_http_curle_resource_factory_ops, NULL, NULL);
        }
 
+       efree(id_str);
+
        return rf;
 }
 
@@ -1851,6 +1878,7 @@ static STATUS php_http_client_curl_dequeue(php_http_client_t *h, php_http_client
        php_http_client_curl_handler_t *handler = enqueue->opaque;
        TSRMLS_FETCH_FROM_CTX(h->ts);
 
+       php_http_client_curl_handler_clear(handler);
        if (CURLM_OK == (rs = curl_multi_remove_handle(curl->handle, handler->handle))) {
                zend_llist_del_element(&h->requests, handler->handle, (int (*)(void *, void *)) compare_queue);
                return SUCCESS;
@@ -2111,12 +2139,20 @@ PHP_MINIT_FUNCTION(http_client_curl)
        */
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_1_0", CURL_HTTP_VERSION_1_0, CONST_CS|CONST_PERSISTENT);
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_1_1", CURL_HTTP_VERSION_1_1, CONST_CS|CONST_PERSISTENT);
+#if PHP_HTTP_CURL_VERSION(7,33,0)
+       REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_2_0", CURL_HTTP_VERSION_2_0, CONST_CS|CONST_PERSISTENT);
+#endif
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_ANY", CURL_HTTP_VERSION_NONE, CONST_CS|CONST_PERSISTENT);
 
        /*
        * SSL Version Constants
        */
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_TLSv1", CURL_SSLVERSION_TLSv1, CONST_CS|CONST_PERSISTENT);
+#if PHP_HTTP_CURL_VERSION(7,34,0)
+       REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_TLSv1_0", CURL_SSLVERSION_TLSv1_0, CONST_CS|CONST_PERSISTENT);
+       REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_TLSv1_1", CURL_SSLVERSION_TLSv1_1, CONST_CS|CONST_PERSISTENT);
+       REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_TLSv1_2", CURL_SSLVERSION_TLSv1_2, CONST_CS|CONST_PERSISTENT);
+#endif
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_SSLv2", CURL_SSLVERSION_SSLv2, CONST_CS|CONST_PERSISTENT);
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_SSLv3", CURL_SSLVERSION_SSLv3, CONST_CS|CONST_PERSISTENT);
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_ANY", CURL_SSLVERSION_DEFAULT, CONST_CS|CONST_PERSISTENT);
@@ -2138,6 +2174,9 @@ PHP_MINIT_FUNCTION(http_client_curl)
 #endif
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "AUTH_NTLM", CURLAUTH_NTLM, CONST_CS|CONST_PERSISTENT);
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "AUTH_GSSNEG", CURLAUTH_GSSNEGOTIATE, CONST_CS|CONST_PERSISTENT);
+#if PHP_HTTP_CURL_VERSION(7,38,0)
+       REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "AUTH_SPNEGO", CURLAUTH_NEGOTIATE, CONST_CS|CONST_PERSISTENT);
+#endif
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "AUTH_ANY", CURLAUTH_ANY, CONST_CS|CONST_PERSISTENT);
 
        /*
@@ -2148,9 +2187,9 @@ PHP_MINIT_FUNCTION(http_client_curl)
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "PROXY_SOCKS5_HOSTNAME", CURLPROXY_SOCKS5, CONST_CS|CONST_PERSISTENT);
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "PROXY_SOCKS5", CURLPROXY_SOCKS5, CONST_CS|CONST_PERSISTENT);
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "PROXY_HTTP", CURLPROXY_HTTP, CONST_CS|CONST_PERSISTENT);
-#      if PHP_HTTP_CURL_VERSION(7,19,4)
+#if PHP_HTTP_CURL_VERSION(7,19,4)
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "PROXY_HTTP_1_0", CURLPROXY_HTTP_1_0, CONST_CS|CONST_PERSISTENT);
-#      endif
+#endif
 
        /*
        * Post Redirection Constants
@@ -2158,6 +2197,9 @@ PHP_MINIT_FUNCTION(http_client_curl)
 #if PHP_HTTP_CURL_VERSION(7,19,1)
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "POSTREDIR_301", CURL_REDIR_POST_301, CONST_CS|CONST_PERSISTENT);
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "POSTREDIR_302", CURL_REDIR_POST_302, CONST_CS|CONST_PERSISTENT);
+#if PHP_HTTP_CURL_VERSION(7,26,0)
+       REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "POSTREDIR_303", CURL_REDIR_POST_303, CONST_CS|CONST_PERSISTENT);
+#endif
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "POSTREDIR_ALL", CURL_REDIR_POST_ALL, CONST_CS|CONST_PERSISTENT);
 #endif