- add support for If-Range header
authorMichael Wallner <mike@php.net>
Thu, 16 Feb 2006 17:49:54 +0000 (17:49 +0000)
committerMichael Wallner <mike@php.net>
Thu, 16 Feb 2006 17:49:54 +0000 (17:49 +0000)
- fix cases where 412 Precondition failed should be generated

http_send_api.c
package2.xml
php_http.h
phpstr/phpstr.c
phpstr/phpstr.h
tests/send_failed_precond_001.phpt [new file with mode: 0644]
tests/send_failed_precond_002.phpt [new file with mode: 0644]
tests/send_ifrange_001.phpt [new file with mode: 0644]
tests/send_ifrange_002.phpt [new file with mode: 0644]
tests/send_ifrange_003.phpt [new file with mode: 0644]
tests/send_ifrange_004.phpt [new file with mode: 0644]

index 1c6fd6bad2287554ba07bab270d9ebabdfe377ee..fd4c4745b627f3ab5d05df27d11b1731a18162d5 100644 (file)
@@ -27,9 +27,9 @@
 #include "php_http_headers_api.h"
 #include "php_http_send_api.h"
 
-#define http_flush(d, l) _http_flush((d), (l) TSRMLS_CC)
+#define http_flush(d, l) _http_flush(NULL, (d), (l) TSRMLS_CC)
 /* {{{ static inline void http_flush() */
-static inline void _http_flush(const char *data, size_t data_len TSRMLS_DC)
+static inline void _http_flush(void *nothing, const char *data, size_t data_len TSRMLS_DC)
 {
        PHPWRITE(data, data_len);
        php_end_ob_buffer(1, 1 TSRMLS_CC);
@@ -88,13 +88,13 @@ static inline void _http_send_response_data_plain(void **buffer, const char *dat
                http_encoding_stream *s = *((http_encoding_stream **) buffer);
                
                http_encoding_deflate_stream_update(s, data, data_len, &encoded, &encoded_len);
-               phpstr_chunked_output((phpstr **) &s->storage, encoded, encoded_len, HTTP_G->send.buffer_size, _http_flush TSRMLS_CC);
+               phpstr_chunked_output((phpstr **) &s->storage, encoded, encoded_len, HTTP_G->send.buffer_size, _http_flush, NULL TSRMLS_CC);
                efree(encoded);
 #else
                http_error(HE_ERROR, HTTP_E_RESPONSE, "Attempt to send GZIP response despite being able to do so; please report this bug");
 #endif
        } else {
-               phpstr_chunked_output((phpstr **) buffer, data, data_len, HTTP_G->send.buffer_size, _http_flush TSRMLS_CC);
+               phpstr_chunked_output((phpstr **) buffer, data, data_len, HTTP_G->send.buffer_size, _http_flush, NULL TSRMLS_CC);
        }
 }
 /* }}} */
@@ -159,14 +159,14 @@ static inline void _http_send_response_finish(void **buffer TSRMLS_DC)
                http_encoding_stream *s = *((http_encoding_stream **) buffer);
                
                http_encoding_deflate_stream_finish(s, &encoded, &encoded_len);
-               phpstr_chunked_output((phpstr **) &s->storage, encoded, encoded_len, 0, _http_flush TSRMLS_CC);
+               phpstr_chunked_output((phpstr **) &s->storage, encoded, encoded_len, 0, _http_flush, NULL TSRMLS_CC);
                http_encoding_deflate_stream_free(&s);
                STR_FREE(encoded);
 #else
                http_error(HE_ERROR, HTTP_E_RESPONSE, "Attempt to send GZIP response despite being able to do so; please report this bug");
 #endif
        } else {
-               phpstr_chunked_output((phpstr **) buffer, NULL, 0, 0, _http_flush TSRMLS_CC);
+               phpstr_chunked_output((phpstr **) buffer, NULL, 0, 0, _http_flush, NULL TSRMLS_CC);
        }
 }
 /* }}} */
@@ -329,72 +329,84 @@ PHP_HTTP_API STATUS _http_send_ex(const void *data_ptr, size_t data_size, http_s
                case RANGE_OK:
                {
                        /* Range Request - only send ranges if entity hasn't changed */
-                       if (    http_match_etag_ex("HTTP_IF_MATCH", HTTP_G->send.unquoted_etag, 0) &&
-                                       http_match_last_modified_ex("HTTP_IF_UNMODIFIED_SINCE", HTTP_G->send.last_modified, 0) &&
-                                       http_match_last_modified_ex("HTTP_UNLESS_MODIFIED_SINCE", HTTP_G->send.last_modified, 0)) {
+                       if (    http_got_server_var("HTTP_IF_RANGE") &&
+                                       !http_match_etag("HTTP_IF_RANGE", HTTP_G->send.unquoted_etag) &&
+                                       !http_match_last_modified("HTTP_IF_RANGE", HTTP_G->send.last_modified)) {
+                               /* fallthrough to send full entity with 200 Ok */
+                               no_cache = 1;
+                       } else if (     !http_match_etag_ex("HTTP_IF_MATCH", HTTP_G->send.unquoted_etag, 0) ||
+                                               !http_match_last_modified_ex("HTTP_IF_UNMODIFIED_SINCE", HTTP_G->send.last_modified, 0) ||
+                                               !http_match_last_modified_ex("HTTP_UNLESS_MODIFIED_SINCE", HTTP_G->send.last_modified, 0)) {
+                               /* 412 Precondition failed */
+                               zend_hash_destroy(&ranges);
+                               http_send_status(412);
+                               return FAILURE;
+                       } else if (zend_hash_num_elements(&ranges) == 1) {
+                               /* single range */
+                               zval **range, **begin, **end;
                                
-                               if (zend_hash_num_elements(&ranges) == 1) {
-                                       /* single range */
-                                       zval **range, **begin, **end;
-                                       
-                                       if (    SUCCESS == zend_hash_index_find(&ranges, 0, (void **) &range) &&
-                                                       SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(range), 0, (void **) &begin) &&
-                                                       SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(range), 1, (void **) &end)) {
-                                               char range_header_str[256];
-                                               size_t range_header_len;
-                                               
-                                               range_header_len = snprintf(range_header_str, lenof(range_header_str), "Content-Range: bytes %ld-%ld/%zu", Z_LVAL_PP(begin), Z_LVAL_PP(end), data_size);
-                                               http_send_status_header_ex(206, range_header_str, range_header_len, 1);
-                                               http_send_response_start(&s, Z_LVAL_PP(end)-Z_LVAL_PP(begin)+1);
-                                               http_send_response_data_fetch(&s, data_ptr, data_size, data_mode, Z_LVAL_PP(begin), Z_LVAL_PP(end) + 1);
-                                               http_send_response_finish(&s);
-                                               zend_hash_destroy(&ranges);
-                                               return SUCCESS;
-                                       }
+                               if (    SUCCESS != zend_hash_index_find(&ranges, 0, (void **) &range) ||
+                                               SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(range), 0, (void **) &begin) ||
+                                               SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(range), 1, (void **) &end)) {
+                                       /* this should never happen */
+                                       zend_hash_destroy(&ranges);
+                                       http_send_status(500);
+                                       return FAILURE;
                                } else {
-                                       /* multi range */
-                                       HashPosition pos;
-                                       zval **range, **begin, **end;
-                                       const char *content_type = HTTP_G->send.content_type;
-                                       char boundary_str[32], range_header_str[256];
-                                       size_t boundary_len, range_header_len;
-                                       
-                                       boundary_len = snprintf(boundary_str, lenof(boundary_str), "%lu%0.9f", (ulong) HTTP_GET_REQUEST_TIME(), (float) php_combined_lcg(TSRMLS_C));
-                                       range_header_len = snprintf(range_header_str, lenof(range_header_str), "Content-Type: multipart/byteranges; boundary=%s", boundary_str);
+                                       char range_header_str[256];
+                                       size_t range_header_len;
                                        
+                                       range_header_len = snprintf(range_header_str, lenof(range_header_str), "Content-Range: bytes %ld-%ld/%zu", Z_LVAL_PP(begin), Z_LVAL_PP(end), data_size);
                                        http_send_status_header_ex(206, range_header_str, range_header_len, 1);
-                                       http_send_response_start(&s, 0);
-                                       
-                                       if (!content_type) {
-                                               content_type = "application/x-octetstream";
-                                       }
-                                       
-                                       FOREACH_HASH_VAL(pos, &ranges, range) {
-                                               if (    SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(range), 0, (void **) &begin) &&
-                                                               SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(range), 1, (void **) &end)) {
-                                                       char preface_str[512];
-                                                       size_t preface_len;
+                                       http_send_response_start(&s, Z_LVAL_PP(end)-Z_LVAL_PP(begin)+1);
+                                       http_send_response_data_fetch(&s, data_ptr, data_size, data_mode, Z_LVAL_PP(begin), Z_LVAL_PP(end) + 1);
+                                       http_send_response_finish(&s);
+                                       zend_hash_destroy(&ranges);
+                                       return SUCCESS;
+                               }
+                       } else {
+                               /* multi range */
+                               HashPosition pos;
+                               zval **range, **begin, **end;
+                               const char *content_type = HTTP_G->send.content_type;
+                               char boundary_str[32], range_header_str[256];
+                               size_t boundary_len, range_header_len;
+                               
+                               boundary_len = snprintf(boundary_str, lenof(boundary_str), "%lu%0.9f", (ulong) HTTP_GET_REQUEST_TIME(), (float) php_combined_lcg(TSRMLS_C));
+                               range_header_len = snprintf(range_header_str, lenof(range_header_str), "Content-Type: multipart/byteranges; boundary=%s", boundary_str);
+                               
+                               http_send_status_header_ex(206, range_header_str, range_header_len, 1);
+                               http_send_response_start(&s, 0);
+                               
+                               if (!content_type) {
+                                       content_type = "application/x-octetstream";
+                               }
+                               
+                               FOREACH_HASH_VAL(pos, &ranges, range) {
+                                       if (    SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(range), 0, (void **) &begin) &&
+                                                       SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(range), 1, (void **) &end)) {
+                                               char preface_str[512];
+                                               size_t preface_len;
 
 #define HTTP_RANGE_PREFACE \
        HTTP_CRLF "--%s" \
        HTTP_CRLF "Content-Type: %s" \
        HTTP_CRLF "Content-Range: bytes %ld-%ld/%zu" \
        HTTP_CRLF HTTP_CRLF
-                                                       
-                                                       preface_len = snprintf(preface_str, lenof(preface_str), HTTP_RANGE_PREFACE, boundary_str, content_type, Z_LVAL_PP(begin), Z_LVAL_PP(end), data_size);
-                                                       http_send_response_data_plain(&s, preface_str, preface_len);
-                                                       http_send_response_data_fetch(&s, data_ptr, data_size, data_mode, Z_LVAL_PP(begin), Z_LVAL_PP(end) + 1);
-                                               }
+                                               
+                                               preface_len = snprintf(preface_str, lenof(preface_str), HTTP_RANGE_PREFACE, boundary_str, content_type, Z_LVAL_PP(begin), Z_LVAL_PP(end), data_size);
+                                               http_send_response_data_plain(&s, preface_str, preface_len);
+                                               http_send_response_data_fetch(&s, data_ptr, data_size, data_mode, Z_LVAL_PP(begin), Z_LVAL_PP(end) + 1);
                                        }
-                                       
-                                       http_send_response_data_plain(&s, HTTP_CRLF "--", lenof(HTTP_CRLF "--"));
-                                       http_send_response_data_plain(&s, boundary_str, boundary_len);
-                                       http_send_response_data_plain(&s, "--", lenof("--"));
-                                       
-                                       http_send_response_finish(&s);
-                                       zend_hash_destroy(&ranges);
-                                       return SUCCESS;
                                }
+                               
+                               http_send_response_data_plain(&s, HTTP_CRLF "--", lenof(HTTP_CRLF "--"));
+                               http_send_response_data_plain(&s, boundary_str, boundary_len);
+                               http_send_response_data_plain(&s, "--", lenof("--"));
+                               
+                               http_send_response_finish(&s);
+                               zend_hash_destroy(&ranges);
+                               return SUCCESS;
                        }
                }
                case RANGE_NO:
index 6d63ec71506efcd8ff25609da67425419f8bf1bc..5a7935afdc725d96394ca92ecc70e86b8afd0747 100644 (file)
@@ -37,7 +37,7 @@ HttpResponse
  </lead>
  <date>2006-00-00</date>
  <version>
-  <release>0.23.2</release>
+  <release>0.24.0dev</release>
   <api>0.23.0</api>
  </version>
  <stability>
@@ -46,6 +46,9 @@ HttpResponse
  </stability>
  <license>BSD, revised</license>
  <notes><![CDATA[
++ Added If-Range header recognition.
+
+* Fixed cases where 412 Precondition failed should be sent.
 * Fixed message chain order in HttpRequest::getHistory().
 ]]></notes>
  <contents>
index 94bf26f382edf6b633694c222ea10fdcdef5394c..4b5aaa557fffa24f9965d2ff86f79f4a6b55e146 100644 (file)
@@ -15,7 +15,7 @@
 #ifndef PHP_EXT_HTTP_H
 #define PHP_EXT_HTTP_H
 
-#define PHP_EXT_HTTP_VERSION "0.23.2"
+#define PHP_EXT_HTTP_VERSION "0.24.0dev"
 
 #ifdef HAVE_CONFIG_H
 #      include "config.h"
index 98a8e63ebcc8bd52b83b1c93235b76a9e11074b1..a312b1e773134dbe2ad558aeaaffaa78ff1ec38b 100644 (file)
@@ -13,7 +13,7 @@ PHPSTR_API phpstr *phpstr_init_ex(phpstr *buf, size_t chunk_size, int flags)
        }
 
        if (buf) {
-               buf->size = (chunk_size > 0) ? chunk_size : PHPSTR_DEFAULT_SIZE;
+               buf->size = (chunk_size) ? chunk_size : PHPSTR_DEFAULT_SIZE;
                buf->pmem = (flags & PHPSTR_INIT_PERSISTENT) ? 1 : 0;
                buf->data = (flags & PHPSTR_INIT_PREALLOC) ? pemalloc(buf->size, buf->pmem) : NULL;
                buf->free = (flags & PHPSTR_INIT_PREALLOC) ? buf->size : 0;
@@ -240,22 +240,16 @@ PHPSTR_API phpstr *phpstr_right(const phpstr *buf, size_t length)
 
 PHPSTR_API phpstr *phpstr_merge_va(phpstr *buf, unsigned argc, va_list argv)
 {
-       unsigned f = 0, i = 0;
+       unsigned i = 0;
        buf = phpstr_init(buf);
 
        if (buf) {
                while (argc > i++) {
                        phpstr_free_t f = va_arg(argv, phpstr_free_t);
                        phpstr *current = va_arg(argv, phpstr *);
-                       if (NOMEM == phpstr_append(buf, current->data, current->used)) {
-                               f = 1;
-                       }
+                       phpstr_append(buf, current->data, current->used);
                        FREE_PHPSTR(f, current);
                }
-               
-               if (f) {
-                       phpstr_free(&buf);
-               }
        }
 
        return buf;
@@ -360,13 +354,13 @@ PHPSTR_API size_t phpstr_chunk_buffer(phpstr **s, const char *data, size_t data_
        return 0;
 }
 
-PHPSTR_API void phpstr_chunked_output(phpstr **s, const char *data, size_t data_len, size_t chunk_len, void (*passthru)(const char *, size_t TSRMLS_DC) TSRMLS_DC)
+PHPSTR_API void phpstr_chunked_output(phpstr **s, const char *data, size_t data_len, size_t chunk_len, phpstr_passthru_func passthru, void *opaque TSRMLS_DC)
 {
        char *chunk = NULL;
        size_t got = 0;
        
        while ((got = phpstr_chunk_buffer(s, data, data_len, &chunk, chunk_len))) {
-               passthru(chunk, got TSRMLS_CC);
+               passthru(opaque, chunk, got TSRMLS_CC);
                if (!chunk_len) {
                        /*      we already got the last chunk,
                                and freed all resources */
index 976a795b4368e393e1e94732b5b5ff1d446469fa..9b6628e75a7bb1b92059fd13871463d358559b66 100644 (file)
@@ -93,7 +93,7 @@ typedef enum {
 
 /* create a new phpstr */
 #define phpstr_new() phpstr_init(NULL)
-#define phpstr_init(b) phpstr_init_ex(b, 0, 0)
+#define phpstr_init(b) phpstr_init_ex(b, PHPSTR_DEFAULT_SIZE, 0)
 #define phpstr_clone(phpstr_pointer) phpstr_init_ex(NULL, (phpstr_pointer)->size, (phpstr_pointer)->pmem ? PHPSTR_INIT_PERSISTENT:0)
 PHPSTR_API phpstr *phpstr_init_ex(phpstr *buf, size_t chunk_size, int flags);
 
@@ -173,8 +173,10 @@ PHPSTR_API void phpstr_free(phpstr **buf);
 /* stores data in a phpstr until it reaches chunk_size */
 PHPSTR_API size_t phpstr_chunk_buffer(phpstr **s, const char *data, size_t data_len, char **chunk, size_t chunk_size);
 
+typedef void (*phpstr_passthru_func)(void *opaque, const char *, size_t TSRMLS_DC);
+
 /* wrapper around phpstr_chunk_buffer, which passes available chunks to passthru() */
-PHPSTR_API void phpstr_chunked_output(phpstr **s, const char *data, size_t data_len, size_t chunk_size, void (*passthru)(const char *, size_t TSRMLS_DC) TSRMLS_DC);
+PHPSTR_API void phpstr_chunked_output(phpstr **s, const char *data, size_t data_len, size_t chunk_size, phpstr_passthru_func passthru, void *opaque TSRMLS_DC);
 
 #endif
 
diff --git a/tests/send_failed_precond_001.phpt b/tests/send_failed_precond_001.phpt
new file mode 100644 (file)
index 0000000..6424ecb
--- /dev/null
@@ -0,0 +1,22 @@
+--TEST--
+http_send() failed precondition
+--SKIPIF--
+<?php
+include 'skip.inc';
+checkcgi();
+checkver(5.1);
+?>
+--FILE--
+<?php
+$_SERVER['HTTP_RANGE'] = 'bytes=0-1';
+$_SERVER['HTTP_IF_UNMODIFIED_SINCE'] = http_date(10000);
+http_cache_last_modified();
+http_send_file(__FILE__);
+?>
+--EXPECTF--
+Status: 412
+X-Powered-By: %s
+Cache-Control: private, must-revalidate, max-age=0
+Last-Modified: %s
+Accept-Ranges: bytes
+Content-type: text/html
diff --git a/tests/send_failed_precond_002.phpt b/tests/send_failed_precond_002.phpt
new file mode 100644 (file)
index 0000000..6494ffd
--- /dev/null
@@ -0,0 +1,21 @@
+--TEST--
+http_send() failed precondition
+--SKIPIF--
+<?php
+include 'skip.inc';
+checkcgi();
+checkmax(5);
+?>
+--FILE--
+<?php
+$_SERVER['HTTP_RANGE'] = 'bytes=0-1';
+$_SERVER['HTTP_IF_UNMODIFIED_SINCE'] = http_date(10000);
+http_cache_last_modified();
+http_send_file(__FILE__);
+?>
+--EXPECTF--
+Status: 412
+X-Powered-By: %s
+Cache-Control: private, must-revalidate, max-age=0
+Last-Modified: %s
+Accept-Ranges: bytes
diff --git a/tests/send_ifrange_001.phpt b/tests/send_ifrange_001.phpt
new file mode 100644 (file)
index 0000000..4328226
--- /dev/null
@@ -0,0 +1,25 @@
+--TEST--
+http_send() If-Range
+--SKIPIF--
+<?php
+include 'skip.inc';
+checkcgi();
+?>
+--FILE--
+<?php
+$_SERVER['HTTP_RANGE'] = 'bytes=0-1';
+$_SERVER['HTTP_IF_RANGE'] = '"abc"';
+http_cache_etag('abc');
+http_send_file(__FILE__);
+?>
+--EXPECTF--
+Status: 206
+X-Powered-By: %s
+Cache-Control: private, must-revalidate, max-age=0
+ETag: "abc"
+Accept-Ranges: bytes
+Content-Range: bytes 0-1/%d
+Content-Length: 2
+Content-type: text/html
+
+<?
diff --git a/tests/send_ifrange_002.phpt b/tests/send_ifrange_002.phpt
new file mode 100644 (file)
index 0000000..60053e9
--- /dev/null
@@ -0,0 +1,25 @@
+--TEST--
+http_send() If-Range
+--SKIPIF--
+<?php
+include 'skip.inc';
+checkcgi();
+checkmax(5);
+?>
+--FILE--
+<?php
+$_SERVER['HTTP_RANGE'] = 'bytes=0-1';
+$_SERVER['HTTP_IF_RANGE'] = '"abc"';
+http_cache_etag('abc');
+http_send_file(__FILE__);
+?>
+--EXPECTF--
+Status: 206
+X-Powered-By: %s
+Cache-Control: private, must-revalidate, max-age=0
+ETag: "abc"
+Accept-Ranges: bytes
+Content-Range: bytes 0-1/%d
+Content-Length: 2
+
+<?
diff --git a/tests/send_ifrange_003.phpt b/tests/send_ifrange_003.phpt
new file mode 100644 (file)
index 0000000..902130f
--- /dev/null
@@ -0,0 +1,23 @@
+--TEST--
+http_send() If-Range
+--SKIPIF--
+<?php
+include 'skip.inc';
+checkcgi();
+?>
+--FILE--
+<?php
+$_SERVER['HTTP_RANGE'] = 'bytes=0-1';
+$_SERVER['HTTP_IF_RANGE'] = '"abcd"';
+http_cache_etag('abc');
+http_send_file(__FILE__);
+?>
+--EXPECTF--
+X-Powered-By: %s
+Cache-Control: private, must-revalidate, max-age=0
+ETag: "abc"
+Accept-Ranges: bytes
+Content-Length: %d
+Content-type: text/html
+
+%s
diff --git a/tests/send_ifrange_004.phpt b/tests/send_ifrange_004.phpt
new file mode 100644 (file)
index 0000000..8f3f429
--- /dev/null
@@ -0,0 +1,23 @@
+--TEST--
+http_send() If-Range
+--SKIPIF--
+<?php
+include 'skip.inc';
+checkcgi();
+checkmax(5);
+?>
+--FILE--
+<?php
+$_SERVER['HTTP_RANGE'] = 'bytes=0-1';
+$_SERVER['HTTP_IF_RANGE'] = '"abcd"';
+http_cache_etag('abc');
+http_send_file(__FILE__);
+?>
+--EXPECTF--
+X-Powered-By: %s
+Cache-Control: private, must-revalidate, max-age=0
+ETag: "abc"
+Accept-Ranges: bytes
+Content-Length: %d
+
+%s