Coverage Report

Created: 2026-04-01 06:49

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
      zend_string *host_str = zend_string_concat3(
241
0
        "[", 1,
242
0
        uriparser_uri->hostText.first, get_text_range_length(&uriparser_uri->hostText),
243
0
        "]", 1
244
0
      );
245
0
      ZVAL_NEW_STR(retval, host_str);
246
0
    } else {
247
0
      ZVAL_STRINGL(retval, uriparser_uri->hostText.first, get_text_range_length(&uriparser_uri->hostText));
248
0
    }
249
0
  } else {
250
0
    ZVAL_NULL(retval);
251
0
  }
252
253
0
  return SUCCESS;
254
0
}
255
256
static zend_result php_uri_parser_rfc3986_host_write(void *uri, zval *value, zval *errors)
257
0
{
258
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
259
0
  int result;
260
261
0
  if (Z_TYPE_P(value) == IS_NULL) {
262
0
    result = uriSetHostAutoMmA(uriparser_uri, NULL, NULL, mm);
263
0
  } else {
264
0
    result = uriSetHostAutoMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
265
0
  }
266
267
0
  switch (result) {
268
0
    case URI_SUCCESS:
269
0
      return SUCCESS;
270
0
    case URI_ERROR_SETHOST_PORT_SET:
271
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot remove the host from a URI that has a port", 0);
272
0
      return FAILURE;
273
0
    case URI_ERROR_SETHOST_USERINFO_SET:
274
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot remove the host from a URI that has a userinfo", 0);
275
0
      return FAILURE;
276
0
    case URI_ERROR_SYNTAX:
277
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified host is malformed", 0);
278
0
      return FAILURE;
279
0
    default:
280
      /* This should be unreachable in practice. */
281
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the host", 0);
282
0
      return FAILURE;
283
0
  }
284
0
}
285
286
ZEND_ATTRIBUTE_NONNULL static zend_long port_str_to_zend_long_checked(const char *str, size_t len)
287
0
{
288
0
  if (len > MAX_LENGTH_OF_LONG) {
289
0
    return -1;
290
0
  }
291
292
0
  char buf[MAX_LENGTH_OF_LONG + 1];
293
0
  *(char*)zend_mempcpy(buf, str, len) = 0;
294
295
0
  zend_ulong result = ZEND_STRTOUL(buf, NULL, 10);
296
297
0
  if (result > ZEND_LONG_MAX) {
298
0
    return -1;
299
0
  }
300
301
0
  return (zend_long)result;
302
0
}
303
304
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_port_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
305
0
{
306
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
307
308
0
  if (has_text_range(&uriparser_uri->portText) && get_text_range_length(&uriparser_uri->portText) > 0) {
309
0
    ZVAL_LONG(retval, port_str_to_zend_long_checked(uriparser_uri->portText.first, get_text_range_length(&uriparser_uri->portText)));
310
0
  } else {
311
0
    ZVAL_NULL(retval);
312
0
  }
313
314
0
  return SUCCESS;
315
0
}
316
317
static zend_result php_uri_parser_rfc3986_port_write(void *uri, zval *value, zval *errors)
318
0
{
319
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
320
0
  int result;
321
322
0
  if (Z_TYPE_P(value) == IS_NULL) {
323
0
    result = uriSetPortTextMmA(uriparser_uri, NULL, NULL, mm);
324
0
  } else {
325
0
    zend_string *tmp = zend_long_to_str(Z_LVAL_P(value));
326
0
    result = uriSetPortTextMmA(uriparser_uri, ZSTR_VAL(tmp), ZSTR_VAL(tmp) + ZSTR_LEN(tmp), mm);
327
0
    zend_string_release_ex(tmp, false);
328
0
  }
329
330
0
  switch (result) {
331
0
    case URI_SUCCESS:
332
0
      return SUCCESS;
333
0
    case URI_ERROR_SETPORT_HOST_NOT_SET:
334
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot set a port without having a host", 0);
335
0
      return FAILURE;
336
0
    case URI_ERROR_SYNTAX:
337
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified port is malformed", 0);
338
0
      return FAILURE;
339
0
    default:
340
      /* This should be unreachable in practice. */
341
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the port", 0);
342
0
      return FAILURE;
343
0
  }
344
0
}
345
346
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_path_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
347
0
{
348
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
349
350
0
  if (uriparser_uri->pathHead != NULL) {
351
0
    size_t total_len = 0;
352
0
    const bool need_leading_slash = uriparser_uri->absolutePath || uriHasHostA(uriparser_uri);
353
354
0
    if (need_leading_slash) {
355
0
      total_len++;
356
0
    }
357
358
0
    for (const UriPathSegmentA *p = uriparser_uri->pathHead; p; p = p->next) {
359
0
      total_len += get_text_range_length(&p->text);
360
0
      if (p->next) {
361
0
        total_len++;
362
0
      }
363
0
    }
364
365
0
    zend_string *str = zend_string_alloc(total_len, false);
366
0
    char *out = ZSTR_VAL(str);
367
368
0
    if (need_leading_slash) {
369
0
      *(out++) = '/';
370
0
    }
371
372
0
    for (const UriPathSegmentA *p = uriparser_uri->pathHead; p; p = p->next) {
373
0
      out = zend_mempcpy(out, p->text.first, get_text_range_length(&p->text));
374
0
      if (p->next) {
375
0
        *(out++) = '/';
376
0
      }
377
0
    }
378
379
0
    *out = '\0';
380
0
    ZVAL_NEW_STR(retval, str);
381
0
  } else if (uriparser_uri->absolutePath) {
382
0
    ZVAL_CHAR(retval, '/');
383
0
  } else {
384
0
    ZVAL_EMPTY_STRING(retval);
385
0
  }
386
387
0
  return SUCCESS;
388
0
}
389
390
static zend_result php_uri_parser_rfc3986_path_write(void *uri, zval *value, zval *errors)
391
0
{
392
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
393
0
  int result;
394
395
0
  if (Z_STRLEN_P(value) == 0) {
396
0
    result = uriSetPathMmA(uriparser_uri, NULL, NULL, mm);
397
0
  } else {
398
0
    result = uriSetPathMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
399
0
  }
400
401
0
  switch (result) {
402
0
    case URI_SUCCESS:
403
0
      return SUCCESS;
404
0
    case URI_ERROR_SYNTAX:
405
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified path is malformed", 0);
406
0
      return FAILURE;
407
0
    default:
408
      /* This should be unreachable in practice. */
409
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the path", 0);
410
0
      return FAILURE;
411
0
  }
412
0
}
413
414
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_query_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
415
0
{
416
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
417
418
0
  if (has_text_range(&uriparser_uri->query)) {
419
0
    ZVAL_STRINGL(retval, uriparser_uri->query.first, get_text_range_length(&uriparser_uri->query));
420
0
  } else {
421
0
    ZVAL_NULL(retval);
422
0
  }
423
424
0
  return SUCCESS;
425
0
}
426
427
static zend_result php_uri_parser_rfc3986_query_write(void *uri, zval *value, zval *errors)
428
0
{
429
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
430
0
  int result;
431
432
0
  if (Z_TYPE_P(value) == IS_NULL) {
433
0
    result = uriSetQueryMmA(uriparser_uri, NULL, NULL, mm);
434
0
  } else {
435
0
    result = uriSetQueryMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
436
0
  }
437
438
0
  switch (result) {
439
0
    case URI_SUCCESS:
440
0
      return SUCCESS;
441
0
    case URI_ERROR_SYNTAX:
442
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified query is malformed", 0);
443
0
      return FAILURE;
444
0
    default:
445
      /* This should be unreachable in practice. */
446
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the query", 0);
447
0
      return FAILURE;
448
0
  }
449
0
}
450
451
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_fragment_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
452
0
{
453
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
454
455
0
  if (has_text_range(&uriparser_uri->fragment)) {
456
0
    ZVAL_STRINGL(retval, uriparser_uri->fragment.first, get_text_range_length(&uriparser_uri->fragment));
457
0
  } else {
458
0
    ZVAL_NULL(retval);
459
0
  }
460
461
0
  return SUCCESS;
462
0
}
463
464
static zend_result php_uri_parser_rfc3986_fragment_write(void *uri, zval *value, zval *errors)
465
0
{
466
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
467
0
  int result;
468
469
0
  if (Z_TYPE_P(value) == IS_NULL) {
470
0
    result = uriSetFragmentMmA(uriparser_uri, NULL, NULL, mm);
471
0
  } else {
472
0
    result = uriSetFragmentMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
473
0
  }
474
475
0
  switch (result) {
476
0
    case URI_SUCCESS:
477
0
      return SUCCESS;
478
0
    case URI_ERROR_SYNTAX:
479
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified fragment is malformed", 0);
480
0
      return FAILURE;
481
0
    default:
482
      /* This should be unreachable in practice. */
483
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the fragment", 0);
484
0
      return FAILURE;
485
0
  }
486
0
}
487
488
static php_uri_parser_rfc3986_uris *uriparser_create_uris(void)
489
0
{
490
0
  php_uri_parser_rfc3986_uris *uriparser_uris = ecalloc(1, sizeof(*uriparser_uris));
491
0
  uriparser_uris->normalized_uri_initialized = false;
492
493
0
  return uriparser_uris;
494
0
}
495
496
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)
497
0
{
498
0
  UriUriA uri = {0};
499
500
  /* Parse the URI. */
501
0
  int result = uriParseSingleUriExMmA(&uri, uri_str, uri_str + uri_str_len, NULL, mm);
502
0
  if (result != URI_SUCCESS) {
503
0
    if (!silent) {
504
0
      switch (result) {
505
0
        case URI_ERROR_SYNTAX:
506
0
          zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified URI is malformed", 0);
507
0
          break;
508
0
        default:
509
          /* This should be unreachable in practice. */
510
0
          zend_throw_exception(php_uri_ce_error, "Failed to parse the specified URI", 0);
511
0
          break;
512
0
      }
513
0
    }
514
515
0
    goto fail;
516
0
  }
517
518
0
  if (uriparser_base_urls != NULL) {
519
0
    UriUriA tmp = {0};
520
521
    /* Combine the parsed URI with the base URI and store the result in 'tmp',
522
     * since the target and source URLs must be distinct. */
523
0
    int result = uriAddBaseUriExMmA(&tmp, &uri, &uriparser_base_urls->uri, URI_RESOLVE_STRICTLY, mm);
524
0
    if (result != URI_SUCCESS) {
525
0
      if (!silent) {
526
0
        switch (result) {
527
0
          case URI_ERROR_ADDBASE_REL_BASE:
528
0
            zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified base URI must be absolute", 0);
529
0
            break;
530
0
          default:
531
            /* This should be unreachable in practice. */
532
0
            zend_throw_exception(php_uri_ce_error, "Failed to resolve the specified URI against the base URI", 0);
533
0
            break;
534
0
        }
535
0
      }
536
537
0
      goto fail;
538
0
    }
539
540
    /* Store the combined URI back into 'uri'. */
541
0
    uriFreeUriMembersMmA(&uri, mm);
542
0
    uri = tmp;
543
0
  }
544
545
  /* Make the resulting URI independent of the 'uri_str'. */
546
0
  uriMakeOwnerMmA(&uri, mm);
547
548
0
  if (has_text_range(&uri.portText) && get_text_range_length(&uri.portText) > 0) {
549
0
    if (port_str_to_zend_long_checked(uri.portText.first, get_text_range_length(&uri.portText)) == -1) {
550
0
      if (!silent) {
551
0
        zend_throw_exception(php_uri_ce_invalid_uri_exception, "The port is out of range", 0);
552
0
      }
553
554
0
      goto fail;
555
0
    }
556
0
  }
557
558
0
  php_uri_parser_rfc3986_uris *uriparser_uris = uriparser_create_uris();
559
0
  uriparser_uris->uri = uri;
560
561
0
  return uriparser_uris;
562
563
0
 fail:
564
565
0
  uriFreeUriMembersMmA(&uri, mm);
566
567
0
  return NULL;
568
0
}
569
570
void *php_uri_parser_rfc3986_parse(const char *uri_str, size_t uri_str_len, const void *base_url, zval *errors, bool silent)
571
0
{
572
0
  return php_uri_parser_rfc3986_parse_ex(uri_str, uri_str_len, base_url, silent);
573
0
}
574
575
ZEND_ATTRIBUTE_NONNULL static void *php_uri_parser_rfc3986_clone(void *uri)
576
0
{
577
0
  const php_uri_parser_rfc3986_uris *uriparser_uris = uri;
578
579
0
  php_uri_parser_rfc3986_uris *new_uriparser_uris = uriparser_create_uris();
580
0
  copy_uri(&new_uriparser_uris->uri, &uriparser_uris->uri);
581
  /* Do not copy the normalized URI: The expected action after cloning is
582
   * modifying the cloned URI (which will invalidate the cached normalized
583
   * URI). */
584
585
0
  return new_uriparser_uris;
586
0
}
587
588
ZEND_ATTRIBUTE_NONNULL static zend_string *php_uri_parser_rfc3986_to_string(void *uri, php_uri_recomposition_mode recomposition_mode, bool exclude_fragment)
589
0
{
590
0
  php_uri_parser_rfc3986_uris *uriparser_uris = uri;
591
0
  const UriUriA *uriparser_uri;
592
593
0
  if (recomposition_mode == PHP_URI_RECOMPOSITION_MODE_RAW_ASCII || recomposition_mode == PHP_URI_RECOMPOSITION_MODE_RAW_UNICODE) {
594
0
    uriparser_uri = &uriparser_uris->uri;
595
0
  } else {
596
0
    uriparser_uri = get_normalized_uri(uriparser_uris);
597
0
  }
598
599
0
  int charsRequired = 0;
600
0
  int result = uriToStringCharsRequiredA(uriparser_uri, &charsRequired);
601
0
  ZEND_ASSERT(result == URI_SUCCESS);
602
603
0
  charsRequired++;
604
605
0
  zend_string *uri_string = zend_string_alloc(charsRequired - 1, false);
606
0
  result = uriToStringA(ZSTR_VAL(uri_string), uriparser_uri, charsRequired, NULL);
607
0
  ZEND_ASSERT(result == URI_SUCCESS);
608
609
0
  if (exclude_fragment) {
610
0
    const char *pos = zend_memrchr(ZSTR_VAL(uri_string), '#', ZSTR_LEN(uri_string));
611
0
    if (pos != NULL) {
612
0
      uri_string = zend_string_truncate(uri_string, (pos - ZSTR_VAL(uri_string)), false);
613
0
      ZSTR_VAL(uri_string)[ZSTR_LEN(uri_string)] = '\0';
614
0
    }
615
0
  }
616
617
0
  return uri_string;
618
0
}
619
620
static void php_uri_parser_rfc3986_destroy(void *uri)
621
5
{
622
5
  php_uri_parser_rfc3986_uris *uriparser_uris = uri;
623
624
5
  if (UNEXPECTED(uriparser_uris == NULL)) {
625
5
    return;
626
5
  }
627
628
0
  uriFreeUriMembersMmA(&uriparser_uris->uri, mm);
629
0
  uriFreeUriMembersMmA(&uriparser_uris->normalized_uri, mm);
630
631
  efree(uriparser_uris);
632
0
}
633
634
PHPAPI const php_uri_parser php_uri_parser_rfc3986 = {
635
  .name = PHP_URI_PARSER_RFC3986,
636
  .parse = php_uri_parser_rfc3986_parse,
637
  .clone = php_uri_parser_rfc3986_clone,
638
  .to_string = php_uri_parser_rfc3986_to_string,
639
  .destroy = php_uri_parser_rfc3986_destroy,
640
  {
641
    .scheme = {.read = php_uri_parser_rfc3986_scheme_read, .write = php_uri_parser_rfc3986_scheme_write},
642
    .username = {.read = php_uri_parser_rfc3986_username_read, .write = NULL},
643
    .password = {.read = php_uri_parser_rfc3986_password_read, .write = NULL},
644
    .host = {.read = php_uri_parser_rfc3986_host_read, .write = php_uri_parser_rfc3986_host_write},
645
    .port = {.read = php_uri_parser_rfc3986_port_read, .write = php_uri_parser_rfc3986_port_write},
646
    .path = {.read = php_uri_parser_rfc3986_path_read, .write = php_uri_parser_rfc3986_path_write},
647
    .query = {.read = php_uri_parser_rfc3986_query_read, .write = php_uri_parser_rfc3986_query_write},
648
    .fragment = {.read = php_uri_parser_rfc3986_fragment_read, .write = php_uri_parser_rfc3986_fragment_write},
649
  }
650
};