Coverage Report

Created: 2025-12-31 07:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/ext/standard/http.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Copyright (c) The PHP Group                                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to version 3.01 of the PHP license,      |
6
   | that is bundled with this package in the file LICENSE, and is        |
7
   | available through the world-wide-web at the following url:           |
8
   | https://www.php.net/license/3_01.txt                                 |
9
   | If you did not receive a copy of the PHP license and are unable to   |
10
   | obtain it through the world-wide-web, please send a note to          |
11
   | license@php.net so we can mail you a copy immediately.               |
12
   +----------------------------------------------------------------------+
13
   | Authors: Sara Golemon <pollita@php.net>                              |
14
   +----------------------------------------------------------------------+
15
*/
16
17
#include "php_http.h"
18
#include "php_ini.h"
19
#include "url.h"
20
#include "SAPI.h"
21
#include "zend_exceptions.h"
22
#include "basic_functions.h"
23
#include "zend_enum.h"
24
25
static void php_url_encode_scalar(zval *scalar, smart_str *form_str,
26
  int encoding_type, zend_ulong index_int,
27
  const char *index_string, size_t index_string_len,
28
  const char *num_prefix, size_t num_prefix_len,
29
  const zend_string *key_prefix,
30
  const zend_string *arg_sep)
31
0
{
32
0
  if (form_str->s) {
33
0
    smart_str_append(form_str, arg_sep);
34
0
  }
35
  /* Simple key=value */
36
0
  if (key_prefix) {
37
0
    smart_str_append(form_str, key_prefix);
38
0
  }
39
0
  if (index_string) {
40
0
    php_url_encode_to_smart_str(form_str, index_string, index_string_len, encoding_type == PHP_QUERY_RFC3986);
41
0
  } else {
42
    /* Numeric key */
43
0
    if (num_prefix) {
44
0
      smart_str_appendl(form_str, num_prefix, num_prefix_len);
45
0
    }
46
0
    smart_str_append_long(form_str, index_int);
47
0
  }
48
0
  if (key_prefix) {
49
0
    smart_str_appendl(form_str, "%5D", strlen("%5D"));
50
0
  }
51
0
  smart_str_appendc(form_str, '=');
52
53
0
try_again:
54
0
  switch (Z_TYPE_P(scalar)) {
55
0
    case IS_STRING:
56
0
      php_url_encode_to_smart_str(form_str, Z_STRVAL_P(scalar), Z_STRLEN_P(scalar), encoding_type == PHP_QUERY_RFC3986);
57
0
      break;
58
0
    case IS_LONG:
59
0
      smart_str_append_long(form_str, Z_LVAL_P(scalar));
60
0
      break;
61
0
    case IS_DOUBLE: {
62
0
      zend_string *tmp = zend_double_to_str(Z_DVAL_P(scalar));
63
0
      php_url_encode_to_smart_str(form_str, ZSTR_VAL(tmp), ZSTR_LEN(tmp), encoding_type == PHP_QUERY_RFC3986);
64
0
      zend_string_free(tmp);
65
0
      break;
66
0
    }
67
0
    case IS_FALSE:
68
0
      smart_str_appendc(form_str, '0');
69
0
      break;
70
0
    case IS_TRUE:
71
0
      smart_str_appendc(form_str, '1');
72
0
      break;
73
0
    case IS_OBJECT:
74
0
      ZEND_ASSERT(Z_OBJCE_P(scalar)->ce_flags & ZEND_ACC_ENUM);
75
0
      if (Z_OBJCE_P(scalar)->enum_backing_type == IS_UNDEF) {
76
0
        zend_value_error("Unbacked enum %s cannot be converted to a string", ZSTR_VAL(Z_OBJCE_P(scalar)->name));
77
0
        return;
78
0
      }
79
0
      scalar = zend_enum_fetch_case_value(Z_OBJ_P(scalar));
80
0
      goto try_again;
81
    /* All possible types are either handled here or previously */
82
0
    EMPTY_SWITCH_DEFAULT_CASE();
83
0
  }
84
0
}
85
86
static zend_always_inline bool php_url_check_stack_limit(void)
87
0
{
88
0
#ifdef ZEND_CHECK_STACK_LIMIT
89
0
  return zend_call_stack_overflowed(EG(stack_limit));
90
#else
91
  return false;
92
#endif
93
0
}
94
95
/* {{{ php_url_encode_hash */
96
PHPAPI void php_url_encode_hash_ex(HashTable *ht, smart_str *formstr,
97
        const char *num_prefix, size_t num_prefix_len,
98
        const zend_string *key_prefix,
99
        zval *type, const zend_string *arg_sep, int enc_type)
100
0
{
101
0
  zend_string *key = NULL;
102
0
  const char *prop_name;
103
0
  size_t prop_len;
104
0
  zend_ulong idx;
105
0
  zval *zdata = NULL;
106
0
  ZEND_ASSERT(ht);
107
108
0
  if (GC_IS_RECURSIVE(ht)) {
109
    /* Prevent recursion */
110
0
    return;
111
0
  }
112
113
  /* Very deeply structured data could trigger a stack overflow, even without recursion. */
114
0
  if (UNEXPECTED(php_url_check_stack_limit())) {
115
0
    zend_throw_error(NULL, "Maximum call stack size reached.");
116
0
    return;
117
0
  }
118
119
0
  if (!arg_sep) {
120
0
    arg_sep = PG(arg_separator).output;
121
0
    if (ZSTR_LEN(arg_sep) == 0) {
122
0
      arg_sep = ZSTR_CHAR('&');
123
0
    }
124
0
  }
125
126
0
  ZEND_HASH_FOREACH_KEY_VAL(ht, idx, key, zdata) {
127
0
    bool is_dynamic = true;
128
0
    if (Z_TYPE_P(zdata) == IS_INDIRECT) {
129
0
      zdata = Z_INDIRECT_P(zdata);
130
0
      if (Z_ISUNDEF_P(zdata)) {
131
0
        continue;
132
0
      }
133
134
0
      is_dynamic = false;
135
0
    }
136
137
    /* handling for private & protected object properties */
138
0
    if (key) {
139
0
      prop_name = ZSTR_VAL(key);
140
0
      prop_len = ZSTR_LEN(key);
141
142
0
      if (type != NULL && zend_check_property_access(Z_OBJ_P(type), key, is_dynamic) != SUCCESS) {
143
        /* property not visible in this scope */
144
0
        continue;
145
0
      }
146
147
0
      if (ZSTR_VAL(key)[0] == '\0' && type != NULL) {
148
0
        const char *tmp;
149
0
        zend_unmangle_property_name_ex(key, &tmp, &prop_name, &prop_len);
150
0
      } else {
151
0
        prop_name = ZSTR_VAL(key);
152
0
        prop_len = ZSTR_LEN(key);
153
0
      }
154
0
    } else {
155
0
      prop_name = NULL;
156
0
      prop_len = 0;
157
0
    }
158
159
0
    ZVAL_DEREF(zdata);
160
0
    if (Z_TYPE_P(zdata) == IS_ARRAY
161
0
     || (Z_TYPE_P(zdata) == IS_OBJECT
162
0
      && !(Z_OBJCE_P(zdata)->ce_flags & ZEND_ACC_ENUM))) {
163
0
      zend_string *new_prefix;
164
0
      if (key) {
165
0
        zend_string *encoded_key;
166
0
        if (enc_type == PHP_QUERY_RFC3986) {
167
0
          encoded_key = php_raw_url_encode(prop_name, prop_len);
168
0
        } else {
169
0
          encoded_key = php_url_encode(prop_name, prop_len);
170
0
        }
171
172
0
        if (key_prefix) {
173
0
          new_prefix = zend_string_concat3(ZSTR_VAL(key_prefix), ZSTR_LEN(key_prefix), ZSTR_VAL(encoded_key), ZSTR_LEN(encoded_key), "%5D%5B", strlen("%5D%5B"));
174
0
        } else {
175
0
          new_prefix = zend_string_concat2(ZSTR_VAL(encoded_key), ZSTR_LEN(encoded_key), "%5B", strlen("%5B"));
176
0
        }
177
0
        zend_string_efree(encoded_key);
178
0
      } else { /* is integer index */
179
0
        char *index_int_as_str;
180
0
        size_t index_int_as_str_len;
181
182
0
        index_int_as_str_len = spprintf(&index_int_as_str, 0, ZEND_LONG_FMT, idx);
183
184
0
        if (key_prefix && num_prefix) {
185
          /* zend_string_concat4() */
186
0
          size_t len = ZSTR_LEN(key_prefix) + num_prefix_len + index_int_as_str_len + strlen("%5D%5B");
187
0
          new_prefix = zend_string_alloc(len, 0);
188
189
0
          memcpy(ZSTR_VAL(new_prefix), ZSTR_VAL(key_prefix), ZSTR_LEN(key_prefix));
190
0
          memcpy(ZSTR_VAL(new_prefix) + ZSTR_LEN(key_prefix), num_prefix, num_prefix_len);
191
0
          memcpy(ZSTR_VAL(new_prefix) + ZSTR_LEN(key_prefix) + num_prefix_len, index_int_as_str, index_int_as_str_len);
192
0
          memcpy(ZSTR_VAL(new_prefix) + ZSTR_LEN(key_prefix) + num_prefix_len +index_int_as_str_len, "%5D%5B", strlen("%5D%5B"));
193
0
          ZSTR_VAL(new_prefix)[len] = '\0';
194
0
        } else if (key_prefix) {
195
0
          new_prefix = zend_string_concat3(ZSTR_VAL(key_prefix), ZSTR_LEN(key_prefix), index_int_as_str, index_int_as_str_len, "%5D%5B", strlen("%5D%5B"));
196
0
        } else if (num_prefix) {
197
0
          new_prefix = zend_string_concat3(num_prefix, num_prefix_len, index_int_as_str, index_int_as_str_len, "%5B", strlen("%5B"));
198
0
        } else {
199
0
          new_prefix = zend_string_concat2(index_int_as_str, index_int_as_str_len, "%5B", strlen("%5B"));
200
0
        }
201
0
        efree(index_int_as_str);
202
0
      }
203
0
      GC_TRY_PROTECT_RECURSION(ht);
204
0
      php_url_encode_hash_ex(HASH_OF(zdata), formstr, NULL, 0, new_prefix, (Z_TYPE_P(zdata) == IS_OBJECT ? zdata : NULL), arg_sep, enc_type);
205
0
      GC_TRY_UNPROTECT_RECURSION(ht);
206
0
      zend_string_efree(new_prefix);
207
0
    } else if (Z_TYPE_P(zdata) == IS_NULL || Z_TYPE_P(zdata) == IS_RESOURCE) {
208
      /* Skip these types */
209
0
      continue;
210
0
    } else {
211
0
      php_url_encode_scalar(zdata, formstr,
212
0
        enc_type, idx,
213
0
        prop_name, prop_len,
214
0
        num_prefix, num_prefix_len,
215
0
        key_prefix,
216
0
        arg_sep);
217
0
    }
218
0
  } ZEND_HASH_FOREACH_END();
219
0
}
220
/* }}} */
221
222
  /* If there is a prefix we need to close the key with an encoded ] ("%5D") */
223
/* {{{ Generates a form-encoded query string from an associative array or object. */
224
PHP_FUNCTION(http_build_query)
225
0
{
226
0
  zval *formdata;
227
0
  char *prefix = NULL;
228
0
  size_t prefix_len = 0;
229
0
  zend_string *arg_sep = NULL;
230
0
  smart_str formstr = {0};
231
0
  zend_long enc_type = PHP_QUERY_RFC1738;
232
233
0
  ZEND_PARSE_PARAMETERS_START(1, 4)
234
0
    Z_PARAM_ARRAY_OR_OBJECT(formdata)
235
0
    Z_PARAM_OPTIONAL
236
0
    Z_PARAM_STRING(prefix, prefix_len)
237
0
    Z_PARAM_STR_OR_NULL(arg_sep)
238
0
    Z_PARAM_LONG(enc_type)
239
0
  ZEND_PARSE_PARAMETERS_END();
240
241
0
  if (UNEXPECTED(Z_TYPE_P(formdata) == IS_OBJECT && (Z_OBJCE_P(formdata)->ce_flags & ZEND_ACC_ENUM))) {
242
0
    zend_argument_type_error(1, "must not be an enum, %s given", zend_zval_value_name(formdata));
243
0
    RETURN_THROWS();
244
0
  }
245
246
0
  php_url_encode_hash_ex(HASH_OF(formdata), &formstr, prefix, prefix_len, /* key_prefix */ NULL, (Z_TYPE_P(formdata) == IS_OBJECT ? formdata : NULL), arg_sep, (int)enc_type);
247
248
0
  RETURN_STR(smart_str_extract(&formstr));
249
0
}
250
/* }}} */
251
252
static zend_result cache_request_parse_body_option(HashTable *options, zval *option, int cache_offset)
253
0
{
254
0
  if (option) {
255
0
    zend_long result;
256
0
    ZVAL_DEREF(option);
257
0
    if (Z_TYPE_P(option) == IS_STRING) {
258
0
      zend_string *errstr;
259
0
      result = zend_ini_parse_quantity(Z_STR_P(option), &errstr);
260
0
      if (errstr) {
261
0
        zend_error(E_WARNING, "%s", ZSTR_VAL(errstr));
262
0
        zend_string_release(errstr);
263
0
      }
264
0
    } else if (Z_TYPE_P(option) == IS_LONG) {
265
0
      result = Z_LVAL_P(option);
266
0
    } else {
267
0
      zend_value_error("Invalid %s value in $options argument", zend_zval_value_name(option));
268
0
      return FAILURE;
269
0
    }
270
0
    SG(request_parse_body_context).options_cache[cache_offset].set = true;
271
0
    SG(request_parse_body_context).options_cache[cache_offset].value = result;
272
0
  } else {
273
0
    SG(request_parse_body_context).options_cache[cache_offset].set = false;
274
0
  }
275
276
0
  return SUCCESS;
277
0
}
278
279
static zend_result cache_request_parse_body_options(HashTable *options)
280
0
{
281
0
  zend_string *key;
282
0
  zval *value;
283
0
  ZEND_HASH_FOREACH_STR_KEY_VAL(options, key, value) {
284
0
    if (!key) {
285
0
      zend_value_error("Invalid integer key in $options argument");
286
0
      return FAILURE;
287
0
    }
288
0
    if (ZSTR_LEN(key) == 0) {
289
0
      zend_value_error("Invalid empty string key in $options argument");
290
0
      return FAILURE;
291
0
    }
292
293
0
#define CHECK_OPTION(name) \
294
0
  if (zend_string_equals_literal_ci(key, #name)) { \
295
0
    if (cache_request_parse_body_option(options, value, REQUEST_PARSE_BODY_OPTION_ ## name) == FAILURE) { \
296
0
      return FAILURE; \
297
0
    } \
298
0
    continue; \
299
0
  }
300
301
0
    switch (ZSTR_VAL(key)[0]) {
302
0
      case 'm':
303
0
      case 'M':
304
0
        CHECK_OPTION(max_file_uploads);
305
0
        CHECK_OPTION(max_input_vars);
306
0
        CHECK_OPTION(max_multipart_body_parts);
307
0
        break;
308
0
      case 'p':
309
0
      case 'P':
310
0
        CHECK_OPTION(post_max_size);
311
0
        break;
312
0
      case 'u':
313
0
      case 'U':
314
0
        CHECK_OPTION(upload_max_filesize);
315
0
        break;
316
0
    }
317
318
0
    zend_value_error("Invalid key \"%s\" in $options argument", ZSTR_VAL(key));
319
0
    return FAILURE;
320
0
  } ZEND_HASH_FOREACH_END();
321
322
0
#undef CACHE_OPTION
323
324
0
  return SUCCESS;
325
0
}
326
327
PHP_FUNCTION(request_parse_body)
328
0
{
329
0
  HashTable *options = NULL;
330
331
0
  ZEND_PARSE_PARAMETERS_START(0, 1)
332
0
    Z_PARAM_OPTIONAL
333
0
    Z_PARAM_ARRAY_HT_OR_NULL(options)
334
0
  ZEND_PARSE_PARAMETERS_END();
335
336
0
  SG(request_parse_body_context).throw_exceptions = true;
337
0
  if (options) {
338
0
    if (cache_request_parse_body_options(options) == FAILURE) {
339
0
      goto exit;
340
0
    }
341
0
  }
342
343
0
  if (!SG(request_info).content_type) {
344
0
    zend_throw_error(zend_ce_request_parse_body_exception, "Request does not provide a content type");
345
0
    goto exit;
346
0
  }
347
348
0
  sapi_read_post_data();
349
0
  if (!SG(request_info).post_entry) {
350
0
    zend_throw_error(zend_ce_request_parse_body_exception, "Content-Type \"%s\" is not supported", SG(request_info).content_type);
351
0
    goto exit;
352
0
  }
353
354
0
  zval post, files, old_post, old_files;
355
0
  zval *global_post = &PG(http_globals)[TRACK_VARS_POST];
356
0
  zval *global_files = &PG(http_globals)[TRACK_VARS_FILES];
357
358
0
  ZVAL_COPY_VALUE(&old_post, global_post);
359
0
  ZVAL_COPY_VALUE(&old_files, global_files);
360
0
  array_init(global_post);
361
0
  array_init(global_files);
362
0
  sapi_handle_post(global_post);
363
0
  ZVAL_COPY_VALUE(&post, global_post);
364
0
  ZVAL_COPY_VALUE(&files, global_files);
365
0
  ZVAL_COPY_VALUE(global_post, &old_post);
366
0
  ZVAL_COPY_VALUE(global_files, &old_files);
367
368
0
  RETVAL_ARR(zend_new_pair(&post, &files));
369
370
0
exit:
371
0
  SG(request_parse_body_context).throw_exceptions = false;
372
0
  memset(&SG(request_parse_body_context).options_cache, 0, sizeof(SG(request_parse_body_context).options_cache));
373
0
}
374
375
PHP_FUNCTION(http_get_last_response_headers)
376
0
{
377
0
  if (zend_parse_parameters_none() == FAILURE) {
378
0
    RETURN_THROWS();
379
0
  }
380
381
0
  if (!Z_ISUNDEF(BG(last_http_headers))) {
382
0
    RETURN_COPY(&BG(last_http_headers));
383
0
  } else {
384
0
    RETURN_NULL();
385
0
  }
386
0
}
387
388
PHP_FUNCTION(http_clear_last_response_headers)
389
0
{
390
0
  if (zend_parse_parameters_none() == FAILURE) {
391
0
    RETURN_THROWS();
392
0
  }
393
394
0
  zval_ptr_dtor(&BG(last_http_headers));
395
0
  ZVAL_UNDEF(&BG(last_http_headers));
396
0
}