Coverage Report

Created: 2025-09-27 06:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/ext/uri/uri_parser_rfc3986.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: Máté Kocsis <kocsismate@php.net>                            |
14
   +----------------------------------------------------------------------+
15
*/
16
17
#include "php.h"
18
#include "uri_parser_rfc3986.h"
19
#include "php_uri_common.h"
20
#include "Zend/zend_smart_str.h"
21
#include "Zend/zend_exceptions.h"
22
23
static void *php_uri_parser_rfc3986_memory_manager_malloc(UriMemoryManager *memory_manager, size_t size)
24
0
{
25
0
  return emalloc(size);
26
0
}
27
28
static void *php_uri_parser_rfc3986_memory_manager_calloc(UriMemoryManager *memory_manager, size_t nmemb, size_t size)
29
0
{
30
0
  return ecalloc(nmemb, size);
31
0
}
32
33
static void *php_uri_parser_rfc3986_memory_manager_realloc(UriMemoryManager *memory_manager, void *ptr, size_t size)
34
0
{
35
0
  return erealloc(ptr, size);
36
0
}
37
38
static void *php_uri_parser_rfc3986_memory_manager_reallocarray(UriMemoryManager *memory_manager, void *ptr, size_t nmemb, size_t size)
39
0
{
40
0
  return safe_erealloc(ptr, nmemb, size, 0);
41
0
}
42
43
static void php_uri_parser_rfc3986_memory_manager_destroy(UriMemoryManager *memory_manager, void *ptr)
44
0
{
45
0
  efree(ptr);
46
0
}
47
48
static const UriMemoryManager php_uri_parser_rfc3986_memory_manager = {
49
  .malloc = php_uri_parser_rfc3986_memory_manager_malloc,
50
  .calloc = php_uri_parser_rfc3986_memory_manager_calloc,
51
  .realloc = php_uri_parser_rfc3986_memory_manager_realloc,
52
  .reallocarray = php_uri_parser_rfc3986_memory_manager_reallocarray,
53
  .free = php_uri_parser_rfc3986_memory_manager_destroy,
54
  .userData = NULL,
55
};
56
57
/* The library expects a pointer to a non-const UriMemoryManager, but does
58
 * not actually modify it (and neither does our implementation). Use a
59
 * const struct with a non-const pointer for convenience. */
60
static UriMemoryManager* const mm = (UriMemoryManager*)&php_uri_parser_rfc3986_memory_manager;
61
62
static inline size_t get_text_range_length(const UriTextRangeA *range)
63
0
{
64
0
  return range->afterLast - range->first;
65
0
}
66
67
static inline bool has_text_range(const UriTextRangeA *range)
68
0
{
69
0
  return range->first != NULL && range->afterLast != NULL;
70
0
}
71
72
ZEND_ATTRIBUTE_NONNULL static void copy_uri(UriUriA *new_uriparser_uri, const UriUriA *uriparser_uri)
73
0
{
74
0
  int result = uriCopyUriMmA(new_uriparser_uri, uriparser_uri, mm);
75
0
  ZEND_ASSERT(result == URI_SUCCESS);
76
0
}
77
78
0
ZEND_ATTRIBUTE_NONNULL static UriUriA *get_normalized_uri(php_uri_parser_rfc3986_uris *uriparser_uris) {
79
0
  if (!uriparser_uris->normalized_uri_initialized) {
80
0
    copy_uri(&uriparser_uris->normalized_uri, &uriparser_uris->uri);
81
0
    int result = uriNormalizeSyntaxExMmA(&uriparser_uris->normalized_uri, (unsigned int)-1, mm);
82
0
    ZEND_ASSERT(result == URI_SUCCESS);
83
0
    uriparser_uris->normalized_uri_initialized = true;
84
0
  }
85
86
0
  return &uriparser_uris->normalized_uri;
87
0
}
88
89
ZEND_ATTRIBUTE_NONNULL static UriUriA *get_uri_for_reading(php_uri_parser_rfc3986_uris *uriparser_uris, php_uri_component_read_mode read_mode)
90
0
{
91
0
  switch (read_mode) {
92
0
    case PHP_URI_COMPONENT_READ_MODE_RAW:
93
0
      return &uriparser_uris->uri;
94
0
    case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII:
95
0
      ZEND_FALLTHROUGH;
96
0
    case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_UNICODE:
97
0
      return get_normalized_uri(uriparser_uris);
98
0
    EMPTY_SWITCH_DEFAULT_CASE()
99
0
  }
100
0
}
101
102
ZEND_ATTRIBUTE_NONNULL static UriUriA *get_uri_for_writing(php_uri_parser_rfc3986_uris *uriparser_uris)
103
0
{
104
0
  return &uriparser_uris->uri;
105
0
}
106
107
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_scheme_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
108
0
{
109
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
110
111
0
  if (has_text_range(&uriparser_uri->scheme)) {
112
0
    ZVAL_STRINGL(retval, uriparser_uri->scheme.first, get_text_range_length(&uriparser_uri->scheme));
113
0
  } else {
114
0
    ZVAL_NULL(retval);
115
0
  }
116
117
0
  return SUCCESS;
118
0
}
119
120
static zend_result php_uri_parser_rfc3986_scheme_write(void *uri, zval *value, zval *errors)
121
0
{
122
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
123
0
  int result;
124
125
0
  if (Z_TYPE_P(value) == IS_NULL) {
126
0
    result = uriSetSchemeMmA(uriparser_uri, NULL, NULL, mm);
127
0
  } else {
128
0
    result = uriSetSchemeMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
129
0
  }
130
131
0
  switch (result) {
132
0
    case URI_SUCCESS:
133
0
      return SUCCESS;
134
0
    case URI_ERROR_SYNTAX:
135
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified scheme is malformed", 0);
136
0
      return FAILURE;
137
0
    default:
138
      /* This should be unreachable in practice. */
139
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the scheme", 0);
140
0
      return FAILURE;
141
0
  }
142
0
}
143
144
ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_userinfo_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
145
0
{
146
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
147
148
0
  if (has_text_range(&uriparser_uri->userInfo)) {
149
0
    ZVAL_STRINGL(retval, uriparser_uri->userInfo.first, get_text_range_length(&uriparser_uri->userInfo));
150
0
  } else {
151
0
    ZVAL_NULL(retval);
152
0
  }
153
154
0
  return SUCCESS;
155
0
}
156
157
zend_result php_uri_parser_rfc3986_userinfo_write(void *uri, zval *value, zval *errors)
158
0
{
159
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
160
0
  int result;
161
162
0
  if (Z_TYPE_P(value) == IS_NULL) {
163
0
    result = uriSetUserInfoMmA(uriparser_uri, NULL, NULL, mm);
164
0
  } else {
165
0
    result = uriSetUserInfoMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
166
0
  }
167
168
0
  switch (result) {
169
0
    case URI_SUCCESS:
170
0
      return SUCCESS;
171
0
    case URI_ERROR_SETUSERINFO_HOST_NOT_SET:
172
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot set a userinfo without having a host", 0);
173
0
      return FAILURE;
174
0
    case URI_ERROR_SYNTAX:
175
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified userinfo is malformed", 0);
176
0
      return FAILURE;
177
0
    default:
178
      /* This should be unreachable in practice. */
179
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the userinfo", 0);
180
0
      return FAILURE;
181
0
  }
182
0
}
183
184
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_username_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
185
0
{
186
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
187
188
0
  if (has_text_range(&uriparser_uri->userInfo)) {
189
0
    size_t length = get_text_range_length(&uriparser_uri->userInfo);
190
0
    const char *c = memchr(uriparser_uri->userInfo.first, ':', length);
191
192
0
    if (c == NULL && length > 0) {
193
0
      ZVAL_STRINGL(retval, uriparser_uri->userInfo.first, length);
194
0
    } else if (c != NULL && c - uriparser_uri->userInfo.first > 0) {
195
0
      ZVAL_STRINGL(retval, uriparser_uri->userInfo.first, c - uriparser_uri->userInfo.first);
196
0
    } else {
197
0
      ZVAL_NULL(retval);
198
0
    }
199
0
  } else {
200
0
    ZVAL_NULL(retval);
201
0
  }
202
203
0
  return SUCCESS;
204
0
}
205
206
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_password_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
207
0
{
208
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
209
210
0
  if (has_text_range(&uriparser_uri->userInfo)) {
211
0
    const char *c = memchr(uriparser_uri->userInfo.first, ':', get_text_range_length(&uriparser_uri->userInfo));
212
213
0
    if (c != NULL && uriparser_uri->userInfo.afterLast - c - 1 > 0) {
214
0
      ZVAL_STRINGL(retval, c + 1, uriparser_uri->userInfo.afterLast - c - 1);
215
0
    } else {
216
0
      ZVAL_NULL(retval);
217
0
    }
218
0
  } else {
219
0
    ZVAL_NULL(retval);
220
0
  }
221
222
0
  return SUCCESS;
223
0
}
224
225
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_host_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
226
0
{
227
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
228
229
0
  if (has_text_range(&uriparser_uri->hostText)) {
230
0
    if (uriparser_uri->hostData.ip6 != NULL || uriparser_uri->hostData.ipFuture.first != NULL) {
231
      /* the textual representation of the host is always accessible in the .hostText field no matter what the host is */
232
0
      smart_str host_str = {0};
233
234
0
      smart_str_appendc(&host_str, '[');
235
0
      smart_str_appendl(&host_str, uriparser_uri->hostText.first, get_text_range_length(&uriparser_uri->hostText));
236
0
      smart_str_appendc(&host_str, ']');
237
238
0
      ZVAL_NEW_STR(retval, smart_str_extract(&host_str));
239
0
    } else {
240
0
      ZVAL_STRINGL(retval, uriparser_uri->hostText.first, get_text_range_length(&uriparser_uri->hostText));
241
0
    }
242
0
  } else {
243
0
    ZVAL_NULL(retval);
244
0
  }
245
246
0
  return SUCCESS;
247
0
}
248
249
static zend_result php_uri_parser_rfc3986_host_write(void *uri, zval *value, zval *errors)
250
0
{
251
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
252
0
  int result;
253
254
0
  if (Z_TYPE_P(value) == IS_NULL) {
255
0
    result = uriSetHostAutoMmA(uriparser_uri, NULL, NULL, mm);
256
0
  } else {
257
0
    result = uriSetHostAutoMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
258
0
  }
259
260
0
  switch (result) {
261
0
    case URI_SUCCESS:
262
0
      return SUCCESS;
263
0
    case URI_ERROR_SETHOST_PORT_SET:
264
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot remove the host from a URI that has a port", 0);
265
0
      return FAILURE;
266
0
    case URI_ERROR_SETHOST_USERINFO_SET:
267
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot remove the host from a URI that has a userinfo", 0);
268
0
      return FAILURE;
269
0
    case URI_ERROR_SYNTAX:
270
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified host is malformed", 0);
271
0
      return FAILURE;
272
0
    default:
273
      /* This should be unreachable in practice. */
274
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the host", 0);
275
0
      return FAILURE;
276
0
  }
277
0
}
278
279
ZEND_ATTRIBUTE_NONNULL static zend_long port_str_to_zend_long_checked(const char *str, size_t len)
280
0
{
281
0
  if (len > MAX_LENGTH_OF_LONG) {
282
0
    return -1;
283
0
  }
284
285
0
  char buf[MAX_LENGTH_OF_LONG + 1];
286
0
  *(char*)zend_mempcpy(buf, str, len) = 0;
287
288
0
  zend_ulong result = ZEND_STRTOUL(buf, NULL, 10);
289
290
0
  if (result > ZEND_LONG_MAX) {
291
0
    return -1;
292
0
  }
293
294
0
  return (zend_long)result;
295
0
}
296
297
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_port_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
298
0
{
299
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
300
301
0
  if (has_text_range(&uriparser_uri->portText) && get_text_range_length(&uriparser_uri->portText) > 0) {
302
0
    ZVAL_LONG(retval, port_str_to_zend_long_checked(uriparser_uri->portText.first, get_text_range_length(&uriparser_uri->portText)));
303
0
  } else {
304
0
    ZVAL_NULL(retval);
305
0
  }
306
307
0
  return SUCCESS;
308
0
}
309
310
static zend_result php_uri_parser_rfc3986_port_write(void *uri, zval *value, zval *errors)
311
0
{
312
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
313
0
  int result;
314
315
0
  if (Z_TYPE_P(value) == IS_NULL) {
316
0
    result = uriSetPortTextMmA(uriparser_uri, NULL, NULL, mm);
317
0
  } else {
318
0
    zend_string *tmp = zend_long_to_str(Z_LVAL_P(value));
319
0
    result = uriSetPortTextMmA(uriparser_uri, ZSTR_VAL(tmp), ZSTR_VAL(tmp) + ZSTR_LEN(tmp), mm);
320
0
    zend_string_release_ex(tmp, false);
321
0
  }
322
323
0
  switch (result) {
324
0
    case URI_SUCCESS:
325
0
      return SUCCESS;
326
0
    case URI_ERROR_SETPORT_HOST_NOT_SET:
327
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot set a port without having a host", 0);
328
0
      return FAILURE;
329
0
    case URI_ERROR_SYNTAX:
330
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified port is malformed", 0);
331
0
      return FAILURE;
332
0
    default:
333
      /* This should be unreachable in practice. */
334
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the port", 0);
335
0
      return FAILURE;
336
0
  }
337
0
}
338
339
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_path_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
340
0
{
341
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
342
343
0
  if (uriparser_uri->pathHead != NULL) {
344
0
    smart_str str = {0};
345
346
0
    if (uriparser_uri->absolutePath || uriHasHostA(uriparser_uri)) {
347
0
      smart_str_appendc(&str, '/');
348
0
    }
349
350
0
    for (const UriPathSegmentA *p = uriparser_uri->pathHead; p; p = p->next) {
351
0
      smart_str_appendl(&str, p->text.first, get_text_range_length(&p->text));
352
0
      if (p->next) {
353
0
        smart_str_appendc(&str, '/');
354
0
      }
355
0
    }
356
357
0
    ZVAL_NEW_STR(retval, smart_str_extract(&str));
358
0
  } else if (uriparser_uri->absolutePath) {
359
0
    ZVAL_CHAR(retval, '/');
360
0
  } else {
361
0
    ZVAL_EMPTY_STRING(retval);
362
0
  }
363
364
0
  return SUCCESS;
365
0
}
366
367
static zend_result php_uri_parser_rfc3986_path_write(void *uri, zval *value, zval *errors)
368
0
{
369
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
370
0
  int result;
371
372
0
  if (Z_STRLEN_P(value) == 0) {
373
0
    result = uriSetPathMmA(uriparser_uri, NULL, NULL, mm);
374
0
  } else {
375
0
    result = uriSetPathMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
376
0
  }
377
378
0
  switch (result) {
379
0
    case URI_SUCCESS:
380
0
      return SUCCESS;
381
0
    case URI_ERROR_SYNTAX:
382
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified path is malformed", 0);
383
0
      return FAILURE;
384
0
    default:
385
      /* This should be unreachable in practice. */
386
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the path", 0);
387
0
      return FAILURE;
388
0
  }
389
0
}
390
391
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_query_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
392
0
{
393
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
394
395
0
  if (has_text_range(&uriparser_uri->query)) {
396
0
    ZVAL_STRINGL(retval, uriparser_uri->query.first, get_text_range_length(&uriparser_uri->query));
397
0
  } else {
398
0
    ZVAL_NULL(retval);
399
0
  }
400
401
0
  return SUCCESS;
402
0
}
403
404
static zend_result php_uri_parser_rfc3986_query_write(void *uri, zval *value, zval *errors)
405
0
{
406
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
407
0
  int result;
408
409
0
  if (Z_TYPE_P(value) == IS_NULL) {
410
0
    result = uriSetQueryMmA(uriparser_uri, NULL, NULL, mm);
411
0
  } else {
412
0
    result = uriSetQueryMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
413
0
  }
414
415
0
  switch (result) {
416
0
    case URI_SUCCESS:
417
0
      return SUCCESS;
418
0
    case URI_ERROR_SYNTAX:
419
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified query is malformed", 0);
420
0
      return FAILURE;
421
0
    default:
422
      /* This should be unreachable in practice. */
423
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the query", 0);
424
0
      return FAILURE;
425
0
  }
426
0
}
427
428
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_fragment_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
429
0
{
430
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
431
432
0
  if (has_text_range(&uriparser_uri->fragment)) {
433
0
    ZVAL_STRINGL(retval, uriparser_uri->fragment.first, get_text_range_length(&uriparser_uri->fragment));
434
0
  } else {
435
0
    ZVAL_NULL(retval);
436
0
  }
437
438
0
  return SUCCESS;
439
0
}
440
441
static zend_result php_uri_parser_rfc3986_fragment_write(void *uri, zval *value, zval *errors)
442
0
{
443
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
444
0
  int result;
445
446
0
  if (Z_TYPE_P(value) == IS_NULL) {
447
0
    result = uriSetFragmentMmA(uriparser_uri, NULL, NULL, mm);
448
0
  } else {
449
0
    result = uriSetFragmentMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
450
0
  }
451
452
0
  switch (result) {
453
0
    case URI_SUCCESS:
454
0
      return SUCCESS;
455
0
    case URI_ERROR_SYNTAX:
456
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified fragment is malformed", 0);
457
0
      return FAILURE;
458
0
    default:
459
      /* This should be unreachable in practice. */
460
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the fragment", 0);
461
0
      return FAILURE;
462
0
  }
463
0
}
464
465
static php_uri_parser_rfc3986_uris *uriparser_create_uris(void)
466
0
{
467
0
  php_uri_parser_rfc3986_uris *uriparser_uris = ecalloc(1, sizeof(*uriparser_uris));
468
0
  uriparser_uris->normalized_uri_initialized = false;
469
470
0
  return uriparser_uris;
471
0
}
472
473
php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_parse_ex(const char *uri_str, size_t uri_str_len, const php_uri_parser_rfc3986_uris *uriparser_base_urls, bool silent)
474
0
{
475
0
  UriUriA uri = {0};
476
477
  /* Parse the URI. */
478
0
  int result = uriParseSingleUriExMmA(&uri, uri_str, uri_str + uri_str_len, NULL, mm);
479
0
  if (result != URI_SUCCESS) {
480
0
    if (!silent) {
481
0
      switch (result) {
482
0
        case URI_ERROR_SYNTAX:
483
0
          zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified URI is malformed", 0);
484
0
          break;
485
0
        default:
486
          /* This should be unreachable in practice. */
487
0
          zend_throw_exception(php_uri_ce_error, "Failed to parse the specified URI", 0);
488
0
          break;
489
0
      }
490
0
    }
491
492
0
    goto fail;
493
0
  }
494
495
0
  if (uriparser_base_urls != NULL) {
496
0
    UriUriA tmp = {0};
497
498
    /* Combine the parsed URI with the base URI and store the result in 'tmp',
499
     * since the target and source URLs must be distinct. */
500
0
    int result = uriAddBaseUriExMmA(&tmp, &uri, &uriparser_base_urls->uri, URI_RESOLVE_STRICTLY, mm);
501
0
    if (result != URI_SUCCESS) {
502
0
      if (!silent) {
503
0
        switch (result) {
504
0
          case URI_ERROR_ADDBASE_REL_BASE:
505
0
            zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified base URI must be absolute", 0);
506
0
            break;
507
0
          default:
508
            /* This should be unreachable in practice. */
509
0
            zend_throw_exception(php_uri_ce_error, "Failed to resolve the specified URI against the base URI", 0);
510
0
            break;
511
0
        }
512
0
      }
513
514
0
      goto fail;
515
0
    }
516
517
    /* Store the combined URI back into 'uri'. */
518
0
    uriFreeUriMembersMmA(&uri, mm);
519
0
    uri = tmp;
520
0
  }
521
522
  /* Make the resulting URI independent of the 'uri_str'. */
523
0
  uriMakeOwnerMmA(&uri, mm);
524
525
0
  if (has_text_range(&uri.portText) && get_text_range_length(&uri.portText) > 0) {
526
0
    if (port_str_to_zend_long_checked(uri.portText.first, get_text_range_length(&uri.portText)) == -1) {
527
0
      if (!silent) {
528
0
        zend_throw_exception(php_uri_ce_invalid_uri_exception, "The port is out of range", 0);
529
0
      }
530
531
0
      goto fail;
532
0
    }
533
0
  }
534
535
0
  php_uri_parser_rfc3986_uris *uriparser_uris = uriparser_create_uris();
536
0
  uriparser_uris->uri = uri;
537
538
0
  return uriparser_uris;
539
540
0
 fail:
541
542
0
  uriFreeUriMembersMmA(&uri, mm);
543
544
0
  return NULL;
545
0
}
546
547
void *php_uri_parser_rfc3986_parse(const char *uri_str, size_t uri_str_len, const void *base_url, zval *errors, bool silent)
548
0
{
549
0
  return php_uri_parser_rfc3986_parse_ex(uri_str, uri_str_len, base_url, silent);
550
0
}
551
552
ZEND_ATTRIBUTE_NONNULL static void *php_uri_parser_rfc3986_clone(void *uri)
553
0
{
554
0
  const php_uri_parser_rfc3986_uris *uriparser_uris = uri;
555
556
0
  php_uri_parser_rfc3986_uris *new_uriparser_uris = uriparser_create_uris();
557
0
  copy_uri(&new_uriparser_uris->uri, &uriparser_uris->uri);
558
  /* Do not copy the normalized URI: The expected action after cloning is
559
   * modifying the cloned URI (which will invalidate the cached normalized
560
   * URI). */
561
562
0
  return new_uriparser_uris;
563
0
}
564
565
ZEND_ATTRIBUTE_NONNULL static zend_string *php_uri_parser_rfc3986_to_string(void *uri, php_uri_recomposition_mode recomposition_mode, bool exclude_fragment)
566
0
{
567
0
  php_uri_parser_rfc3986_uris *uriparser_uris = uri;
568
0
  const UriUriA *uriparser_uri;
569
570
0
  if (recomposition_mode == PHP_URI_RECOMPOSITION_MODE_RAW_ASCII || recomposition_mode == PHP_URI_RECOMPOSITION_MODE_RAW_UNICODE) {
571
0
    uriparser_uri = &uriparser_uris->uri;
572
0
  } else {
573
0
    uriparser_uri = get_normalized_uri(uriparser_uris);
574
0
  }
575
576
0
  int charsRequired = 0;
577
0
  int result = uriToStringCharsRequiredA(uriparser_uri, &charsRequired);
578
0
  ZEND_ASSERT(result == URI_SUCCESS);
579
580
0
  charsRequired++;
581
582
0
  zend_string *uri_string = zend_string_alloc(charsRequired - 1, false);
583
0
  result = uriToStringA(ZSTR_VAL(uri_string), uriparser_uri, charsRequired, NULL);
584
0
  ZEND_ASSERT(result == URI_SUCCESS);
585
586
0
  if (exclude_fragment) {
587
0
    const char *pos = zend_memrchr(ZSTR_VAL(uri_string), '#', ZSTR_LEN(uri_string));
588
0
    if (pos != NULL) {
589
0
      uri_string = zend_string_truncate(uri_string, (pos - ZSTR_VAL(uri_string)), false);
590
0
    }
591
0
  }
592
593
0
  return uri_string;
594
0
}
595
596
static void php_uri_parser_rfc3986_destroy(void *uri)
597
6
{
598
6
  php_uri_parser_rfc3986_uris *uriparser_uris = uri;
599
600
6
  if (UNEXPECTED(uriparser_uris == NULL)) {
601
6
    return;
602
6
  }
603
604
0
  uriFreeUriMembersMmA(&uriparser_uris->uri, mm);
605
0
  uriFreeUriMembersMmA(&uriparser_uris->normalized_uri, mm);
606
607
  efree(uriparser_uris);
608
0
}
609
610
const php_uri_parser php_uri_parser_rfc3986 = {
611
  .name = PHP_URI_PARSER_RFC3986,
612
  .parse = php_uri_parser_rfc3986_parse,
613
  .clone = php_uri_parser_rfc3986_clone,
614
  .to_string = php_uri_parser_rfc3986_to_string,
615
  .destroy = php_uri_parser_rfc3986_destroy,
616
  {
617
    .scheme = {.read = php_uri_parser_rfc3986_scheme_read, .write = php_uri_parser_rfc3986_scheme_write},
618
    .username = {.read = php_uri_parser_rfc3986_username_read, .write = NULL},
619
    .password = {.read = php_uri_parser_rfc3986_password_read, .write = NULL},
620
    .host = {.read = php_uri_parser_rfc3986_host_read, .write = php_uri_parser_rfc3986_host_write},
621
    .port = {.read = php_uri_parser_rfc3986_port_read, .write = php_uri_parser_rfc3986_port_write},
622
    .path = {.read = php_uri_parser_rfc3986_path_read, .write = php_uri_parser_rfc3986_path_write},
623
    .query = {.read = php_uri_parser_rfc3986_query_read, .write = php_uri_parser_rfc3986_query_write},
624
    .fragment = {.read = php_uri_parser_rfc3986_fragment_read, .write = php_uri_parser_rfc3986_fragment_write},
625
  }
626
};