Coverage Report

Created: 2026-06-02 06:36

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 © The PHP Group and Contributors.                          |
4
   +----------------------------------------------------------------------+
5
   | This source file is subject to the Modified BSD License that is      |
6
   | bundled with this package in the file LICENSE, and is available      |
7
   | through the World Wide Web at <https://www.php.net/license/>.        |
8
   |                                                                      |
9
   | SPDX-License-Identifier: BSD-3-Clause                                |
10
   +----------------------------------------------------------------------+
11
   | Authors: Máté Kocsis <kocsismate@php.net>                            |
12
   +----------------------------------------------------------------------+
13
*/
14
15
#include "php.h"
16
#include "uri_parser_rfc3986.h"
17
#include "php_uri_common.h"
18
#include "Zend/zend_enum.h"
19
#include "Zend/zend_smart_str.h"
20
#include "Zend/zend_exceptions.h"
21
22
#include <uriparser/Uri.h>
23
24
struct php_uri_parser_rfc3986_uris {
25
  UriUriA uri;
26
  UriUriA normalized_uri;
27
  unsigned int normalization_mask;
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
    int mask_result = uriNormalizeSyntaxMaskRequiredExA(&uriparser_uris->uri, &uriparser_uris->normalization_mask);
89
0
    ZEND_ASSERT(mask_result == URI_SUCCESS);
90
91
0
    if (uriparser_uris->normalization_mask != URI_NORMALIZED) {
92
0
      copy_uri(&uriparser_uris->normalized_uri, &uriparser_uris->uri);
93
0
      int result = uriNormalizeSyntaxExMmA(&uriparser_uris->normalized_uri, uriparser_uris->normalization_mask, mm);
94
0
      ZEND_ASSERT(result == URI_SUCCESS);
95
0
    }
96
0
    uriparser_uris->normalized_uri_initialized = true;
97
0
  }
98
99
0
  if (uriparser_uris->normalization_mask == URI_NORMALIZED) {
100
0
    return &uriparser_uris->uri;
101
0
  }
102
103
0
  return &uriparser_uris->normalized_uri;
104
0
}
105
106
ZEND_ATTRIBUTE_NONNULL static UriUriA *get_uri_for_reading(php_uri_parser_rfc3986_uris *uriparser_uris, php_uri_component_read_mode read_mode)
107
0
{
108
0
  switch (read_mode) {
109
0
    case PHP_URI_COMPONENT_READ_MODE_RAW:
110
0
      return &uriparser_uris->uri;
111
0
    case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII:
112
0
      ZEND_FALLTHROUGH;
113
0
    case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_UNICODE:
114
0
      return get_normalized_uri(uriparser_uris);
115
0
    default: ZEND_UNREACHABLE();
116
0
  }
117
0
}
118
119
ZEND_ATTRIBUTE_NONNULL static UriUriA *get_uri_for_writing(php_uri_parser_rfc3986_uris *uriparser_uris)
120
0
{
121
0
  return &uriparser_uris->uri;
122
0
}
123
124
ZEND_ATTRIBUTE_NONNULL void php_uri_parser_rfc3986_uri_type_read(php_uri_parser_rfc3986_uris *uri, zval *retval)
125
0
{
126
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, PHP_URI_COMPONENT_READ_MODE_RAW);
127
128
0
  const char *type;
129
130
0
  if (has_text_range(&uriparser_uri->scheme)) {
131
0
    type = "Uri";
132
0
  } else if (has_text_range(&uriparser_uri->hostText)) {
133
0
    type = "NetworkPathReference";
134
0
  } else if (uriparser_uri->absolutePath) {
135
0
    type = "AbsolutePathReference";
136
0
  } else {
137
0
    type = "RelativePathReference";
138
0
  }
139
140
0
  ZVAL_OBJ_COPY(retval, zend_enum_get_case_cstr(php_uri_ce_rfc3986_uri_type, type));
141
0
}
142
143
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_scheme_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
144
0
{
145
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
146
147
0
  if (has_text_range(&uriparser_uri->scheme)) {
148
0
    ZVAL_STRINGL(retval, uriparser_uri->scheme.first, get_text_range_length(&uriparser_uri->scheme));
149
0
  } else {
150
0
    ZVAL_NULL(retval);
151
0
  }
152
153
0
  return SUCCESS;
154
0
}
155
156
static zend_result php_uri_parser_rfc3986_scheme_write(void *uri, zval *value, zval *errors)
157
0
{
158
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
159
0
  int result;
160
161
0
  if (Z_TYPE_P(value) == IS_NULL) {
162
0
    result = uriSetSchemeMmA(uriparser_uri, NULL, NULL, mm);
163
0
  } else {
164
0
    result = uriSetSchemeMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
165
0
  }
166
167
0
  switch (result) {
168
0
    case URI_SUCCESS:
169
0
      return SUCCESS;
170
0
    case URI_ERROR_SYNTAX:
171
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified scheme is malformed", 0);
172
0
      return FAILURE;
173
0
    default:
174
      /* This should be unreachable in practice. */
175
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the scheme", 0);
176
0
      return FAILURE;
177
0
  }
178
0
}
179
180
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)
181
0
{
182
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
183
184
0
  if (has_text_range(&uriparser_uri->userInfo)) {
185
0
    ZVAL_STRINGL(retval, uriparser_uri->userInfo.first, get_text_range_length(&uriparser_uri->userInfo));
186
0
  } else {
187
0
    ZVAL_NULL(retval);
188
0
  }
189
190
0
  return SUCCESS;
191
0
}
192
193
zend_result php_uri_parser_rfc3986_userinfo_write(php_uri_parser_rfc3986_uris *uri, zval *value, zval *errors)
194
0
{
195
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
196
0
  int result;
197
198
0
  if (Z_TYPE_P(value) == IS_NULL) {
199
0
    result = uriSetUserInfoMmA(uriparser_uri, NULL, NULL, mm);
200
0
  } else {
201
0
    result = uriSetUserInfoMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
202
0
  }
203
204
0
  switch (result) {
205
0
    case URI_SUCCESS:
206
0
      return SUCCESS;
207
0
    case URI_ERROR_SETUSERINFO_HOST_NOT_SET:
208
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot set a userinfo without having a host", 0);
209
0
      return FAILURE;
210
0
    case URI_ERROR_SYNTAX:
211
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified userinfo is malformed", 0);
212
0
      return FAILURE;
213
0
    default:
214
      /* This should be unreachable in practice. */
215
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the userinfo", 0);
216
0
      return FAILURE;
217
0
  }
218
0
}
219
220
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_username_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
221
0
{
222
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
223
224
0
  if (has_text_range(&uriparser_uri->userInfo)) {
225
0
    size_t length = get_text_range_length(&uriparser_uri->userInfo);
226
0
    const char *c = memchr(uriparser_uri->userInfo.first, ':', length);
227
228
0
    if (c == NULL && length > 0) {
229
0
      ZVAL_STRINGL(retval, uriparser_uri->userInfo.first, length);
230
0
    } else if (c != NULL && c - uriparser_uri->userInfo.first > 0) {
231
0
      ZVAL_STRINGL(retval, uriparser_uri->userInfo.first, c - uriparser_uri->userInfo.first);
232
0
    } else {
233
0
      ZVAL_EMPTY_STRING(retval);
234
0
    }
235
0
  } else {
236
0
    ZVAL_NULL(retval);
237
0
  }
238
239
0
  return SUCCESS;
240
0
}
241
242
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_password_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
243
0
{
244
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
245
246
0
  if (has_text_range(&uriparser_uri->userInfo)) {
247
0
    const char *c = memchr(uriparser_uri->userInfo.first, ':', get_text_range_length(&uriparser_uri->userInfo));
248
249
0
    if (c != NULL && uriparser_uri->userInfo.afterLast - c - 1 > 0) {
250
0
      ZVAL_STRINGL(retval, c + 1, uriparser_uri->userInfo.afterLast - c - 1);
251
0
    } else {
252
0
      ZVAL_EMPTY_STRING(retval);
253
0
    }
254
0
  } else {
255
0
    ZVAL_NULL(retval);
256
0
  }
257
258
0
  return SUCCESS;
259
0
}
260
261
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_host_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
262
0
{
263
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
264
265
0
  if (has_text_range(&uriparser_uri->hostText)) {
266
0
    if (uriparser_uri->hostData.ip6 != NULL || uriparser_uri->hostData.ipFuture.first != NULL) {
267
      /* the textual representation of the host is always accessible in the .hostText field no matter what the host is */
268
0
      zend_string *host_str = zend_string_concat3(
269
0
        "[", 1,
270
0
        uriparser_uri->hostText.first, get_text_range_length(&uriparser_uri->hostText),
271
0
        "]", 1
272
0
      );
273
0
      ZVAL_NEW_STR(retval, host_str);
274
0
    } else {
275
0
      ZVAL_STRINGL(retval, uriparser_uri->hostText.first, get_text_range_length(&uriparser_uri->hostText));
276
0
    }
277
0
  } else {
278
0
    ZVAL_NULL(retval);
279
0
  }
280
281
0
  return SUCCESS;
282
0
}
283
284
ZEND_ATTRIBUTE_NONNULL void php_uri_parser_rfc3986_host_type_read(php_uri_parser_rfc3986_uris *uri, zval *retval)
285
0
{
286
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, PHP_URI_COMPONENT_READ_MODE_RAW);
287
288
0
  if (!has_text_range(&uriparser_uri->hostText)) {
289
0
    ZVAL_NULL(retval);
290
0
    return;
291
0
  }
292
293
0
  const char *type;
294
295
0
  if (uriparser_uri->hostData.ip4 != NULL) {
296
0
    type = "IPv4";
297
0
  } else if (uriparser_uri->hostData.ip6 != NULL) {
298
0
    type = "IPv6";
299
0
  } else if (has_text_range(&uriparser_uri->hostData.ipFuture)) {
300
0
    type = "IPvFuture";
301
0
  } else {
302
0
    type = "RegisteredName";
303
0
  }
304
305
0
  ZVAL_OBJ_COPY(retval, zend_enum_get_case_cstr(php_uri_ce_rfc3986_uri_host_type, type));
306
0
}
307
308
static zend_result php_uri_parser_rfc3986_host_write(void *uri, zval *value, zval *errors)
309
0
{
310
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
311
0
  int result;
312
313
0
  if (Z_TYPE_P(value) == IS_NULL) {
314
0
    result = uriSetHostAutoMmA(uriparser_uri, NULL, NULL, mm);
315
0
  } else {
316
0
    result = uriSetHostAutoMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
317
0
  }
318
319
0
  switch (result) {
320
0
    case URI_SUCCESS:
321
0
      return SUCCESS;
322
0
    case URI_ERROR_SETHOST_PORT_SET:
323
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot remove the host from a URI that has a port", 0);
324
0
      return FAILURE;
325
0
    case URI_ERROR_SETHOST_USERINFO_SET:
326
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot remove the host from a URI that has a userinfo", 0);
327
0
      return FAILURE;
328
0
    case URI_ERROR_SYNTAX:
329
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified host is malformed", 0);
330
0
      return FAILURE;
331
0
    default:
332
      /* This should be unreachable in practice. */
333
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the host", 0);
334
0
      return FAILURE;
335
0
  }
336
0
}
337
338
ZEND_ATTRIBUTE_NONNULL static zend_long port_str_to_zend_long_checked(const char *str, size_t len)
339
0
{
340
0
  if (len > MAX_LENGTH_OF_LONG) {
341
0
    return -1;
342
0
  }
343
344
0
  char buf[MAX_LENGTH_OF_LONG + 1];
345
0
  *(char*)zend_mempcpy(buf, str, len) = 0;
346
347
0
  zend_ulong result = ZEND_STRTOUL(buf, NULL, 10);
348
349
0
  if (result > ZEND_LONG_MAX) {
350
0
    return -1;
351
0
  }
352
353
0
  return (zend_long)result;
354
0
}
355
356
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_port_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
357
0
{
358
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
359
360
0
  if (has_text_range(&uriparser_uri->portText) && get_text_range_length(&uriparser_uri->portText) > 0) {
361
0
    ZVAL_LONG(retval, port_str_to_zend_long_checked(uriparser_uri->portText.first, get_text_range_length(&uriparser_uri->portText)));
362
0
  } else {
363
0
    ZVAL_NULL(retval);
364
0
  }
365
366
0
  return SUCCESS;
367
0
}
368
369
static zend_result php_uri_parser_rfc3986_port_write(void *uri, zval *value, zval *errors)
370
0
{
371
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
372
0
  int result;
373
374
0
  if (Z_TYPE_P(value) == IS_NULL) {
375
0
    result = uriSetPortTextMmA(uriparser_uri, NULL, NULL, mm);
376
0
  } else {
377
0
    zend_string *tmp = zend_long_to_str(Z_LVAL_P(value));
378
0
    result = uriSetPortTextMmA(uriparser_uri, ZSTR_VAL(tmp), ZSTR_VAL(tmp) + ZSTR_LEN(tmp), mm);
379
0
    zend_string_release_ex(tmp, false);
380
0
  }
381
382
0
  switch (result) {
383
0
    case URI_SUCCESS:
384
0
      return SUCCESS;
385
0
    case URI_ERROR_SETPORT_HOST_NOT_SET:
386
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot set a port without having a host", 0);
387
0
      return FAILURE;
388
0
    case URI_ERROR_SYNTAX:
389
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified port is malformed", 0);
390
0
      return FAILURE;
391
0
    default:
392
      /* This should be unreachable in practice. */
393
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the port", 0);
394
0
      return FAILURE;
395
0
  }
396
0
}
397
398
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_path_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
399
0
{
400
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
401
402
0
  if (uriparser_uri->pathHead != NULL) {
403
0
    size_t total_len = 0;
404
0
    const bool need_leading_slash = uriparser_uri->absolutePath || uriHasHostA(uriparser_uri);
405
406
0
    if (need_leading_slash) {
407
0
      total_len++;
408
0
    }
409
410
0
    for (const UriPathSegmentA *p = uriparser_uri->pathHead; p; p = p->next) {
411
0
      total_len += get_text_range_length(&p->text);
412
0
      if (p->next) {
413
0
        total_len++;
414
0
      }
415
0
    }
416
417
0
    zend_string *str = zend_string_alloc(total_len, false);
418
0
    char *out = ZSTR_VAL(str);
419
420
0
    if (need_leading_slash) {
421
0
      *(out++) = '/';
422
0
    }
423
424
0
    for (const UriPathSegmentA *p = uriparser_uri->pathHead; p; p = p->next) {
425
0
      out = zend_mempcpy(out, p->text.first, get_text_range_length(&p->text));
426
0
      if (p->next) {
427
0
        *(out++) = '/';
428
0
      }
429
0
    }
430
431
0
    *out = '\0';
432
0
    ZVAL_NEW_STR(retval, str);
433
0
  } else if (uriparser_uri->absolutePath) {
434
0
    ZVAL_CHAR(retval, '/');
435
0
  } else {
436
0
    ZVAL_EMPTY_STRING(retval);
437
0
  }
438
439
0
  return SUCCESS;
440
0
}
441
442
static zend_result php_uri_parser_rfc3986_path_write(void *uri, zval *value, zval *errors)
443
0
{
444
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
445
0
  int result;
446
447
0
  if (Z_STRLEN_P(value) == 0) {
448
0
    result = uriSetPathMmA(uriparser_uri, NULL, NULL, mm);
449
0
  } else {
450
0
    result = uriSetPathMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
451
0
  }
452
453
0
  switch (result) {
454
0
    case URI_SUCCESS:
455
0
      return SUCCESS;
456
0
    case URI_ERROR_SYNTAX:
457
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified path is malformed", 0);
458
0
      return FAILURE;
459
0
    default:
460
      /* This should be unreachable in practice. */
461
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the path", 0);
462
0
      return FAILURE;
463
0
  }
464
0
}
465
466
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_query_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
467
0
{
468
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
469
470
0
  if (has_text_range(&uriparser_uri->query)) {
471
0
    ZVAL_STRINGL(retval, uriparser_uri->query.first, get_text_range_length(&uriparser_uri->query));
472
0
  } else {
473
0
    ZVAL_NULL(retval);
474
0
  }
475
476
0
  return SUCCESS;
477
0
}
478
479
static zend_result php_uri_parser_rfc3986_query_write(void *uri, zval *value, zval *errors)
480
0
{
481
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
482
0
  int result;
483
484
0
  if (Z_TYPE_P(value) == IS_NULL) {
485
0
    result = uriSetQueryMmA(uriparser_uri, NULL, NULL, mm);
486
0
  } else {
487
0
    result = uriSetQueryMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
488
0
  }
489
490
0
  switch (result) {
491
0
    case URI_SUCCESS:
492
0
      return SUCCESS;
493
0
    case URI_ERROR_SYNTAX:
494
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified query is malformed", 0);
495
0
      return FAILURE;
496
0
    default:
497
      /* This should be unreachable in practice. */
498
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the query", 0);
499
0
      return FAILURE;
500
0
  }
501
0
}
502
503
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_fragment_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
504
0
{
505
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
506
507
0
  if (has_text_range(&uriparser_uri->fragment)) {
508
0
    ZVAL_STRINGL(retval, uriparser_uri->fragment.first, get_text_range_length(&uriparser_uri->fragment));
509
0
  } else {
510
0
    ZVAL_NULL(retval);
511
0
  }
512
513
0
  return SUCCESS;
514
0
}
515
516
static zend_result php_uri_parser_rfc3986_fragment_write(void *uri, zval *value, zval *errors)
517
0
{
518
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
519
0
  int result;
520
521
0
  if (Z_TYPE_P(value) == IS_NULL) {
522
0
    result = uriSetFragmentMmA(uriparser_uri, NULL, NULL, mm);
523
0
  } else {
524
0
    result = uriSetFragmentMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
525
0
  }
526
527
0
  switch (result) {
528
0
    case URI_SUCCESS:
529
0
      return SUCCESS;
530
0
    case URI_ERROR_SYNTAX:
531
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified fragment is malformed", 0);
532
0
      return FAILURE;
533
0
    default:
534
      /* This should be unreachable in practice. */
535
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the fragment", 0);
536
0
      return FAILURE;
537
0
  }
538
0
}
539
540
static php_uri_parser_rfc3986_uris *uriparser_create_uris(void)
541
0
{
542
0
  php_uri_parser_rfc3986_uris *uriparser_uris = ecalloc(1, sizeof(*uriparser_uris));
543
0
  uriparser_uris->normalized_uri_initialized = false;
544
545
0
  return uriparser_uris;
546
0
}
547
548
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)
549
0
{
550
0
  UriUriA uri = {0};
551
552
  /* Parse the URI. */
553
0
  int result = uriParseSingleUriExMmA(&uri, uri_str, uri_str + uri_str_len, NULL, mm);
554
0
  if (result != URI_SUCCESS) {
555
0
    if (!silent) {
556
0
      switch (result) {
557
0
        case URI_ERROR_SYNTAX:
558
0
          zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified URI is malformed", 0);
559
0
          break;
560
0
        default:
561
          /* This should be unreachable in practice. */
562
0
          zend_throw_exception(php_uri_ce_error, "Failed to parse the specified URI", 0);
563
0
          break;
564
0
      }
565
0
    }
566
567
0
    goto fail;
568
0
  }
569
570
0
  if (uriparser_base_urls != NULL) {
571
0
    UriUriA tmp = {0};
572
573
    /* Combine the parsed URI with the base URI and store the result in 'tmp',
574
     * since the target and source URLs must be distinct. */
575
0
    int result = uriAddBaseUriExMmA(&tmp, &uri, &uriparser_base_urls->uri, URI_RESOLVE_STRICTLY, mm);
576
0
    if (result != URI_SUCCESS) {
577
0
      if (!silent) {
578
0
        switch (result) {
579
0
          case URI_ERROR_ADDBASE_REL_BASE:
580
0
            zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified base URI must be absolute", 0);
581
0
            break;
582
0
          default:
583
            /* This should be unreachable in practice. */
584
0
            zend_throw_exception(php_uri_ce_error, "Failed to resolve the specified URI against the base URI", 0);
585
0
            break;
586
0
        }
587
0
      }
588
589
0
      goto fail;
590
0
    }
591
592
    /* Store the combined URI back into 'uri'. */
593
0
    uriFreeUriMembersMmA(&uri, mm);
594
0
    uri = tmp;
595
0
  }
596
597
  /* Make the resulting URI independent of the 'uri_str'. */
598
0
  uriMakeOwnerMmA(&uri, mm);
599
600
0
  if (has_text_range(&uri.portText) && get_text_range_length(&uri.portText) > 0) {
601
0
    if (port_str_to_zend_long_checked(uri.portText.first, get_text_range_length(&uri.portText)) == -1) {
602
0
      if (!silent) {
603
0
        zend_throw_exception(php_uri_ce_invalid_uri_exception, "The port is out of range", 0);
604
0
      }
605
606
0
      goto fail;
607
0
    }
608
0
  }
609
610
0
  php_uri_parser_rfc3986_uris *uriparser_uris = uriparser_create_uris();
611
0
  uriparser_uris->uri = uri;
612
613
0
  return uriparser_uris;
614
615
0
 fail:
616
617
0
  uriFreeUriMembersMmA(&uri, mm);
618
619
0
  return NULL;
620
0
}
621
622
void *php_uri_parser_rfc3986_parse(const char *uri_str, size_t uri_str_len, const void *base_url, zval *errors, bool silent)
623
0
{
624
0
  return php_uri_parser_rfc3986_parse_ex(uri_str, uri_str_len, base_url, silent);
625
0
}
626
627
ZEND_ATTRIBUTE_NONNULL static void *php_uri_parser_rfc3986_clone(void *uri)
628
0
{
629
0
  const php_uri_parser_rfc3986_uris *uriparser_uris = uri;
630
631
0
  php_uri_parser_rfc3986_uris *new_uriparser_uris = uriparser_create_uris();
632
0
  copy_uri(&new_uriparser_uris->uri, &uriparser_uris->uri);
633
  /* Do not copy the normalized URI: The expected action after cloning is
634
   * modifying the cloned URI (which will invalidate the cached normalized
635
   * URI). */
636
637
0
  return new_uriparser_uris;
638
0
}
639
640
ZEND_ATTRIBUTE_NONNULL static zend_string *php_uri_parser_rfc3986_to_string(void *uri, php_uri_recomposition_mode recomposition_mode, bool exclude_fragment)
641
0
{
642
0
  php_uri_parser_rfc3986_uris *uriparser_uris = uri;
643
0
  const UriUriA *uriparser_uri;
644
645
0
  if (recomposition_mode == PHP_URI_RECOMPOSITION_MODE_RAW_ASCII || recomposition_mode == PHP_URI_RECOMPOSITION_MODE_RAW_UNICODE) {
646
0
    uriparser_uri = &uriparser_uris->uri;
647
0
  } else {
648
0
    uriparser_uri = get_normalized_uri(uriparser_uris);
649
0
  }
650
651
0
  int charsRequired = 0;
652
0
  int result = uriToStringCharsRequiredA(uriparser_uri, &charsRequired);
653
0
  ZEND_ASSERT(result == URI_SUCCESS);
654
655
0
  charsRequired++;
656
657
0
  zend_string *uri_string = zend_string_alloc(charsRequired - 1, false);
658
0
  result = uriToStringA(ZSTR_VAL(uri_string), uriparser_uri, charsRequired, NULL);
659
0
  ZEND_ASSERT(result == URI_SUCCESS);
660
661
0
  if (exclude_fragment) {
662
0
    const char *pos = zend_memrchr(ZSTR_VAL(uri_string), '#', ZSTR_LEN(uri_string));
663
0
    if (pos != NULL) {
664
0
      uri_string = zend_string_truncate(uri_string, (pos - ZSTR_VAL(uri_string)), false);
665
0
      ZSTR_VAL(uri_string)[ZSTR_LEN(uri_string)] = '\0';
666
0
    }
667
0
  }
668
669
0
  return uri_string;
670
0
}
671
672
static void php_uri_parser_rfc3986_destroy(void *uri)
673
2
{
674
2
  php_uri_parser_rfc3986_uris *uriparser_uris = uri;
675
676
2
  if (UNEXPECTED(uriparser_uris == NULL)) {
677
2
    return;
678
2
  }
679
680
0
  uriFreeUriMembersMmA(&uriparser_uris->uri, mm);
681
0
  uriFreeUriMembersMmA(&uriparser_uris->normalized_uri, mm);
682
683
  efree(uriparser_uris);
684
0
}
685
686
PHPAPI const php_uri_parser php_uri_parser_rfc3986 = {
687
  .name = PHP_URI_PARSER_RFC3986,
688
  .parse = php_uri_parser_rfc3986_parse,
689
  .clone = php_uri_parser_rfc3986_clone,
690
  .to_string = php_uri_parser_rfc3986_to_string,
691
  .destroy = php_uri_parser_rfc3986_destroy,
692
  {
693
    .scheme = {.read = php_uri_parser_rfc3986_scheme_read, .write = php_uri_parser_rfc3986_scheme_write},
694
    .username = {.read = php_uri_parser_rfc3986_username_read, .write = NULL},
695
    .password = {.read = php_uri_parser_rfc3986_password_read, .write = NULL},
696
    .host = {.read = php_uri_parser_rfc3986_host_read, .write = php_uri_parser_rfc3986_host_write},
697
    .port = {.read = php_uri_parser_rfc3986_port_read, .write = php_uri_parser_rfc3986_port_write},
698
    .path = {.read = php_uri_parser_rfc3986_path_read, .write = php_uri_parser_rfc3986_path_write},
699
    .query = {.read = php_uri_parser_rfc3986_query_read, .write = php_uri_parser_rfc3986_query_write},
700
    .fragment = {.read = php_uri_parser_rfc3986_fragment_read, .write = php_uri_parser_rfc3986_fragment_write},
701
  }
702
};