From: Michael Wallner Date: Thu, 16 Feb 2006 17:49:54 +0000 (+0000) Subject: - add support for If-Range header X-Git-Tag: RELEASE_0_24_0~8 X-Git-Url: https://git.m6w6.name/?p=m6w6%2Fext-http;a=commitdiff_plain;h=5685b2140c8948fe617766ce154bb7462729ea6f - add support for If-Range header - fix cases where 412 Precondition failed should be generated --- diff --git a/http_send_api.c b/http_send_api.c index 1c6fd6b..fd4c474 100644 --- a/http_send_api.c +++ b/http_send_api.c @@ -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: diff --git a/package2.xml b/package2.xml index 6d63ec7..5a7935a 100644 --- a/package2.xml +++ b/package2.xml @@ -37,7 +37,7 @@ HttpResponse 2006-00-00 - 0.23.2 + 0.24.0dev 0.23.0 @@ -46,6 +46,9 @@ HttpResponse BSD, revised diff --git a/php_http.h b/php_http.h index 94bf26f..4b5aaa5 100644 --- a/php_http.h +++ b/php_http.h @@ -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" diff --git a/phpstr/phpstr.c b/phpstr/phpstr.c index 98a8e63..a312b1e 100644 --- a/phpstr/phpstr.c +++ b/phpstr/phpstr.c @@ -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 */ diff --git a/phpstr/phpstr.h b/phpstr/phpstr.h index 976a795..9b6628e 100644 --- a/phpstr/phpstr.h +++ b/phpstr/phpstr.h @@ -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 index 0000000..6424ecb --- /dev/null +++ b/tests/send_failed_precond_001.phpt @@ -0,0 +1,22 @@ +--TEST-- +http_send() failed precondition +--SKIPIF-- + +--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 index 0000000..6494ffd --- /dev/null +++ b/tests/send_failed_precond_002.phpt @@ -0,0 +1,21 @@ +--TEST-- +http_send() failed precondition +--SKIPIF-- + +--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 index 0000000..4328226 --- /dev/null +++ b/tests/send_ifrange_001.phpt @@ -0,0 +1,25 @@ +--TEST-- +http_send() If-Range +--SKIPIF-- + +--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 + + +--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 + + +--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 index 0000000..8f3f429 --- /dev/null +++ b/tests/send_ifrange_004.phpt @@ -0,0 +1,23 @@ +--TEST-- +http_send() If-Range +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +X-Powered-By: %s +Cache-Control: private, must-revalidate, max-age=0 +ETag: "abc" +Accept-Ranges: bytes +Content-Length: %d + +%s