Coverage Report

Created: 2026-10-28 06:20

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 femalloc(size, /* fallible */ true);
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 fecalloc(nmemb, size, /* fallible */ true);
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 safe_ferealloc(ptr, 1, size, 0, /* fallible */ true);
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_ferealloc(ptr, nmemb, size, 0, /* fallible */ true);
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 bool handle_pending_oom(void)
71
0
{
72
0
  if (ZEND_HAS_OOM()) {
73
0
    zend_handle_delayed_oom();
74
0
    return true;
75
0
  }
76
77
0
  return false;
78
0
}
79
80
static inline size_t get_text_range_length(const UriTextRangeA *range)
81
0
{
82
0
  return range->afterLast - range->first;
83
0
}
84
85
static inline bool has_text_range(const UriTextRangeA *range)
86
0
{
87
0
  return range->first != NULL && range->afterLast != NULL;
88
0
}
89
90
/* Returns false when the copy could not be allocated. */
91
ZEND_ATTRIBUTE_NONNULL static bool copy_uri(UriUriA *new_uriparser_uri, const UriUriA *uriparser_uri)
92
0
{
93
0
  int result = uriCopyUriMmA(new_uriparser_uri, uriparser_uri, mm);
94
0
  ZEND_ASSERT(result == URI_SUCCESS || result == URI_ERROR_MALLOC);
95
96
0
  return result == URI_SUCCESS;
97
0
}
98
99
/* Returns NULL when the normalized form could not be allocated. */
100
0
ZEND_ATTRIBUTE_NONNULL static UriUriA *get_normalized_uri(php_uri_parser_rfc3986_uris *uriparser_uris) {
101
0
  if (!uriparser_uris->normalized_uri_initialized) {
102
0
    int mask_result = uriNormalizeSyntaxMaskRequiredExA(&uriparser_uris->uri, &uriparser_uris->normalization_mask);
103
0
    ZEND_ASSERT(mask_result == URI_SUCCESS);
104
105
0
    if (uriparser_uris->normalization_mask != URI_NORMALIZED) {
106
0
      if (UNEXPECTED(!copy_uri(&uriparser_uris->normalized_uri, &uriparser_uris->uri))) {
107
0
        return NULL;
108
0
      }
109
0
      int result = uriNormalizeSyntaxExMmA(&uriparser_uris->normalized_uri, uriparser_uris->normalization_mask, mm);
110
0
      ZEND_ASSERT(result == URI_SUCCESS || result == URI_ERROR_MALLOC);
111
0
      if (UNEXPECTED(result != URI_SUCCESS)) {
112
0
        uriFreeUriMembersMmA(&uriparser_uris->normalized_uri, mm);
113
0
        memset(&uriparser_uris->normalized_uri, 0, sizeof(uriparser_uris->normalized_uri));
114
0
        return NULL;
115
0
      }
116
0
    }
117
0
    uriparser_uris->normalized_uri_initialized = true;
118
0
  }
119
120
0
  if (uriparser_uris->normalization_mask == URI_NORMALIZED) {
121
0
    return &uriparser_uris->uri;
122
0
  }
123
124
0
  return &uriparser_uris->normalized_uri;
125
0
}
126
127
ZEND_ATTRIBUTE_NONNULL static UriUriA *get_uri_for_reading(php_uri_parser_rfc3986_uris *uriparser_uris, const php_uri_component_read_mode read_mode)
128
0
{
129
0
  switch (read_mode) {
130
0
    case PHP_URI_COMPONENT_READ_MODE_RAW:
131
0
      return &uriparser_uris->uri;
132
0
    case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII:
133
0
      ZEND_FALLTHROUGH;
134
0
    case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_UNICODE:
135
0
      return get_normalized_uri(uriparser_uris);
136
0
    default: ZEND_UNREACHABLE();
137
0
  }
138
0
}
139
140
ZEND_ATTRIBUTE_NONNULL static UriUriA *get_uri_for_writing(php_uri_parser_rfc3986_uris *uriparser_uris)
141
0
{
142
0
  return &uriparser_uris->uri;
143
0
}
144
145
ZEND_ATTRIBUTE_NONNULL void php_uri_parser_rfc3986_uri_type_read(php_uri_parser_rfc3986_uris *uri, zval *retval)
146
0
{
147
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, PHP_URI_COMPONENT_READ_MODE_RAW);
148
149
0
  zend_object *value;
150
151
0
  if (has_text_range(&uriparser_uri->scheme)) {
152
0
    value = zend_enum_get_case_by_id(php_uri_ce_rfc3986_uri_type, ZEND_ENUM_Uri_Rfc3986_UriType_Uri);
153
0
  } else if (has_text_range(&uriparser_uri->hostText)) {
154
0
    value = zend_enum_get_case_by_id(php_uri_ce_rfc3986_uri_type, ZEND_ENUM_Uri_Rfc3986_UriType_NetworkPathReference);
155
0
  } else if (uriparser_uri->absolutePath) {
156
0
    value = zend_enum_get_case_by_id(php_uri_ce_rfc3986_uri_type, ZEND_ENUM_Uri_Rfc3986_UriType_AbsolutePathReference);
157
0
  } else {
158
0
    value = zend_enum_get_case_by_id(php_uri_ce_rfc3986_uri_type, ZEND_ENUM_Uri_Rfc3986_UriType_RelativePathReference);
159
0
  }
160
161
0
  ZVAL_OBJ_COPY(retval, value);
162
0
}
163
164
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_scheme_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval)
165
0
{
166
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
167
0
  if (UNEXPECTED(uriparser_uri == NULL)) {
168
0
    return FAILURE;
169
0
  }
170
171
0
  if (has_text_range(&uriparser_uri->scheme)) {
172
0
    ZVAL_STRINGL(retval, uriparser_uri->scheme.first, get_text_range_length(&uriparser_uri->scheme));
173
0
  } else {
174
0
    ZVAL_NULL(retval);
175
0
  }
176
177
0
  return SUCCESS;
178
0
}
179
180
static zend_result php_uri_parser_rfc3986_scheme_write(void *uri, const zval *value, zval *errors)
181
0
{
182
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
183
0
  int result;
184
185
0
  if (Z_TYPE_P(value) == IS_NULL) {
186
0
    result = uriSetSchemeMmA(uriparser_uri, NULL, NULL, mm);
187
0
  } else {
188
0
    result = uriSetSchemeMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
189
0
  }
190
191
0
  switch (result) {
192
0
    case URI_SUCCESS:
193
0
      return SUCCESS;
194
0
    case URI_ERROR_SYNTAX:
195
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified scheme is malformed", 0);
196
0
      return FAILURE;
197
0
    default:
198
0
      if (handle_pending_oom()) {
199
0
        return FAILURE;
200
0
      }
201
202
      /* This should be unreachable in practice. */
203
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the scheme", 0);
204
0
      return FAILURE;
205
0
  }
206
0
}
207
208
ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_userinfo_read(php_uri_parser_rfc3986_uris *uri, const php_uri_component_read_mode read_mode, zval *retval)
209
0
{
210
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
211
0
  if (UNEXPECTED(uriparser_uri == NULL)) {
212
0
    return FAILURE;
213
0
  }
214
215
0
  if (has_text_range(&uriparser_uri->userInfo)) {
216
0
    ZVAL_STRINGL(retval, uriparser_uri->userInfo.first, get_text_range_length(&uriparser_uri->userInfo));
217
0
  } else {
218
0
    ZVAL_NULL(retval);
219
0
  }
220
221
0
  return SUCCESS;
222
0
}
223
224
zend_result php_uri_parser_rfc3986_userinfo_write(php_uri_parser_rfc3986_uris *uri, const zval *value, zval *errors)
225
0
{
226
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
227
0
  int result;
228
229
0
  if (Z_TYPE_P(value) == IS_NULL) {
230
0
    result = uriSetUserInfoMmA(uriparser_uri, NULL, NULL, mm);
231
0
  } else {
232
0
    result = uriSetUserInfoMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
233
0
  }
234
235
0
  switch (result) {
236
0
    case URI_SUCCESS:
237
0
      return SUCCESS;
238
0
    case URI_ERROR_SETUSERINFO_HOST_NOT_SET:
239
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot set a userinfo without having a host", 0);
240
0
      return FAILURE;
241
0
    case URI_ERROR_SYNTAX:
242
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified userinfo is malformed", 0);
243
0
      return FAILURE;
244
0
    default:
245
0
      if (handle_pending_oom()) {
246
0
        return FAILURE;
247
0
      }
248
249
      /* This should be unreachable in practice. */
250
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the userinfo", 0);
251
0
      return FAILURE;
252
0
  }
253
0
}
254
255
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_username_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval)
256
0
{
257
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
258
0
  if (UNEXPECTED(uriparser_uri == NULL)) {
259
0
    return FAILURE;
260
0
  }
261
262
0
  if (has_text_range(&uriparser_uri->userInfo)) {
263
0
    size_t length = get_text_range_length(&uriparser_uri->userInfo);
264
0
    const char *c = memchr(uriparser_uri->userInfo.first, ':', length);
265
266
0
    if (c == NULL && length > 0) {
267
0
      ZVAL_STRINGL(retval, uriparser_uri->userInfo.first, length);
268
0
    } else if (c != NULL && c - uriparser_uri->userInfo.first > 0) {
269
0
      ZVAL_STRINGL(retval, uriparser_uri->userInfo.first, c - uriparser_uri->userInfo.first);
270
0
    } else {
271
0
      ZVAL_EMPTY_STRING(retval);
272
0
    }
273
0
  } else {
274
0
    ZVAL_NULL(retval);
275
0
  }
276
277
0
  return SUCCESS;
278
0
}
279
280
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_password_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval)
281
0
{
282
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
283
0
  if (UNEXPECTED(uriparser_uri == NULL)) {
284
0
    return FAILURE;
285
0
  }
286
287
0
  if (has_text_range(&uriparser_uri->userInfo)) {
288
0
    const char *c = memchr(uriparser_uri->userInfo.first, ':', get_text_range_length(&uriparser_uri->userInfo));
289
290
0
    if (c != NULL && uriparser_uri->userInfo.afterLast - c - 1 > 0) {
291
0
      ZVAL_STRINGL(retval, c + 1, uriparser_uri->userInfo.afterLast - c - 1);
292
0
    } else {
293
0
      ZVAL_EMPTY_STRING(retval);
294
0
    }
295
0
  } else {
296
0
    ZVAL_NULL(retval);
297
0
  }
298
299
0
  return SUCCESS;
300
0
}
301
302
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_host_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval)
303
0
{
304
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
305
0
  if (UNEXPECTED(uriparser_uri == NULL)) {
306
0
    return FAILURE;
307
0
  }
308
309
0
  if (has_text_range(&uriparser_uri->hostText)) {
310
0
    if (uriparser_uri->hostData.ip6 != NULL || uriparser_uri->hostData.ipFuture.first != NULL) {
311
      /* the textual representation of the host is always accessible in the .hostText field no matter what the host is */
312
0
      zend_string *host_str = zend_string_concat3(
313
0
        "[", 1,
314
0
        uriparser_uri->hostText.first, get_text_range_length(&uriparser_uri->hostText),
315
0
        "]", 1
316
0
      );
317
0
      ZVAL_NEW_STR(retval, host_str);
318
0
    } else {
319
0
      ZVAL_STRINGL(retval, uriparser_uri->hostText.first, get_text_range_length(&uriparser_uri->hostText));
320
0
    }
321
0
  } else {
322
0
    ZVAL_NULL(retval);
323
0
  }
324
325
0
  return SUCCESS;
326
0
}
327
328
ZEND_ATTRIBUTE_NONNULL void php_uri_parser_rfc3986_host_type_read(php_uri_parser_rfc3986_uris *uri, zval *retval)
329
0
{
330
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, PHP_URI_COMPONENT_READ_MODE_RAW);
331
332
0
  if (!has_text_range(&uriparser_uri->hostText)) {
333
0
    ZVAL_NULL(retval);
334
0
    return;
335
0
  }
336
337
0
  zend_object *value;
338
339
0
  if (uriparser_uri->hostData.ip4 != NULL) {
340
0
    value = zend_enum_get_case_by_id(php_uri_ce_rfc3986_uri_host_type, ZEND_ENUM_Uri_Rfc3986_UriHostType_IpV4);
341
0
  } else if (uriparser_uri->hostData.ip6 != NULL) {
342
0
    value = zend_enum_get_case_by_id(php_uri_ce_rfc3986_uri_host_type, ZEND_ENUM_Uri_Rfc3986_UriHostType_IpV6);
343
0
  } else if (has_text_range(&uriparser_uri->hostData.ipFuture)) {
344
0
    value = zend_enum_get_case_by_id(php_uri_ce_rfc3986_uri_host_type, ZEND_ENUM_Uri_Rfc3986_UriHostType_IpVFuture);
345
0
  } else {
346
0
    value = zend_enum_get_case_by_id(php_uri_ce_rfc3986_uri_host_type, ZEND_ENUM_Uri_Rfc3986_UriHostType_RegisteredName);
347
0
  }
348
349
0
  ZVAL_OBJ_COPY(retval, value);
350
0
}
351
352
static zend_result php_uri_parser_rfc3986_host_write(void *uri, const zval *value, zval *errors)
353
0
{
354
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
355
0
  int result;
356
357
0
  if (Z_TYPE_P(value) == IS_NULL) {
358
0
    result = uriSetHostAutoMmA(uriparser_uri, NULL, NULL, mm);
359
0
  } else {
360
0
    result = uriSetHostAutoMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
361
0
  }
362
363
0
  switch (result) {
364
0
    case URI_SUCCESS:
365
0
      return SUCCESS;
366
0
    case URI_ERROR_SETHOST_PORT_SET:
367
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot remove the host from a URI that has a port", 0);
368
0
      return FAILURE;
369
0
    case URI_ERROR_SETHOST_USERINFO_SET:
370
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot remove the host from a URI that has a userinfo", 0);
371
0
      return FAILURE;
372
0
    case URI_ERROR_SYNTAX:
373
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified host is malformed", 0);
374
0
      return FAILURE;
375
0
    default:
376
0
      if (handle_pending_oom()) {
377
0
        return FAILURE;
378
0
      }
379
380
      /* This should be unreachable in practice. */
381
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the host", 0);
382
0
      return FAILURE;
383
0
  }
384
0
}
385
386
ZEND_ATTRIBUTE_NONNULL static zend_long port_str_to_zend_long_checked(const char *str, const size_t len)
387
0
{
388
0
  if (len > MAX_LENGTH_OF_LONG) {
389
0
    return -1;
390
0
  }
391
392
0
  char buf[MAX_LENGTH_OF_LONG + 1];
393
0
  *(char*)zend_mempcpy(buf, str, len) = 0;
394
395
0
  zend_ulong result = ZEND_STRTOUL(buf, NULL, 10);
396
397
0
  if (result > ZEND_LONG_MAX) {
398
0
    return -1;
399
0
  }
400
401
0
  return (zend_long)result;
402
0
}
403
404
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_port_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval)
405
0
{
406
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
407
0
  if (UNEXPECTED(uriparser_uri == NULL)) {
408
0
    return FAILURE;
409
0
  }
410
411
0
  if (has_text_range(&uriparser_uri->portText) && get_text_range_length(&uriparser_uri->portText) > 0) {
412
0
    ZVAL_LONG(retval, port_str_to_zend_long_checked(uriparser_uri->portText.first, get_text_range_length(&uriparser_uri->portText)));
413
0
  } else {
414
0
    ZVAL_NULL(retval);
415
0
  }
416
417
0
  return SUCCESS;
418
0
}
419
420
static zend_result php_uri_parser_rfc3986_port_write(void *uri, const zval *value, zval *errors)
421
0
{
422
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
423
0
  int result;
424
425
0
  if (Z_TYPE_P(value) == IS_NULL) {
426
0
    result = uriSetPortTextMmA(uriparser_uri, NULL, NULL, mm);
427
0
  } else {
428
0
    zend_string *tmp = zend_long_to_str(Z_LVAL_P(value));
429
0
    result = uriSetPortTextMmA(uriparser_uri, ZSTR_VAL(tmp), ZSTR_VAL(tmp) + ZSTR_LEN(tmp), mm);
430
0
    zend_string_release_ex(tmp, false);
431
0
  }
432
433
0
  switch (result) {
434
0
    case URI_SUCCESS:
435
0
      return SUCCESS;
436
0
    case URI_ERROR_SETPORT_HOST_NOT_SET:
437
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "Cannot set a port without having a host", 0);
438
0
      return FAILURE;
439
0
    case URI_ERROR_SYNTAX:
440
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified port is malformed", 0);
441
0
      return FAILURE;
442
0
    default:
443
0
      if (handle_pending_oom()) {
444
0
        return FAILURE;
445
0
      }
446
447
      /* This should be unreachable in practice. */
448
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the port", 0);
449
0
      return FAILURE;
450
0
  }
451
0
}
452
453
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_path_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval)
454
0
{
455
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
456
0
  if (UNEXPECTED(uriparser_uri == NULL)) {
457
0
    return FAILURE;
458
0
  }
459
460
0
  if (uriparser_uri->pathHead != NULL) {
461
0
    size_t total_len = 0;
462
0
    const bool need_leading_slash = uriparser_uri->absolutePath || uriHasHostA(uriparser_uri);
463
464
0
    if (need_leading_slash) {
465
0
      total_len++;
466
0
    }
467
468
0
    for (const UriPathSegmentA *p = uriparser_uri->pathHead; p; p = p->next) {
469
0
      total_len += get_text_range_length(&p->text);
470
0
      if (p->next) {
471
0
        total_len++;
472
0
      }
473
0
    }
474
475
0
    zend_string *str = zend_string_alloc(total_len, false);
476
0
    char *out = ZSTR_VAL(str);
477
478
0
    if (need_leading_slash) {
479
0
      *(out++) = '/';
480
0
    }
481
482
0
    for (const UriPathSegmentA *p = uriparser_uri->pathHead; p; p = p->next) {
483
0
      out = zend_mempcpy(out, p->text.first, get_text_range_length(&p->text));
484
0
      if (p->next) {
485
0
        *(out++) = '/';
486
0
      }
487
0
    }
488
489
0
    *out = '\0';
490
0
    ZVAL_NEW_STR(retval, str);
491
0
  } else if (uriparser_uri->absolutePath) {
492
0
    ZVAL_CHAR(retval, '/');
493
0
  } else {
494
0
    ZVAL_EMPTY_STRING(retval);
495
0
  }
496
497
0
  return SUCCESS;
498
0
}
499
500
static zend_result php_uri_parser_rfc3986_path_write(void *uri, const zval *value, zval *errors)
501
0
{
502
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
503
0
  int result;
504
505
0
  if (Z_STRLEN_P(value) == 0) {
506
0
    result = uriSetPathMmA(uriparser_uri, NULL, NULL, mm);
507
0
  } else {
508
0
    result = uriSetPathMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
509
0
  }
510
511
0
  switch (result) {
512
0
    case URI_SUCCESS:
513
0
      return SUCCESS;
514
0
    case URI_ERROR_SYNTAX:
515
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified path is malformed", 0);
516
0
      return FAILURE;
517
0
    default:
518
0
      if (handle_pending_oom()) {
519
0
        return FAILURE;
520
0
      }
521
522
      /* This should be unreachable in practice. */
523
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the path", 0);
524
0
      return FAILURE;
525
0
  }
526
0
}
527
528
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_query_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval)
529
0
{
530
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
531
0
  if (UNEXPECTED(uriparser_uri == NULL)) {
532
0
    return FAILURE;
533
0
  }
534
535
0
  if (has_text_range(&uriparser_uri->query)) {
536
0
    ZVAL_STRINGL(retval, uriparser_uri->query.first, get_text_range_length(&uriparser_uri->query));
537
0
  } else {
538
0
    ZVAL_NULL(retval);
539
0
  }
540
541
0
  return SUCCESS;
542
0
}
543
544
static zend_result php_uri_parser_rfc3986_query_write(void *uri, const zval *value, zval *errors)
545
0
{
546
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
547
0
  int result;
548
549
0
  if (Z_TYPE_P(value) == IS_NULL) {
550
0
    result = uriSetQueryMmA(uriparser_uri, NULL, NULL, mm);
551
0
  } else {
552
0
    result = uriSetQueryMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
553
0
  }
554
555
0
  switch (result) {
556
0
    case URI_SUCCESS:
557
0
      return SUCCESS;
558
0
    case URI_ERROR_SYNTAX:
559
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified query is malformed", 0);
560
0
      return FAILURE;
561
0
    default:
562
0
      if (handle_pending_oom()) {
563
0
        return FAILURE;
564
0
      }
565
566
      /* This should be unreachable in practice. */
567
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the query", 0);
568
0
      return FAILURE;
569
0
  }
570
0
}
571
572
ZEND_ATTRIBUTE_NONNULL static zend_result php_uri_parser_rfc3986_fragment_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval)
573
0
{
574
0
  const UriUriA *uriparser_uri = get_uri_for_reading(uri, read_mode);
575
0
  if (UNEXPECTED(uriparser_uri == NULL)) {
576
0
    return FAILURE;
577
0
  }
578
579
0
  if (has_text_range(&uriparser_uri->fragment)) {
580
0
    ZVAL_STRINGL(retval, uriparser_uri->fragment.first, get_text_range_length(&uriparser_uri->fragment));
581
0
  } else {
582
0
    ZVAL_NULL(retval);
583
0
  }
584
585
0
  return SUCCESS;
586
0
}
587
588
static zend_result php_uri_parser_rfc3986_fragment_write(void *uri, const zval *value, zval *errors)
589
0
{
590
0
  UriUriA *uriparser_uri = get_uri_for_writing(uri);
591
0
  int result;
592
593
0
  if (Z_TYPE_P(value) == IS_NULL) {
594
0
    result = uriSetFragmentMmA(uriparser_uri, NULL, NULL, mm);
595
0
  } else {
596
0
    result = uriSetFragmentMmA(uriparser_uri, Z_STRVAL_P(value), Z_STRVAL_P(value) + Z_STRLEN_P(value), mm);
597
0
  }
598
599
0
  switch (result) {
600
0
    case URI_SUCCESS:
601
0
      return SUCCESS;
602
0
    case URI_ERROR_SYNTAX:
603
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified fragment is malformed", 0);
604
0
      return FAILURE;
605
0
    default:
606
0
      if (handle_pending_oom()) {
607
0
        return FAILURE;
608
0
      }
609
610
      /* This should be unreachable in practice. */
611
0
      zend_throw_exception(php_uri_ce_error, "Failed to update the fragment", 0);
612
0
      return FAILURE;
613
0
  }
614
0
}
615
616
static php_uri_parser_rfc3986_uris *uriparser_create_uris(void)
617
0
{
618
0
  php_uri_parser_rfc3986_uris *uriparser_uris = ecalloc(1, sizeof(*uriparser_uris));
619
0
  uriparser_uris->normalized_uri_initialized = false;
620
621
0
  return uriparser_uris;
622
0
}
623
624
static zend_result php_uri_parser_rfc3986_add_base_url(
625
  UriUriA *tmp, const UriUriA *uri, const php_uri_parser_rfc3986_uris *uriparser_base_urls, const bool silent
626
0
) {
627
0
  const int result = uriAddBaseUriExMmA(tmp, uri, &uriparser_base_urls->uri, URI_RESOLVE_STRICTLY, mm);
628
0
  if (result != URI_SUCCESS) {
629
0
    if (!handle_pending_oom() && !silent) {
630
0
      switch (result) {
631
0
        case URI_ERROR_ADDBASE_REL_BASE:
632
0
          zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified base URI must be absolute", 0);
633
0
          break;
634
0
        default:
635
          /* This should be unreachable in practice. */
636
0
          zend_throw_exception(php_uri_ce_error, "Failed to resolve the specified URI against the base URI", 0);
637
0
          break;
638
0
      }
639
0
    }
640
641
0
    return FAILURE;
642
0
  }
643
644
0
  return SUCCESS;
645
0
}
646
647
php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_parse_ex(const char *uri_str, const size_t uri_str_len, const php_uri_parser_rfc3986_uris *uriparser_base_urls, const bool silent)
648
0
{
649
0
  UriUriA uri = {0};
650
651
  /* Parse the URI. */
652
0
  const int result = uriParseSingleUriExMmA(&uri, uri_str, uri_str + uri_str_len, NULL, mm);
653
0
  if (result != URI_SUCCESS) {
654
0
    if (!handle_pending_oom() && !silent) {
655
0
      switch (result) {
656
0
        case URI_ERROR_SYNTAX:
657
0
          zend_throw_exception(php_uri_ce_invalid_uri_exception, "The specified URI is malformed", 0);
658
0
          break;
659
0
        default:
660
          /* This should be unreachable in practice. */
661
0
          zend_throw_exception(php_uri_ce_error, "Failed to parse the specified URI", 0);
662
0
          break;
663
0
      }
664
0
    }
665
666
0
    goto fail;
667
0
  }
668
669
0
  if (uriparser_base_urls != NULL) {
670
0
    UriUriA tmp = {0};
671
672
    /* Combine the parsed URI with the base URI and store the result in 'tmp',
673
     * since the target and source URLs must be distinct. */
674
0
    if (php_uri_parser_rfc3986_add_base_url(&tmp, &uri, uriparser_base_urls, silent) == FAILURE) {
675
0
      goto fail;
676
0
    }
677
678
    /* Store the combined URI back into 'uri'. */
679
0
    uriFreeUriMembersMmA(&uri, mm);
680
0
    uri = tmp;
681
0
  }
682
683
  /* Make the resulting URI independent of the 'uri_str'. */
684
0
  if (UNEXPECTED(uriMakeOwnerMmA(&uri, mm) != URI_SUCCESS)) {
685
    /* The only documented failure is a failed allocation, and the caller
686
     * expects either a URI or a reported error. */
687
0
    if (!handle_pending_oom() && !silent) {
688
0
      zend_throw_exception(php_uri_ce_error, "Failed to parse the specified URI", 0);
689
0
    }
690
691
0
    goto fail;
692
0
  }
693
694
0
  if (has_text_range(&uri.portText) && get_text_range_length(&uri.portText) > 0) {
695
0
    if (port_str_to_zend_long_checked(uri.portText.first, get_text_range_length(&uri.portText)) == -1) {
696
0
      if (!silent) {
697
0
        zend_throw_exception(php_uri_ce_invalid_uri_exception, "The port is out of range", 0);
698
0
      }
699
700
0
      goto fail;
701
0
    }
702
0
  }
703
704
0
  php_uri_parser_rfc3986_uris *uriparser_uris = uriparser_create_uris();
705
0
  uriparser_uris->uri = uri;
706
707
0
  return uriparser_uris;
708
709
0
 fail:
710
711
0
  uriFreeUriMembersMmA(&uri, mm);
712
713
0
  return NULL;
714
0
}
715
716
static void *php_uri_parser_rfc3986_parse(const char *uri_str, const size_t uri_str_len, const void *base_url, zval *errors, bool silent)
717
0
{
718
0
  return php_uri_parser_rfc3986_parse_ex(uri_str, uri_str_len, base_url, silent);
719
0
}
720
721
ZEND_ATTRIBUTE_NONNULL static void *php_uri_parser_rfc3986_clone(void *uri)
722
0
{
723
0
  const php_uri_parser_rfc3986_uris *uriparser_uris = uri;
724
725
0
  php_uri_parser_rfc3986_uris *new_uriparser_uris = uriparser_create_uris();
726
0
  if (UNEXPECTED(!copy_uri(&new_uriparser_uris->uri, &uriparser_uris->uri))) {
727
0
    efree(new_uriparser_uris);
728
0
    return NULL;
729
0
  }
730
  /* Do not copy the normalized URI: The expected action after cloning is
731
   * modifying the cloned URI (which will invalidate the cached normalized
732
   * URI). */
733
734
0
  return new_uriparser_uris;
735
0
}
736
737
ZEND_ATTRIBUTE_NONNULL static zend_string *php_uri_parser_rfc3986_to_string(void *uri, const php_uri_recomposition_mode recomposition_mode, const bool exclude_fragment)
738
0
{
739
0
  php_uri_parser_rfc3986_uris *uriparser_uris = uri;
740
0
  const UriUriA *uriparser_uri;
741
742
0
  if (recomposition_mode == PHP_URI_RECOMPOSITION_MODE_RAW_ASCII || recomposition_mode == PHP_URI_RECOMPOSITION_MODE_RAW_UNICODE) {
743
0
    uriparser_uri = &uriparser_uris->uri;
744
0
  } else {
745
0
    uriparser_uri = get_normalized_uri(uriparser_uris);
746
0
    if (UNEXPECTED(uriparser_uri == NULL)) {
747
0
      return NULL;
748
0
    }
749
0
  }
750
751
0
  int charsRequired = 0;
752
0
  int result = uriToStringCharsRequiredA(uriparser_uri, &charsRequired);
753
0
  ZEND_ASSERT(result == URI_SUCCESS);
754
755
0
  charsRequired++;
756
757
0
  zend_string *uri_string = zend_string_alloc(charsRequired - 1, false);
758
0
  result = uriToStringA(ZSTR_VAL(uri_string), uriparser_uri, charsRequired, NULL);
759
0
  ZEND_ASSERT(result == URI_SUCCESS);
760
761
0
  if (exclude_fragment) {
762
0
    const char *pos = zend_memrchr(ZSTR_VAL(uri_string), '#', ZSTR_LEN(uri_string));
763
0
    if (pos != NULL) {
764
0
      uri_string = zend_string_truncate(uri_string, (pos - ZSTR_VAL(uri_string)), false);
765
0
      ZSTR_VAL(uri_string)[ZSTR_LEN(uri_string)] = '\0';
766
0
    }
767
0
  }
768
769
0
  return uri_string;
770
0
}
771
772
static void php_uri_parser_rfc3986_destroy(void *uri)
773
11
{
774
11
  php_uri_parser_rfc3986_uris *uriparser_uris = uri;
775
776
11
  if (UNEXPECTED(uriparser_uris == NULL)) {
777
11
    return;
778
11
  }
779
780
0
  uriFreeUriMembersMmA(&uriparser_uris->uri, mm);
781
0
  uriFreeUriMembersMmA(&uriparser_uris->normalized_uri, mm);
782
783
0
  efree(uriparser_uris);
784
0
}
785
786
static zend_always_inline zend_result php_uri_parser_rfc3986_validate_component_result(const bool well_formed, const char *component_name)
787
0
{
788
0
  if (well_formed) {
789
0
    return SUCCESS;
790
0
  }
791
792
0
  zend_throw_exception_ex(php_uri_ce_invalid_uri_exception, 0, "The specified %s is malformed", component_name);
793
0
  return FAILURE;
794
0
}
795
796
ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_scheme_validate(const zend_string *scheme)
797
0
{
798
0
  const char *p = ZSTR_VAL(scheme);
799
0
  const size_t len = ZSTR_LEN(scheme);
800
0
  const bool well_formed = uriIsWellFormedSchemeA(p, p + len) == URI_TRUE;
801
802
0
  return php_uri_parser_rfc3986_validate_component_result(well_formed, "scheme");
803
0
}
804
805
ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_userinfo_validate(const zend_string *userinfo)
806
0
{
807
0
  const char *p = ZSTR_VAL(userinfo);
808
0
  const size_t len = ZSTR_LEN(userinfo);
809
0
  const bool well_formed = uriIsWellFormedUserInfoA(p, p + len) == URI_TRUE;
810
811
0
  return php_uri_parser_rfc3986_validate_component_result(well_formed, "userinfo");
812
0
}
813
814
ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_host_validate(const zend_string *host)
815
0
{
816
0
  const char *p = ZSTR_VAL(host);
817
0
  const size_t len = ZSTR_LEN(host);
818
819
0
  if (len == 0) {
820
0
    return SUCCESS;
821
0
  }
822
823
0
  if (p[0] == '[') {
824
0
    if (p[len - 1] != ']') {
825
0
      return php_uri_parser_rfc3986_validate_component_result(false, "host");
826
0
    }
827
828
0
    if (len >= 2 && (p[1] == 'v' || p[1] == 'V')) {
829
0
      return php_uri_parser_rfc3986_validate_component_result(
830
0
        uriIsWellFormedHostIpFutureMmA(p + 1, p + len - 1, mm) == URI_SUCCESS,
831
0
        "host"
832
0
      );
833
0
    }
834
835
0
    return php_uri_parser_rfc3986_validate_component_result(
836
0
      uriIsWellFormedHostIp6MmA(p + 1, p + len - 1, mm) == URI_SUCCESS,
837
0
      "host"
838
0
    );
839
0
  }
840
841
0
  if (uriIsWellFormedHostIp4A(p, p + len) == URI_TRUE) {
842
0
    return SUCCESS;
843
0
  }
844
845
0
  return php_uri_parser_rfc3986_validate_component_result(
846
0
    uriIsWellFormedHostRegNameA(p, p + len) == URI_TRUE,
847
0
    "host"
848
0
  );
849
0
}
850
851
ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_port_validate(const zend_long port)
852
0
{
853
0
  char buf[MAX_LENGTH_OF_LONG + 1];
854
0
  const char *res = zend_print_long_to_buf(buf + sizeof(buf) - 1, port);
855
856
0
  const bool well_formed = uriIsWellFormedPortA(res, res + strlen(res));
857
858
0
  return php_uri_parser_rfc3986_validate_component_result(well_formed, "port");
859
0
}
860
861
ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_path_validate(const zend_string *path)
862
0
{
863
0
  const char *p = ZSTR_VAL(path);
864
0
  const size_t len = ZSTR_LEN(path);
865
  /* The build() method checks whether the path begins with a "/" when there's a host.
866
   * In order to skip doing the same check, a false hasHost argument is passed to uriIsWellFormedPathA(). */
867
0
  const bool well_formed = uriIsWellFormedPathA(p, p + len, /* hasHost */ false);
868
869
0
  return php_uri_parser_rfc3986_validate_component_result(well_formed, "path");
870
0
}
871
872
ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_query_validate(const zend_string *query)
873
0
{
874
0
  const char *p = ZSTR_VAL(query);
875
0
  const size_t len = ZSTR_LEN(query);
876
0
  const bool well_formed = uriIsWellFormedQueryA(p, p + len);
877
878
0
  return php_uri_parser_rfc3986_validate_component_result(well_formed, "query");
879
0
}
880
881
ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_rfc3986_fragment_validate(const zend_string *fragment)
882
0
{
883
0
  const char *p = ZSTR_VAL(fragment);
884
0
  const size_t len = ZSTR_LEN(fragment);
885
0
  const bool well_formed = uriIsWellFormedFragmentA(p, p + len);
886
887
0
  return php_uri_parser_rfc3986_validate_component_result(well_formed, "fragment");
888
0
}
889
890
ZEND_ATTRIBUTE_NONNULL_ARGS(2,3,4,5,6,7,8) php_uri_parser_rfc3986_uris *php_uri_parser_rfc3986_build_from_zval(
891
  const php_uri_parser_rfc3986_uris *uriparser_base_uris,
892
  const zval *scheme, const zval *userinfo, const zval *host, const zval *port,
893
  const zval *path, const zval *query, const zval *fragment
894
0
) {
895
0
  php_uri_parser_rfc3986_uris *uriparser_uris = uriparser_create_uris();
896
897
0
  if (Z_STRLEN_P(path) > 0) {
898
    /* The first segment of the path must not contain ":" if the URI does not contain a scheme */
899
0
    if (Z_TYPE_P(scheme) == IS_NULL) {
900
0
      const char *p = Z_STRVAL_P(path);
901
0
      while (*p != '\0' && *p != '/') {
902
0
        if (*p == ':') {
903
0
          zend_throw_exception(php_uri_ce_invalid_uri_exception, "The path must not begin with \":\" when the URI does not contain a scheme", 0);
904
0
          goto failure;
905
0
        }
906
907
0
        p++;
908
0
      }
909
0
    }
910
911
    /* The path must not begin with "//" if the URI does not contain a host */
912
0
    if (Z_TYPE_P(host) == IS_NULL && zend_string_starts_with_literal(Z_STR_P(path), "//")) {
913
0
      zend_throw_exception(php_uri_ce_invalid_uri_exception, "The path must not begin with \"//\" when the URI does not contain a host", 0);
914
0
      goto failure;
915
0
    }
916
0
  }
917
918
0
  zend_result result = php_uri_parser_rfc3986_scheme_write(uriparser_uris, scheme, NULL);
919
0
  if (result == FAILURE) {
920
0
    goto failure;
921
0
  }
922
0
  result = php_uri_parser_rfc3986_host_write(uriparser_uris, host, NULL);
923
0
  if (result == FAILURE) {
924
0
    goto failure;
925
0
  }
926
  /* Intentionally writing userinfo after host to avoid error when the userinfo is set but the host is missing */
927
0
  result = php_uri_parser_rfc3986_userinfo_write(uriparser_uris, userinfo, NULL);
928
0
  if (result == FAILURE) {
929
0
    goto failure;
930
0
  }
931
  /* Intentionally writing userinfo after host to avoid error when the port is set but the host is missing */
932
0
  result = php_uri_parser_rfc3986_port_write(uriparser_uris, port, NULL);
933
0
  if (result == FAILURE) {
934
0
    goto failure;
935
0
  }
936
0
  result = php_uri_parser_rfc3986_path_write(uriparser_uris, path, NULL);
937
0
  if (result == FAILURE) {
938
0
    goto failure;
939
0
  }
940
0
  result = php_uri_parser_rfc3986_query_write(uriparser_uris, query, NULL);
941
0
  if (result == FAILURE) {
942
0
    goto failure;
943
0
  }
944
0
  result = php_uri_parser_rfc3986_fragment_write(uriparser_uris, fragment, NULL);
945
0
  if (result == FAILURE) {
946
0
    goto failure;
947
0
  }
948
949
0
  if (uriparser_base_uris != NULL) {
950
0
    UriUriA tmp = {0};
951
952
0
    if (php_uri_parser_rfc3986_add_base_url(&tmp, &uriparser_uris->uri, uriparser_base_uris, false) == FAILURE) {
953
0
      goto failure;
954
0
    }
955
956
0
    uriMakeOwnerMmA(&tmp, mm);
957
0
    uriFreeUriMembersMmA(&uriparser_uris->uri, mm);
958
0
    uriparser_uris->uri = tmp;
959
0
  }
960
961
0
  return uriparser_uris;
962
963
0
failure:
964
0
  uriFreeUriMembersMmA(&uriparser_uris->uri, mm);
965
0
  efree(uriparser_uris);
966
  return NULL;
967
0
}
968
969
PHPAPI const php_uri_parser php_uri_parser_rfc3986 = {
970
  .name = PHP_URI_PARSER_RFC3986,
971
  .parse = php_uri_parser_rfc3986_parse,
972
  .clone = php_uri_parser_rfc3986_clone,
973
  .to_string = php_uri_parser_rfc3986_to_string,
974
  .destroy = php_uri_parser_rfc3986_destroy,
975
  {
976
    .scheme = {.read = php_uri_parser_rfc3986_scheme_read, .write = php_uri_parser_rfc3986_scheme_write},
977
    .username = {.read = php_uri_parser_rfc3986_username_read, .write = NULL},
978
    .password = {.read = php_uri_parser_rfc3986_password_read, .write = NULL},
979
    .host = {.read = php_uri_parser_rfc3986_host_read, .write = php_uri_parser_rfc3986_host_write},
980
    .port = {.read = php_uri_parser_rfc3986_port_read, .write = php_uri_parser_rfc3986_port_write},
981
    .path = {.read = php_uri_parser_rfc3986_path_read, .write = php_uri_parser_rfc3986_path_write},
982
    .query = {.read = php_uri_parser_rfc3986_query_read, .write = php_uri_parser_rfc3986_query_write},
983
    .fragment = {.read = php_uri_parser_rfc3986_fragment_read, .write = php_uri_parser_rfc3986_fragment_write},
984
  }
985
};