-
-/* {{{ http_range_status http_get_request_ranges(HashTable *ranges, size_t) */
-PHP_HTTP_API http_range_status _http_get_request_ranges(HashTable *ranges,
- const size_t length TSRMLS_DC)
-{
- zval *zrange;
- char *range, c;
- long begin = -1, end = -1, *ptr;
-
- HTTP_GSC(zrange, "HTTP_RANGE", RANGE_NO);
- range = Z_STRVAL_P(zrange);
-
- if (strncmp(range, "bytes=", sizeof("bytes=") - 1)) {
- php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Range header misses bytes=");
- return RANGE_NO;
- }
-
- ptr = &begin;
- range += sizeof("bytes=") - 1;
-
- do {
- switch (c = *(range++))
- {
- case '0':
- *ptr *= 10;
- break;
-
- case '1': case '2': case '3':
- case '4': case '5': case '6':
- case '7': case '8': case '9':
- /*
- * If the value of the pointer is already set (non-negative)
- * then multiply its value by ten and add the current value,
- * else initialise the pointers value with the current value
- * --
- * This let us recognize empty fields when validating the
- * ranges, i.e. a "-10" for begin and "12345" for the end
- * was the following range request: "Range: bytes=0-12345";
- * While a "-1" for begin and "12345" for the end would
- * have been: "Range: bytes=-12345".
- */
- if (*ptr > 0) {
- *ptr *= 10;
- *ptr += c - '0';
- } else {
- *ptr = c - '0';
- }
- break;
-
- case '-':
- ptr = &end;
- break;
-
- case ' ':
- /* IE - ignore for now */
- break;
-
- case 0:
- case ',':
-
- if (length) {
- /* validate ranges */
- switch (begin)
- {
- /* "0-12345" */
- case -10:
- if (length <= end) {
- return RANGE_ERR;
- }
- begin = 0;
- break;
-
- /* "-12345" */
- case -1:
- if (length <= end) {
- return RANGE_ERR;
- }
- begin = length - end;
- end = length - 1;
- break;
-
- /* "12345-(xxx)" */
- default:
- switch (end)
- {
- /* "12345-" */
- case -1:
- if (length <= begin) {
- return RANGE_ERR;
- }
- end = length - 1;
- break;
-
- /* "12345-67890" */
- default:
- if ( (length <= begin) ||
- (length <= end) ||
- (end < begin)) {
- return RANGE_ERR;
- }
- break;
- }
- break;
- }
- }
- {
- zval *zentry;
- MAKE_STD_ZVAL(zentry);
- array_init(zentry);
- add_index_long(zentry, 0, begin);
- add_index_long(zentry, 1, end);
- zend_hash_next_index_insert(ranges, &zentry, sizeof(zval *), NULL);
-
- begin = -1;
- end = -1;
- ptr = &begin;
- }
- break;
-
- default:
- return RANGE_NO;
- break;
- }
- } while (c != 0);
-
- return RANGE_OK;
-}
-/* }}} */
-
-/* {{{ STATUS http_send_ranges(HashTable *, void *, size_t, http_send_mode) */
-PHP_HTTP_API STATUS _http_send_ranges(HashTable *ranges, const void *data, const size_t size, const http_send_mode mode TSRMLS_DC)
-{
- long **begin, **end;
- zval **zrange;
-
- /* single range */
- if (zend_hash_num_elements(ranges) == 1) {
- char range_header[256] = {0};
-
- if (SUCCESS != zend_hash_index_find(ranges, 0, (void **) &zrange) ||
- SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 0, (void **) &begin) ||
- SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 1, (void **) &end)) {
- return FAILURE;
- }
-
- /* Send HTTP 206 Partial Content */
- http_send_status(206);
-
- /* send content range header */
- snprintf(range_header, 255, "Content-Range: bytes %d-%d/%d", **begin, **end, size);
- http_send_header(range_header);
-
- /* send requested chunk */
- return http_send_chunk(data, **begin, **end + 1, mode);
- }
-
- /* multi range */
- else {
- char bound[23] = {0}, preface[1024] = {0},
- multi_header[68] = "Content-Type: multipart/byteranges; boundary=";
-
- /* Send HTTP 206 Partial Content */
- http_send_status(206);
-
- /* send multipart/byteranges header */
- snprintf(bound, 22, "--%d%0.9f", time(NULL), php_combined_lcg(TSRMLS_C));
- strncat(multi_header, bound + 2, 21);
- http_send_header(multi_header);
-
- /* send each requested chunk */
- FOREACH_HASH_VAL(ranges, zrange) {
- if (SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 0, (void **) &begin) ||
- SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(zrange), 1, (void **) &end)) {
- break;
- }
-
- snprintf(preface, 1023,
- HTTP_CRLF "%s"
- HTTP_CRLF "Content-Type: %s"
- HTTP_CRLF "Content-Range: bytes %ld-%ld/%ld"
- HTTP_CRLF
- HTTP_CRLF,
-
- bound,
- HTTP_G(ctype) ? HTTP_G(ctype) : "application/x-octetstream",
- **begin,
- **end,
- size
- );
-
- php_body_write(preface, strlen(preface) TSRMLS_CC);
- http_send_chunk(data, **begin, **end + 1, mode);
- }
-
- /* write boundary once more */
- php_body_write(HTTP_CRLF, sizeof(HTTP_CRLF) - 1 TSRMLS_CC);
- php_body_write(bound, strlen(bound) TSRMLS_CC);
- php_body_write("--", 2 TSRMLS_CC);
-
- return SUCCESS;
- }
-}
-/* }}} */
-
-/* {{{ STATUS http_send(void *, size_t, http_send_mode) */
-PHP_HTTP_API STATUS _http_send(const void *data_ptr, const size_t data_size,
- const http_send_mode data_mode TSRMLS_DC)
-{
- HashTable ranges;
- http_range_status range_status;
-
- if (!data_ptr) {
- return FAILURE;
- }
- if (!data_size) {
- return SUCCESS;
- }
-
- zend_hash_init(&ranges, 0, NULL, ZVAL_PTR_DTOR, 0);
- range_status = http_get_request_ranges(&ranges, data_size);
-
- if (range_status != RANGE_OK) {
- zend_hash_destroy(&ranges);
- }
-
- if (range_status == RANGE_ERR) {
- http_send_status(416);
- return FAILURE;
- }
-
- /* etag handling */
-
- if (HTTP_G(etag_started)) {
- char *etag;
-
- /* interrupt */
- HTTP_G(etag_started) = 0;
- /* never ever use the output to compute the ETag if http_send() is used */
- php_end_ob_buffer(0, 0 TSRMLS_CC);
-
- if (!(etag = http_etag(data_ptr, data_size, data_mode))) {
- return FAILURE;
- }
-
- /* send 304 Not Modified if etag matches */
- if ((range_status == RANGE_NO) && http_etag_match("HTTP_IF_NONE_MATCH", etag)) {
- efree(etag);
- return http_send_status(304);
- }
-
- http_send_etag(etag, 32);
- efree(etag);
- }
-
- switch (range_status) {
- /* breaks intentionally left out */
-
- case RANGE_OK:
- /* only send ranges if entity hasn't changed */
- if ( http_etag_match_ex("HTTP_IF_MATCH", HTTP_G(etag), 0) &&
- http_modified_match_ex("HTTP_IF_UNMODIFIED_SINCE", HTTP_G(lmod), 0)) {
- STATUS result = http_send_ranges(&ranges, data_ptr, data_size, data_mode);
- zend_hash_destroy(&ranges);
- return result;
- } else {
- zend_hash_destroy(&ranges);
- }
-
- case RANGE_NO:
- if (http_modified_match("HTTP_IF_MODIFIED_SINCE", HTTP_G(lmod))) {
- return http_send_status(304);
- }
- return http_send_chunk(data_ptr, 0, data_size, data_mode);
- }
-}
-/* }}} */
-
-/* {{{ STATUS http_send_stream(php_stream *) */
-PHP_HTTP_API STATUS _http_send_stream_ex(php_stream *file,
- zend_bool close_stream TSRMLS_DC)
-{
- STATUS status;
-
- if ((!file) || php_stream_stat(file, &HTTP_G(ssb))) {
- return FAILURE;
- }
-
- status = http_send(file, HTTP_G(ssb).sb.st_size, SEND_RSRC);
-
- if (close_stream) {
- php_stream_close(file);
- }
-
- return status;
-}
-/* }}} */
-
-/* {{{ STATUS http_chunked_decode(char *, size_t, char **, size_t *) */
-PHP_HTTP_API STATUS _http_chunked_decode(const char *encoded,
- const size_t encoded_len, char **decoded, size_t *decoded_len TSRMLS_DC)
-{
- const char *e_ptr;
- char *d_ptr;
-
- *decoded_len = 0;
- *decoded = ecalloc(1, encoded_len);
- d_ptr = *decoded;
- e_ptr = encoded;
-
- while (((e_ptr - encoded) - encoded_len) > 0) {
- char hex_len[9] = {0};
- size_t chunk_len = 0;
- int i = 0;
-
- /* read in chunk size */
- while (isxdigit(*e_ptr)) {
- if (i == 9) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING,
- "Chunk size is too long: 0x%s...", hex_len);
- efree(*decoded);
- return FAILURE;
- }
- hex_len[i++] = *e_ptr++;
- }
-
- /* reached the end */
- if (!strcmp(hex_len, "0")) {
- break;
- }
-
- /* new line */
- if (strncmp(e_ptr, HTTP_CRLF, 2)) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING,
- "Invalid character (expected 0x0D 0x0A; got: %x %x)",
- *e_ptr, *(e_ptr + 1));
- efree(*decoded);
- return FAILURE;
- }
-
- /* hex to long */
- {
- char *error = NULL;
- chunk_len = strtol(hex_len, &error, 16);
- if (error == hex_len) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING,
- "Invalid chunk size string: '%s'", hex_len);
- efree(*decoded);
- return FAILURE;
- }
- }
-
- memcpy(d_ptr, e_ptr += 2, chunk_len);
- d_ptr += chunk_len;
- e_ptr += chunk_len + 2;
- *decoded_len += chunk_len;
- }
-
- return SUCCESS;
-}
-/* }}} */
-
-/* {{{ STATUS http_split_response(zval *, zval *, zval *) */
-PHP_HTTP_API STATUS _http_split_response(zval *response, zval *headers, zval *body TSRMLS_DC)
-{
- char *b = NULL;
- size_t l = 0;
- STATUS status = http_split_response_ex(Z_STRVAL_P(response), Z_STRLEN_P(response), Z_ARRVAL_P(headers), &b, &l);
- ZVAL_STRINGL(body, b, l, 0);
- return status;
-}
-/* }}} */
-
-/* {{{ STATUS http_split_response(char *, size_t, HashTable *, char **, size_t *) */
-PHP_HTTP_API STATUS _http_split_response_ex(char *response,
- size_t response_len, HashTable *headers, char **body, size_t *body_len TSRMLS_DC)
-{
- char *header = response, *real_body = NULL;
-
- while (0 < (response_len - (response - header + 4))) {
- if ( (*response++ == '\r') &&
- (*response++ == '\n') &&
- (*response++ == '\r') &&
- (*response++ == '\n')) {
- real_body = response;
- break;
- }
- }
-
- if (real_body && (*body_len = (response_len - (real_body - header)))) {
- *body = ecalloc(1, *body_len + 1);
- memcpy(*body, real_body, *body_len);
- }
-
- return http_parse_headers_ex(header, real_body ? response_len - *body_len : response_len, headers, 1);
-}
-/* }}} */
-
-/* {{{ STATUS http_parse_headers(char *, long, zval *) */
-PHP_HTTP_API STATUS _http_parse_headers_ex(char *header, int header_len,
- HashTable *headers, zend_bool prettify TSRMLS_DC)
-{
- char *colon = NULL, *line = NULL, *begin = header;
- zval array;
-
- Z_ARRVAL(array) = headers;
-
- if (header_len < 2) {
- return FAILURE;
- }
-
- /* status code */
- if (!strncmp(header, "HTTP/1.", 7)) {
- char *end = strstr(header, HTTP_CRLF);
- add_assoc_stringl(&array, "Status",
- header + sizeof("HTTP/1.x ") - 1,
- end - (header + sizeof("HTTP/1.x ") - 1), 1);
- header = end + 2;
- }
-
- line = header;
-
- while (header_len >= (line - begin)) {
- int value_len = 0;
-
- switch (*line++)
- {
- case 0:
- --value_len; /* we don't have CR so value length is one char less */
- case '\n':
- if (colon && ((!(*line - 1)) || ((*line != ' ') && (*line != '\t')))) {
-
- /* skip empty key */
- if (header != colon) {
- char *key = estrndup(header, colon - header);
-
- if (prettify) {
- key = pretty_key(key, colon - header, 1, 1);
- }
-
- value_len += line - colon - 1;
-
- /* skip leading ws */
- while (isspace(*(++colon))) --value_len;
- /* skip trailing ws */
- while (isspace(colon[value_len - 1])) --value_len;
-
- if (value_len < 1) {
- /* hm, empty header? */
- add_assoc_stringl(&array, key, "", 0, 1);
- } else {
- add_assoc_stringl(&array, key, colon, value_len, 1);
- }
- efree(key);
- }
-
- colon = NULL;
- value_len = 0;
- header += line - header;
- }
- break;
-
- case ':':
- if (!colon) {
- colon = line - 1;
- }
- break;
- }
- }
- return SUCCESS;
-}
-/* }}} */
-
-/* {{{ void http_get_request_headers_ex(HashTable *, zend_bool) */
-PHP_HTTP_API void _http_get_request_headers_ex(HashTable *headers, zend_bool prettify TSRMLS_DC)
-{
- char *key = NULL;
- long idx = 0;
- zval array;
-
- Z_ARRVAL(array) = headers;
-
- FOREACH_HASH_KEY(HTTP_SERVER_VARS, key, idx) {
- if (key && !strncmp(key, "HTTP_", 5)) {
- zval **header;
-
- if (prettify) {
- key = pretty_key(key + 5, strlen(key) - 5, 1, 1);
- }
-
- zend_hash_get_current_data(HTTP_SERVER_VARS, (void **) &header);
- add_assoc_stringl(&array, key, Z_STRVAL_PP(header), Z_STRLEN_PP(header), 1);
- key = NULL;
- }
- }
-}
-/* }}} */
-
-/* {{{ STATUS http_urlencode_hash_ex(HashTable *, int, char **, size_t *) */
-PHP_HTTP_API STATUS _http_urlencode_hash_ex(HashTable *hash, int override_argsep,
- char *pre_encoded_data, size_t pre_encoded_len,
- char **encoded_data, size_t *encoded_len TSRMLS_DC)
-{
- smart_str qstr = {0};
-
- if (override_argsep) {
- HTTP_URL_ARGSEP_OVERRIDE;
- }
-
- if (pre_encoded_len && pre_encoded_data) {
- smart_str_appendl(&qstr, pre_encoded_data, pre_encoded_len);
- }
-
- if (SUCCESS != php_url_encode_hash_ex(hash, &qstr, NULL, 0, NULL, 0, NULL, 0, NULL TSRMLS_CC)) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "Couldn't encode query data");
- if (qstr.c) {
- efree(qstr.c);
- }
- if (override_argsep) {
- HTTP_URL_ARGSEP_RESTORE;
- }
- return FAILURE;
- }
-
- if (override_argsep) {
- HTTP_URL_ARGSEP_RESTORE;
- }
-
- smart_str_0(&qstr);
-
- *encoded_data = qstr.c;
- if (encoded_len) {
- *encoded_len = qstr.len;
- }
-
- return SUCCESS;
-}
-/* }}} */
-
-/* {{{ STATUS http_auth_header(char *, char*) */
-PHP_HTTP_API STATUS _http_auth_header(const char *type, const char *realm TSRMLS_DC)
-{
- char realm_header[1024] = {0};
- snprintf(realm_header, 1023, "WWW-Authenticate: %s realm=\"%s\"", type, realm);
- return http_send_status_header(401, realm_header);
-}
-/* }}} */
-
-/* {{{ STATUS http_auth_credentials(char **, char **) */
-PHP_HTTP_API STATUS _http_auth_credentials(char **user, char **pass TSRMLS_DC)
-{
- if (strncmp(sapi_module.name, "isapi", 5)) {
- zval *zuser, *zpass;
-
- HTTP_GSC(zuser, "PHP_AUTH_USER", FAILURE);
- HTTP_GSC(zpass, "PHP_AUTH_PW", FAILURE);
-
- *user = estrndup(Z_STRVAL_P(zuser), Z_STRLEN_P(zuser));
- *pass = estrndup(Z_STRVAL_P(zpass), Z_STRLEN_P(zpass));
-
- return SUCCESS;
- } else {
- zval *zauth = NULL;
- HTTP_GSC(zauth, "HTTP_AUTHORIZATION", FAILURE);
- {
- char *decoded, *colon;
- int decoded_len;
- decoded = php_base64_decode(Z_STRVAL_P(zauth), Z_STRLEN_P(zauth),
- &decoded_len);
-
- if (colon = strchr(decoded + 6, ':')) {
- *user = estrndup(decoded + 6, colon - decoded - 6);
- *pass = estrndup(colon + 1, decoded + decoded_len - colon - 6 - 1);
-
- return SUCCESS;
- } else {
- return FAILURE;
- }
- }
- }
-}
-/* }}} */
-
-#ifndef ZEND_ENGINE_2
-/* {{{ php_url_encode_hash
- Author: Sara Golemon <pollita@php.net> */
-PHP_HTTP_API STATUS php_url_encode_hash_ex(HashTable *ht, smart_str *formstr,
- const char *num_prefix, int num_prefix_len,
- const char *key_prefix, int key_prefix_len,
- const char *key_suffix, int key_suffix_len,
- zval *type TSRMLS_DC)
-{
- char *arg_sep = NULL, *key = NULL, *ekey, *newprefix, *p;
- int arg_sep_len, key_len, ekey_len, key_type, newprefix_len;
- ulong idx;
- zval **zdata = NULL, *copyzval;
-
- if (!ht) {
- return FAILURE;
- }
-
- if (ht->nApplyCount > 0) {
- /* Prevent recursion */
- return SUCCESS;
- }
-
- arg_sep = INI_STR("arg_separator.output");
- if (!arg_sep || !strlen(arg_sep)) {
- arg_sep = HTTP_URL_ARGSEP_DEFAULT;
- }
- arg_sep_len = strlen(arg_sep);
-
- for (zend_hash_internal_pointer_reset(ht);
- (key_type = zend_hash_get_current_key_ex(ht, &key, &key_len, &idx, 0, NULL)) != HASH_KEY_NON_EXISTANT;
- zend_hash_move_forward(ht)
- ) {
- if (key_type == HASH_KEY_IS_STRING && key_len && key[key_len-1] == '\0') {
- /* We don't want that trailing NULL */
- key_len -= 1;
- }
-
-#ifdef ZEND_ENGINE_2
- /* handling for private & protected object properties */
- if (key && *key == '\0' && type != NULL) {
- char *tmp;
-
- zend_object *zobj = zend_objects_get_address(type TSRMLS_CC);
- if (zend_check_property_access(zobj, key TSRMLS_CC) != SUCCESS) {
- /* private or protected property access outside of the class */
- continue;
- }
- zend_unmangle_property_name(key, &tmp, &key);
- key_len = strlen(key);
- }
-#endif
-
- if (zend_hash_get_current_data_ex(ht, (void **)&zdata, NULL) == FAILURE || !zdata || !(*zdata)) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error traversing form data array.");
- return FAILURE;
- }
- if (Z_TYPE_PP(zdata) == IS_ARRAY || Z_TYPE_PP(zdata) == IS_OBJECT) {
- if (key_type == HASH_KEY_IS_STRING) {
- ekey = php_url_encode(key, key_len, &ekey_len);
- newprefix_len = key_suffix_len + ekey_len + key_prefix_len + 1;
- newprefix = emalloc(newprefix_len + 1);
- p = newprefix;
-
- if (key_prefix) {
- memcpy(p, key_prefix, key_prefix_len);
- p += key_prefix_len;
- }
-
- memcpy(p, ekey, ekey_len);
- p += ekey_len;
- efree(ekey);
-
- if (key_suffix) {
- memcpy(p, key_suffix, key_suffix_len);
- p += key_suffix_len;
- }
-
- *(p++) = '[';
- *p = '\0';
- } else {
- /* Is an integer key */
- ekey_len = spprintf(&ekey, 12, "%ld", idx);
- newprefix_len = key_prefix_len + num_prefix_len + ekey_len + key_suffix_len + 1;
- newprefix = emalloc(newprefix_len + 1);
- p = newprefix;
-
- if (key_prefix) {
- memcpy(p, key_prefix, key_prefix_len);
- p += key_prefix_len;
- }
-
- memcpy(p, num_prefix, num_prefix_len);
- p += num_prefix_len;
-
- memcpy(p, ekey, ekey_len);
- p += ekey_len;
- efree(ekey);
-
- if (key_suffix) {
- memcpy(p, key_suffix, key_suffix_len);
- p += key_suffix_len;
- }
- *(p++) = '[';
- *p = '\0';
- }
- ht->nApplyCount++;
- php_url_encode_hash_ex(HASH_OF(*zdata), formstr, NULL, 0, newprefix, newprefix_len, "]", 1, (Z_TYPE_PP(zdata) == IS_OBJECT ? *zdata : NULL) TSRMLS_CC);
- ht->nApplyCount--;
- efree(newprefix);
- } else if (Z_TYPE_PP(zdata) == IS_NULL || Z_TYPE_PP(zdata) == IS_RESOURCE) {
- /* Skip these types */
- continue;
- } else {
- if (formstr->len) {
- smart_str_appendl(formstr, arg_sep, arg_sep_len);
- }
- /* Simple key=value */
- smart_str_appendl(formstr, key_prefix, key_prefix_len);
- if (key_type == HASH_KEY_IS_STRING) {
- ekey = php_url_encode(key, key_len, &ekey_len);
- smart_str_appendl(formstr, ekey, ekey_len);
- efree(ekey);
- } else {
- /* Numeric key */
- if (num_prefix) {
- smart_str_appendl(formstr, num_prefix, num_prefix_len);
- }
- ekey_len = spprintf(&ekey, 12, "%ld", idx);
- smart_str_appendl(formstr, ekey, ekey_len);
- efree(ekey);
- }
- smart_str_appendl(formstr, key_suffix, key_suffix_len);
- smart_str_appendl(formstr, "=", 1);
- switch (Z_TYPE_PP(zdata)) {
- case IS_STRING:
- ekey = php_url_encode(Z_STRVAL_PP(zdata), Z_STRLEN_PP(zdata), &ekey_len);
- break;
- case IS_LONG:
- case IS_BOOL:
- ekey_len = spprintf(&ekey, 12, "%ld", Z_LVAL_PP(zdata));
- break;
- case IS_DOUBLE:
- ekey_len = spprintf(&ekey, 48, "%.*G", (int) EG(precision), Z_DVAL_PP(zdata));
- break;
- default:
- /* fall back on convert to string */
- MAKE_STD_ZVAL(copyzval);
- *copyzval = **zdata;
- zval_copy_ctor(copyzval);
- convert_to_string_ex(©zval);
- ekey = php_url_encode(Z_STRVAL_P(copyzval), Z_STRLEN_P(copyzval), &ekey_len);
- zval_ptr_dtor(©zval);
- }
- smart_str_appendl(formstr, ekey, ekey_len);
- efree(ekey);
- }
- }
-
- return SUCCESS;
-}
-/* }}} */
-#endif /* !ZEND_ENDGINE_2 */
-
-/* }}} public API */
-