fix Content-Range messages; remove superfluous Content-Lenght:0 header
authorMichael Wallner <mike@php.net>
Fri, 13 Feb 2015 08:50:40 +0000 (09:50 +0100)
committerMichael Wallner <mike@php.net>
Fri, 13 Feb 2015 08:50:40 +0000 (09:50 +0100)
php_http_message.c
php_http_message_parser.c
php_http_message_parser.h
tests/client019.phpt
tests/client020.phpt
tests/client025.phpt
tests/data/message_r_content_range.txt [new file with mode: 0644]
tests/message001.phpt
tests/message016.phpt [new file with mode: 0644]
tests/messageparser001.phpt
tests/messageparser002.phpt

index 5a278f68c675f14cc51a24eaad7cdbb4cfce4ec4..efdf053c244fba3d413a1adee177eabca0929083 100644 (file)
@@ -297,6 +297,9 @@ void php_http_message_update_headers(php_http_message_t *msg)
 
        if (php_http_message_body_stream(msg->body)->readfilters.head) {
                /* if a read stream filter is attached to the body the caller must also care for the headers */
+       } else if ((h = php_http_message_header(msg, ZEND_STRL("Content-Range"), 0))) {
+               /* don't mess around with a Content-Range message */
+               zval_ptr_dtor(&h);
        } else if ((size = php_http_message_body_size(msg->body))) {
                MAKE_STD_ZVAL(h);
                ZVAL_LONG(h, size);
@@ -324,6 +327,7 @@ void php_http_message_update_headers(php_http_message_t *msg)
 
                zval_ptr_dtor(&h);
                if (Z_LVAL_P(h_cpy)) {
+                       /* body->size == 0, so get rid of old Content-Length */
                        zend_hash_del(&msg->hdrs, "Content-Length", sizeof("Content-Length"));
                }
                zval_ptr_dtor(&h_cpy);
index 086ce0ce5b28c3726a88d05f00c47cfe2ff799fc..ce2a515b26e59174c0140e8dcfb90bea7c3c2a8a 100644 (file)
@@ -30,12 +30,13 @@ static const php_http_message_parser_state_spec_t php_http_message_parser_states
                {PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH,             1},
                {PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED,    1},
                {PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE,               0},
+               {PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL,               0},
                {PHP_HTTP_MESSAGE_PARSER_STATE_DONE,                    0}
 };
 
 #if DBG_PARSER
 const char *php_http_message_parser_state_name(php_http_message_parser_state_t state) {
-       const char *states[] = {"START", "HEADER", "HEADER_DONE", "BODY", "BODY_DUMB", "BODY_LENGTH", "BODY_CHUNK", "BODY_DONE", "DONE"};
+       const char *states[] = {"START", "HEADER", "HEADER_DONE", "BODY", "BODY_DUMB", "BODY_LENGTH", "BODY_CHUNK", "BODY_DONE", "UPDATE_CL", "DONE"};
        
        if (state < 0 || state > (sizeof(states)/sizeof(char*))-1) {
                return "FAILURE";
@@ -168,6 +169,7 @@ php_http_message_parser_state_t php_http_message_parser_parse_stream(php_http_me
 
                        case PHP_HTTP_MESSAGE_PARSER_STATE_BODY:
                        case PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL:
                                /* should not occur */
                                abort();
                                break;
@@ -252,23 +254,32 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                        {
                                zval *h, *h_loc = NULL, *h_con = NULL, **h_cl = NULL, **h_cr = NULL, **h_te = NULL;
 
+                               /* Content-Range has higher precedence than Content-Length,
+                                * and content-length denotes the original length of the entity,
+                                * so let's *NOT* remove CR/CL, because that would fundamentally
+                                * change the meaning of the whole message
+                                */
                                if ((h = php_http_message_header(*message, ZEND_STRL("Transfer-Encoding"), 1))) {
-                                       zend_hash_update(&(*message)->hdrs, "X-Original-Transfer-Encoding", sizeof("X-Original-Transfer-Encoding"), &h, sizeof(zval *), (void *) &h_te);
+                                       zend_hash_update(&(*message)->hdrs, "X-Original-Transfer-Encoding", sizeof("X-Original-Transfer-Encoding"), (void *) &h, sizeof(zval *), (void *) &h_te);
                                        zend_hash_del(&(*message)->hdrs, "Transfer-Encoding", sizeof("Transfer-Encoding"));
+
+                                       /* reset */
+                                       MAKE_STD_ZVAL(h);
+                                       ZVAL_LONG(h, 0);
+                                       zend_hash_update(&(*message)->hdrs, "Content-Length", sizeof("Content-Length"), (void *) &h, sizeof(zval *), NULL);
+                               } else if ((h = php_http_message_header(*message, ZEND_STRL("Content-Length"), 1))) {
+                                       zend_hash_update(&(*message)->hdrs, "X-Original-Content-Length", sizeof("X-Original-Content-Length"), (void *) &h, sizeof(zval *), (void *) &h_cl);
                                }
-                               if ((h = php_http_message_header(*message, ZEND_STRL("Content-Length"), 1))) {
-                                       zend_hash_update(&(*message)->hdrs, "X-Original-Content-Length", sizeof("X-Original-Content-Length"), &h, sizeof(zval *), (void *) &h_cl);
-                               }
+
                                if ((h = php_http_message_header(*message, ZEND_STRL("Content-Range"), 1))) {
-                                       zend_hash_update(&(*message)->hdrs, "X-Original-Content-Range", sizeof("X-Original-Content-Range"), &h, sizeof(zval *), (void *) &h_cr);
-                                       zend_hash_del(&(*message)->hdrs, "Content-Range", sizeof("Content-Range"));
+                                       zend_hash_find(&(*message)->hdrs, ZEND_STRS("Content-Range"), (void *) &h_cr);
+                                       if (h != *h_cr) {
+                                               zend_hash_update(&(*message)->hdrs, "Content-Range", sizeof("Content-Range"), &h, sizeof(zval *), (void *) &h_cr);
+                                       } else {
+                                               zval_ptr_dtor(&h);
+                                       }
                                }
 
-                               /* default */
-                               MAKE_STD_ZVAL(h);
-                               ZVAL_LONG(h, 0);
-                               zend_hash_update(&(*message)->hdrs, "Content-Length", sizeof("Content-Length"), &h, sizeof(zval *), NULL);
-
                                /* so, if curl sees a 3xx code, a Location header and a Connection:close header
                                 * it decides not to read the response body.
                                 */
@@ -377,8 +388,7 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                        case PHP_HTTP_MESSAGE_PARSER_STATE_BODY:
                        {
                                if (len) {
-                                       zval *zcl;
-
+                                       /* FIXME: what if we re-use the parser? */
                                        if (parser->inflate) {
                                                char *dec_str = NULL;
                                                size_t dec_len;
@@ -396,10 +406,6 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
 
                                        php_stream_write(php_http_message_body_stream((*message)->body), str, len);
 
-                                       /* keep track */
-                                       MAKE_STD_ZVAL(zcl);
-                                       ZVAL_LONG(zcl, php_http_message_body_size((*message)->body));
-                                       zend_hash_update(&(*message)->hdrs, "Content-Length", sizeof("Content-Length"), &zcl, sizeof(zval *), NULL);
                                }
 
                                if (cut) {
@@ -472,7 +478,7 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                        {
                                php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_DONE);
 
-                               if (parser->dechunk) {
+                               if (parser->dechunk && parser->dechunk->ctx) {
                                        char *dec_str = NULL;
                                        size_t dec_len;
 
@@ -485,14 +491,24 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                                                str = dec_str;
                                                len = dec_len;
                                                cut = 0;
-                                               php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_BODY);
+                                               php_http_message_parser_state_push(parser, 2, PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL, PHP_HTTP_MESSAGE_PARSER_STATE_BODY);
                                        }
                                }
 
                                break;
                        }
 
-                       case PHP_HTTP_MESSAGE_PARSER_STATE_DONE: {
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL:
+                       {
+                               zval *zcl;
+                               MAKE_STD_ZVAL(zcl);
+                               ZVAL_LONG(zcl, php_http_message_body_size((*message)->body));
+                               zend_hash_update(&(*message)->hdrs, "Content-Length", sizeof("Content-Length"), &zcl, sizeof(zval *), NULL);
+                               break;
+                       }
+
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_DONE:
+                       {
                                char *ptr = buffer->data;
 
                                while (ptr - buffer->data < buffer->used && PHP_HTTP_IS_CTYPE(space, *ptr)) {
index c2bee1544713ac84c031ff9b2fd9a9bccd9c1352..0bac9da84ea60c5fcb2b11531a169d69c1f92afc 100644 (file)
@@ -27,6 +27,7 @@ typedef enum php_http_message_parser_state {
        PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH,
        PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED,
        PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE,
+       PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL,
        PHP_HTTP_MESSAGE_PARSER_STATE_DONE
 } php_http_message_parser_state_t;
 
index 66d99be36088c03192910b163bc1200724987ed2..98781e050618cb293dd8cf819ae60b2af2b998a4 100644 (file)
@@ -43,5 +43,4 @@ Host: www.example.com:80
 User-Agent: PECL_HTTP/%s PHP/%s libcurl/%s
 Proxy-Connection: Keep-Alive
 Hello: there!
-Content-Length: 0
 ===DONE===
index 29a6ed24699c1a35b003a1f7b5569409934511ad..d8282b443b00a191d33d4f878473da877e023042 100644 (file)
@@ -38,5 +38,4 @@ GET / HTTP/1.1
 User-Agent: PECL_HTTP/%s PHP/%s libcurl/%s
 Host: localhost:%d
 Accept: */*
-Content-Length: 0
 ===DONE===
index 7b7a605932a0623f2812b9e0177aac932fc0a990..18c5095cdf01d8289f4b61991aea2d3805db903c 100644 (file)
@@ -30,12 +30,12 @@ X-Original-Transfer-Encoding: chunked
 Content-Length: %d
 
 PUT / HTTP/1.1
+Content-Range: bytes 1-2/3
 User-Agent: %s
 Host: localhost:%d
 Accept: */*
-Content-Length: 2
+Content-Length: 3
 Expect: 100-continue
 X-Original-Content-Length: 3
-X-Original-Content-Range: bytes 1-2/3
 
 23===DONE===
diff --git a/tests/data/message_r_content_range.txt b/tests/data/message_r_content_range.txt
new file mode 100644 (file)
index 0000000..d42dbff
--- /dev/null
@@ -0,0 +1,9 @@
+PUT / HTTP/1.1
+User-Agent: PECL_HTTP/2.3.0dev PHP/5.6.6-dev libcurl/7.41.0-DEV
+Host: localhost:8000
+Accept: */*
+Expect: 100-continue
+Content-Length: 3
+Content-Range: bytes 1-2/3
+
+23
\ No newline at end of file
index 230fd6b8a6deeb858867f9dbd2a1fa36977c4914..0214dfa4654578898901e7cf9d6fa31af4b3e50e 100644 (file)
@@ -184,7 +184,7 @@ array(10) {
   ["Accept-Ranges"]=>
   string(5) "bytes"
   ["Content-Length"]=>
-  int(0)
+  string(1) "0"
   ["Vary"]=>
   string(15) "Accept-Encoding"
   ["Connection"]=>
@@ -197,7 +197,6 @@ array(10) {
 GET /default/empty.txt HTTP/1.1
 Host: localhost
 Connection: close
-Content-Length: 0
 HTTP/1.1 200 OK
 Date: Thu, 26 Aug 2010 09:55:09 GMT
 Server: Apache/2.2.16 (Unix) mod_ssl/2.2.16 OpenSSL/1.0.0a mod_fastcgi/2.4.6
@@ -205,7 +204,6 @@ Last-Modified: Wed, 28 Apr 2010 10:54:37 GMT
 Etag: "2002a-0-48549d615a35c"
 Accept-Ranges: bytes
 Vary: Accept-Encoding
-Content-Length: 0
 Connection: close
 Content-Type: text/plain
 X-Original-Content-Length: 20
@@ -214,7 +212,7 @@ string(3) "1.1"
 bool(true)
 int(200)
 string(2) "OK"
-array(11) {
+array(10) {
   ["Date"]=>
   string(29) "Thu, 26 Aug 2010 09:55:09 GMT"
   ["Server"]=>
@@ -227,8 +225,6 @@ array(11) {
   string(5) "bytes"
   ["Vary"]=>
   string(15) "Accept-Encoding"
-  ["Content-Length"]=>
-  int(0)
   ["Connection"]=>
   string(5) "close"
   ["Content-Type"]=>
@@ -242,7 +238,6 @@ GET /default/empty.txt HTTP/1.1
 Host: localhost
 Accept-Encoding: gzip
 Connection: close
-Content-Length: 0
 HTTP/1.1 200 OK
 Date: Thu, 26 Aug 2010 11:41:02 GMT
 Server: Apache/2.2.16 (Unix) mod_ssl/2.2.16 OpenSSL/1.0.0a mod_fastcgi/2.4.6
@@ -277,7 +272,6 @@ array(8) {
 GET /default/empty.php HTTP/1.1
 Connection: close
 Host: localhost
-Content-Length: 0
 HTTP/1.1 200 OK
 Date: Thu, 26 Aug 2010 12:51:28 GMT
 Server: Apache/2.2.16 (Unix) mod_ssl/2.2.16 OpenSSL/1.0.0a mod_fastcgi/2.4.6
@@ -311,7 +305,6 @@ array(7) {
 GET /cgi-bin/chunked.sh HTTP/1.1
 Host: localhost
 Connection: close
-Content-Length: 0
 ---
 HTTP/1.1 200 OK
 Date: Wed, 25 Aug 2010 12:11:44 GMT
@@ -340,7 +333,7 @@ array(10) {
   ["Accept-Ranges"]=>
   string(5) "bytes"
   ["Content-Length"]=>
-  int(0)
+  string(1) "0"
   ["Vary"]=>
   string(15) "Accept-Encoding"
   ["Connection"]=>
@@ -353,7 +346,6 @@ array(10) {
 GET /default/empty.txt HTTP/1.1
 Host: localhost
 Connection: close
-Content-Length: 0
 HTTP/1.1 200 OK
 Date: Thu, 26 Aug 2010 09:55:09 GMT
 Server: Apache/2.2.16 (Unix) mod_ssl/2.2.16 OpenSSL/1.0.0a mod_fastcgi/2.4.6
@@ -361,7 +353,6 @@ Last-Modified: Wed, 28 Apr 2010 10:54:37 GMT
 Etag: "2002a-0-48549d615a35c"
 Accept-Ranges: bytes
 Vary: Accept-Encoding
-Content-Length: 0
 Connection: close
 Content-Type: text/plain
 X-Original-Content-Length: 20
@@ -370,7 +361,7 @@ string(3) "1.1"
 bool(true)
 int(200)
 string(2) "OK"
-array(11) {
+array(10) {
   ["Date"]=>
   string(29) "Thu, 26 Aug 2010 09:55:09 GMT"
   ["Server"]=>
@@ -383,8 +374,6 @@ array(11) {
   string(5) "bytes"
   ["Vary"]=>
   string(15) "Accept-Encoding"
-  ["Content-Length"]=>
-  int(0)
   ["Connection"]=>
   string(5) "close"
   ["Content-Type"]=>
@@ -398,7 +387,6 @@ GET /default/empty.txt HTTP/1.1
 Host: localhost
 Accept-Encoding: gzip
 Connection: close
-Content-Length: 0
 HTTP/1.1 200 OK
 Date: Thu, 26 Aug 2010 11:41:02 GMT
 Server: Apache/2.2.16 (Unix) mod_ssl/2.2.16 OpenSSL/1.0.0a mod_fastcgi/2.4.6
@@ -433,7 +421,6 @@ array(8) {
 GET /default/empty.php HTTP/1.1
 Connection: close
 Host: localhost
-Content-Length: 0
 HTTP/1.1 200 OK
 Date: Thu, 26 Aug 2010 12:51:28 GMT
 Server: Apache/2.2.16 (Unix) mod_ssl/2.2.16 OpenSSL/1.0.0a mod_fastcgi/2.4.6
@@ -467,5 +454,4 @@ array(7) {
 GET /cgi-bin/chunked.sh HTTP/1.1
 Host: localhost
 Connection: close
-Content-Length: 0
 Done
diff --git a/tests/message016.phpt b/tests/message016.phpt
new file mode 100644 (file)
index 0000000..c29d8d6
--- /dev/null
@@ -0,0 +1,24 @@
+--TEST--
+message content range
+--SKIPIF--
+<?php 
+include "skipif.inc";
+?>
+--FILE--
+<?php 
+echo "Test\n";
+echo new http\Message(fopen(__DIR__."/data/message_r_content_range.txt", "r"));
+?>
+===DONE===
+--EXPECT--
+Test
+PUT / HTTP/1.1
+User-Agent: PECL_HTTP/2.3.0dev PHP/5.6.6-dev libcurl/7.41.0-DEV
+Host: localhost:8000
+Accept: */*
+Expect: 100-continue
+Content-Length: 3
+Content-Range: bytes 1-2/3
+X-Original-Content-Length: 3
+
+23===DONE===
index f4ec2d90967a01e42350817d1a93f8cf776f63ce..d2d22a50aafe2c2d1eaa857f8eb59878c7bb3d93 100644 (file)
@@ -12,6 +12,7 @@ echo "Test\n";
 use http\Message\Parser;
 
 foreach (glob(__DIR__."/data/message_*.txt") as $file) {
+       $string = "";
        $parser = new Parser;
        $fd = fopen($file, "r") or die("Could not open $file");
        while (!feof($fd)) {
@@ -23,6 +24,11 @@ foreach (glob(__DIR__."/data/message_*.txt") as $file) {
                        throw new Exception(($e = error_get_last()) ? $e["message"] : "Could not parse $file");
                }
        }
+       
+       if (!$string) {
+               $s = ["START", "HEADER", "HEADER_DONE", "BODY", "BODY_DUMB", "BODY_LENGTH", "BODY_CHUNK", "BODY_DONE", "UPDATE_CL", "DONE"];
+               printf("Unexpected state: %s (%s)\n", $s[$parser->getState()], $file);
+       }
 
        $parser = new Parser;
        rewind($fd);
@@ -38,12 +44,14 @@ foreach (glob(__DIR__."/data/message_*.txt") as $file) {
        if ($string !== (string) $message) {
                $a = explode("\n", $string);
                $b = explode("\n", (string) $message);
-               while ((null !== ($aa = array_shift($a))) || (null !== ($bb = array_shift($b)))) {
+               do {
+                       $aa = array_shift($a);
+                       $bb = array_shift($b);
                        if ($aa !== $bb) {
                                isset($aa) and printf("-- %s\n", $aa);
                                isset($bb) and printf("++ %s\n", $bb);
                        }
-               }
+               } while ($a || $b);
        }
 }
 ?>
index 2030e93cfd448147006b1557944c40ea1000685c..71f2b5e2b2741874893336cc0caf340d22fedd83 100644 (file)
@@ -54,7 +54,7 @@ object(http\Message)#%d (9) {
     ["Host"]=>
     string(9) "localhost"
     ["Content-Length"]=>
-    int(3)
+    string(1) "3"
     ["X-Original-Content-Length"]=>
     string(1) "3"
   }