Coverage Report

Created: 2025-11-16 06:23

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