Coverage Report

Created: 2026-06-13 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/ext/uri/uri_parser_whatwg.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_whatwg.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
#ifdef HAVE_ARPA_INET_H
22
#include <arpa/inet.h>
23
#endif
24
25
ZEND_TLS lexbor_mraw_t lexbor_mraw = {0};
26
ZEND_TLS lxb_url_parser_t lexbor_parser = {0};
27
ZEND_TLS lxb_unicode_idna_t lexbor_idna = {0};
28
29
static const size_t lexbor_mraw_byte_size = 8192;
30
31
static zend_always_inline void zval_string_or_null_to_lexbor_str(zval *value, lexbor_str_t *lexbor_str)
32
0
{
33
0
  if (Z_TYPE_P(value) == IS_STRING && Z_STRLEN_P(value) > 0) {
34
0
    lexbor_str->data = (lxb_char_t *) Z_STRVAL_P(value);
35
0
    lexbor_str->length = Z_STRLEN_P(value);
36
0
  } else {
37
0
    ZEND_ASSERT(Z_ISNULL_P(value) || (Z_TYPE_P(value) == IS_STRING && Z_STRLEN_P(value) == 0));
38
0
    lexbor_str->data = (lxb_char_t *) "";
39
0
    lexbor_str->length = 0;
40
0
  }
41
0
}
42
43
static zend_always_inline void zval_long_or_null_to_lexbor_str(zval *value, lexbor_str_t *lexbor_str)
44
0
{
45
0
  if (Z_TYPE_P(value) == IS_LONG) {
46
0
    ZVAL_STR(value, zend_long_to_str(Z_LVAL_P(value)));
47
0
    lexbor_str_init_append(lexbor_str, lexbor_parser.mraw, (const lxb_char_t *) Z_STRVAL_P(value), Z_STRLEN_P(value));
48
0
    zval_ptr_dtor_str(value);
49
0
  } else {
50
0
    ZEND_ASSERT(Z_ISNULL_P(value));
51
0
    lexbor_str->data = (lxb_char_t *) "";
52
0
    lexbor_str->length = 0;
53
0
  }
54
0
}
55
56
/**
57
 * Creates a Uri\WhatWg\UrlValidationError class by mapping error codes listed in
58
 * https://url.spec.whatwg.org/#writing to a Uri\WhatWg\UrlValidationErrorType enum.
59
 * The result is passed by reference to the errors parameter.
60
 */
61
static const char *fill_errors(zval *errors)
62
0
{
63
0
  size_t log_len;
64
0
  if (lexbor_parser.log == NULL || (log_len = lexbor_plog_length(lexbor_parser.log)) == 0) {
65
0
    ZVAL_EMPTY_ARRAY(errors);
66
0
    return NULL;
67
0
  }
68
69
0
  array_init_size(errors, log_len);
70
0
  const char *result = NULL;
71
72
0
  lexbor_plog_entry_t *lxb_error;
73
0
  while ((lxb_error = lexbor_array_obj_pop(&lexbor_parser.log->list)) != NULL) {
74
0
    zval error;
75
0
    object_init_ex(&error, php_uri_ce_whatwg_url_validation_error);
76
0
    zend_update_property_string(php_uri_ce_whatwg_url_validation_error, Z_OBJ(error), ZEND_STRL("context"), (const char *) lxb_error->data);
77
78
0
    const char *error_str;
79
0
    zval failure;
80
0
    switch (lxb_error->id) {
81
0
      case LXB_URL_ERROR_TYPE_DOMAIN_TO_ASCII:
82
0
        error_str = "DomainToAscii";
83
0
        ZVAL_TRUE(&failure);
84
0
        break;
85
0
      case LXB_URL_ERROR_TYPE_DOMAIN_TO_UNICODE:
86
0
        error_str = "DomainToUnicode";
87
0
        ZVAL_FALSE(&failure);
88
0
        break;
89
0
      case LXB_URL_ERROR_TYPE_DOMAIN_INVALID_CODE_POINT:
90
0
        error_str = "DomainInvalidCodePoint";
91
0
        ZVAL_TRUE(&failure);
92
0
        break;
93
0
      case LXB_URL_ERROR_TYPE_HOST_INVALID_CODE_POINT:
94
0
        error_str = "HostInvalidCodePoint";
95
0
        ZVAL_TRUE(&failure);
96
0
        break;
97
0
      case LXB_URL_ERROR_TYPE_IPV4_EMPTY_PART:
98
0
        error_str = "Ipv4EmptyPart";
99
0
        ZVAL_FALSE(&failure);
100
0
        break;
101
0
      case LXB_URL_ERROR_TYPE_IPV4_TOO_MANY_PARTS:
102
0
        error_str = "Ipv4TooManyParts";
103
0
        ZVAL_TRUE(&failure);
104
0
        break;
105
0
      case LXB_URL_ERROR_TYPE_IPV4_NON_NUMERIC_PART:
106
0
        error_str = "Ipv4NonNumericPart";
107
0
        ZVAL_TRUE(&failure);
108
0
        break;
109
0
      case LXB_URL_ERROR_TYPE_IPV4_NON_DECIMAL_PART:
110
0
        error_str = "Ipv4NonDecimalPart";
111
0
        ZVAL_FALSE(&failure);
112
0
        break;
113
0
      case LXB_URL_ERROR_TYPE_IPV4_OUT_OF_RANGE_PART:
114
0
        error_str = "Ipv4OutOfRangePart";
115
0
        ZVAL_TRUE(&failure);
116
0
        break;
117
0
      case LXB_URL_ERROR_TYPE_IPV6_UNCLOSED:
118
0
        error_str = "Ipv6Unclosed";
119
0
        ZVAL_TRUE(&failure);
120
0
        break;
121
0
      case LXB_URL_ERROR_TYPE_IPV6_INVALID_COMPRESSION:
122
0
        error_str = "Ipv6InvalidCompression";
123
0
        ZVAL_TRUE(&failure);
124
0
        break;
125
0
      case LXB_URL_ERROR_TYPE_IPV6_TOO_MANY_PIECES:
126
0
        error_str = "Ipv6TooManyPieces";
127
0
        ZVAL_TRUE(&failure);
128
0
        break;
129
0
      case LXB_URL_ERROR_TYPE_IPV6_MULTIPLE_COMPRESSION:
130
0
        error_str = "Ipv6MultipleCompression";
131
0
        ZVAL_TRUE(&failure);
132
0
        break;
133
0
      case LXB_URL_ERROR_TYPE_IPV6_INVALID_CODE_POINT:
134
0
        error_str = "Ipv6InvalidCodePoint";
135
0
        ZVAL_TRUE(&failure);
136
0
        break;
137
0
      case LXB_URL_ERROR_TYPE_IPV6_TOO_FEW_PIECES:
138
0
        error_str = "Ipv6TooFewPieces";
139
0
        ZVAL_TRUE(&failure);
140
0
        break;
141
0
      case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_TOO_MANY_PIECES:
142
0
        error_str = "Ipv4InIpv6TooManyPieces";
143
0
        ZVAL_TRUE(&failure);
144
0
        break;
145
0
      case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_INVALID_CODE_POINT:
146
0
        error_str = "Ipv4InIpv6InvalidCodePoint";
147
0
        ZVAL_TRUE(&failure);
148
0
        break;
149
0
      case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_OUT_OF_RANGE_PART:
150
0
        error_str = "Ipv4InIpv6OutOfRangePart";
151
0
        ZVAL_TRUE(&failure);
152
0
        break;
153
0
      case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_TOO_FEW_PARTS:
154
0
        error_str = "Ipv4InIpv6TooFewParts";
155
0
        ZVAL_TRUE(&failure);
156
0
        break;
157
0
      case LXB_URL_ERROR_TYPE_INVALID_URL_UNIT:
158
0
        error_str = "InvalidUrlUnit";
159
0
        ZVAL_FALSE(&failure);
160
0
        break;
161
0
      case LXB_URL_ERROR_TYPE_SPECIAL_SCHEME_MISSING_FOLLOWING_SOLIDUS:
162
0
        error_str = "SpecialSchemeMissingFollowingSolidus";
163
0
        ZVAL_FALSE(&failure);
164
0
        break;
165
0
      case LXB_URL_ERROR_TYPE_MISSING_SCHEME_NON_RELATIVE_URL:
166
0
        error_str = "MissingSchemeNonRelativeUrl";
167
0
        ZVAL_TRUE(&failure);
168
0
        break;
169
0
      case LXB_URL_ERROR_TYPE_INVALID_REVERSE_SOLIDUS:
170
0
        error_str = "InvalidReverseSoldius";
171
0
        ZVAL_FALSE(&failure);
172
0
        break;
173
0
      case LXB_URL_ERROR_TYPE_INVALID_CREDENTIALS:
174
0
        error_str = "InvalidCredentials";
175
0
        ZVAL_FALSE(&failure);
176
0
        break;
177
0
      case LXB_URL_ERROR_TYPE_HOST_MISSING:
178
0
        error_str = "HostMissing";
179
0
        ZVAL_TRUE(&failure);
180
0
        break;
181
0
      case LXB_URL_ERROR_TYPE_PORT_OUT_OF_RANGE:
182
0
        error_str = "PortOutOfRange";
183
0
        ZVAL_TRUE(&failure);
184
0
        break;
185
0
      case LXB_URL_ERROR_TYPE_PORT_INVALID:
186
0
        error_str = "PortInvalid";
187
0
        ZVAL_TRUE(&failure);
188
0
        break;
189
0
      case LXB_URL_ERROR_TYPE_FILE_INVALID_WINDOWS_DRIVE_LETTER:
190
0
        error_str = "FileInvalidWindowsDriveLetter";
191
0
        ZVAL_FALSE(&failure);
192
0
        break;
193
0
      case LXB_URL_ERROR_TYPE_FILE_INVALID_WINDOWS_DRIVE_LETTER_HOST:
194
0
        error_str = "FileInvalidWindowsDriveLetterHost";
195
0
        ZVAL_FALSE(&failure);
196
0
        break;
197
0
      default: ZEND_UNREACHABLE();
198
0
    }
199
200
0
    zval error_type;
201
0
    ZVAL_OBJ(&error_type, zend_enum_get_case_cstr(php_uri_ce_whatwg_url_validation_error_type, error_str));
202
0
    zend_update_property_ex(php_uri_ce_whatwg_url_validation_error, Z_OBJ(error), ZSTR_KNOWN(ZEND_STR_TYPE), &error_type);
203
204
0
    zend_update_property(php_uri_ce_whatwg_url_validation_error, Z_OBJ(error), ZEND_STRL("failure"), &failure);
205
206
0
    if (Z_TYPE(failure) == IS_TRUE) {
207
0
      result = error_str;
208
0
    }
209
210
0
    add_next_index_zval(errors, &error);
211
0
  }
212
213
0
  return result;
214
0
}
215
216
static void throw_invalid_url_exception_during_write(zval *errors, const char *component)
217
0
{
218
0
  zval err;
219
0
  const char *reason = fill_errors(&err);
220
0
  zend_object *exception = zend_throw_exception_ex(
221
0
    php_uri_ce_whatwg_invalid_url_exception,
222
0
    0,
223
0
    "The specified %s is malformed%s%s%s",
224
0
    component,
225
0
    reason ? " (" : "",
226
0
    reason ? reason : "",
227
0
    reason ? ")" : ""
228
0
  );
229
0
  zend_update_property(exception->ce, exception, ZEND_STRL("errors"), &err);
230
0
  if (errors) {
231
0
    zval_ptr_dtor(errors);
232
0
    ZVAL_COPY_VALUE(errors, &err);
233
0
  } else {
234
0
    zval_ptr_dtor(&err);
235
0
  }
236
0
}
237
238
static lxb_status_t serialize_to_smart_str_callback(const lxb_char_t *data, size_t length, void *ctx)
239
0
{
240
0
  smart_str *uri_str = ctx;
241
242
0
  if (data != NULL && length > 0) {
243
0
    smart_str_appendl(uri_str, (const char *) data, length);
244
0
  }
245
246
0
  return LXB_STATUS_OK;
247
0
}
248
249
static zend_result php_uri_parser_whatwg_scheme_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
250
0
{
251
0
  const lxb_url_t *lexbor_uri = uri;
252
253
0
  ZEND_ASSERT(lexbor_uri->scheme.type != LXB_URL_SCHEMEL_TYPE__UNDEF);
254
255
0
  ZVAL_STRINGL(retval, (const char *) lexbor_uri->scheme.name.data, lexbor_uri->scheme.name.length);
256
257
0
  return SUCCESS;
258
0
}
259
260
static zend_result php_uri_parser_whatwg_scheme_write(void *uri, zval *value, zval *errors)
261
0
{
262
0
  lxb_url_t *lexbor_uri = uri;
263
0
  lexbor_str_t str = {0};
264
265
0
  zval_string_or_null_to_lexbor_str(value, &str);
266
267
0
  lxb_url_parser_clean(&lexbor_parser);
268
269
0
  if (lxb_url_api_protocol_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
270
0
    throw_invalid_url_exception_during_write(errors, "scheme");
271
272
0
    return FAILURE;
273
0
  }
274
275
0
  return SUCCESS;
276
0
}
277
278
ZEND_ATTRIBUTE_NONNULL bool php_uri_parser_whatwg_is_special(const lxb_url_t *lexbor_uri)
279
0
{
280
0
  return lxb_url_is_special(lexbor_uri);
281
0
}
282
283
/* 4.2. URL miscellaneous: A URL includes credentials if its username or password is not the empty string. */
284
static bool includes_credentials(const lxb_url_t *lexbor_uri)
285
0
{
286
0
  return lexbor_uri->username.length > 0 || lexbor_uri->password.length > 0;
287
0
}
288
289
static zend_result php_uri_parser_whatwg_username_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
290
0
{
291
0
  const lxb_url_t *lexbor_uri = uri;
292
293
0
  if (includes_credentials(lexbor_uri)) {
294
0
    ZVAL_STRINGL_FAST(retval, (const char *) lexbor_uri->username.data, lexbor_uri->username.length);
295
0
  } else {
296
0
    ZVAL_NULL(retval);
297
0
  }
298
299
0
  return SUCCESS;
300
0
}
301
302
static zend_result php_uri_parser_whatwg_username_write(void *uri, zval *value, zval *errors)
303
0
{
304
0
  lxb_url_t *lexbor_uri = uri;
305
0
  lexbor_str_t str = {0};
306
307
0
  zval_string_or_null_to_lexbor_str(value, &str);
308
309
0
  lxb_url_parser_clean(&lexbor_parser);
310
311
0
  if (lxb_url_api_username_set(lexbor_uri, str.data, str.length) != LXB_STATUS_OK) {
312
0
    throw_invalid_url_exception_during_write(errors, "username");
313
314
0
    return FAILURE;
315
0
  }
316
317
0
  return SUCCESS;
318
0
}
319
320
static zend_result php_uri_parser_whatwg_password_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
321
0
{
322
0
  const lxb_url_t *lexbor_uri = uri;
323
324
0
  if (includes_credentials(lexbor_uri)) {
325
0
    ZVAL_STRINGL_FAST(retval, (const char *) lexbor_uri->password.data, lexbor_uri->password.length);
326
0
  } else {
327
0
    ZVAL_NULL(retval);
328
0
  }
329
330
0
  return SUCCESS;
331
0
}
332
333
static zend_result php_uri_parser_whatwg_password_write(void *uri, zval *value, zval *errors)
334
0
{
335
0
  lxb_url_t *lexbor_uri = uri;
336
0
  lexbor_str_t str = {0};
337
338
0
  zval_string_or_null_to_lexbor_str(value, &str);
339
340
0
  lxb_url_parser_clean(&lexbor_parser);
341
342
0
  if (lxb_url_api_password_set(lexbor_uri, str.data, str.length) != LXB_STATUS_OK) {
343
0
    throw_invalid_url_exception_during_write(errors, "password");
344
345
0
    return FAILURE;
346
0
  }
347
348
0
  return SUCCESS;
349
0
}
350
351
static zend_result php_uri_parser_whatwg_host_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
352
0
{
353
0
  const lxb_url_t *lexbor_uri = uri;
354
355
0
  if (lexbor_uri->host.type == LXB_URL_HOST_TYPE_IPV4) {
356
0
    smart_str host_str = {0};
357
358
0
    lxb_url_serialize_host_ipv4(lexbor_uri->host.u.ipv4, serialize_to_smart_str_callback, &host_str);
359
360
0
    ZVAL_NEW_STR(retval, smart_str_extract(&host_str));
361
0
  } else if (lexbor_uri->host.type == LXB_URL_HOST_TYPE_IPV6) {
362
0
    smart_str host_str = {0};
363
364
0
    smart_str_appendc(&host_str, '[');
365
0
    lxb_url_serialize_host_ipv6(lexbor_uri->host.u.ipv6, serialize_to_smart_str_callback, &host_str);
366
0
    smart_str_appendc(&host_str, ']');
367
368
0
    ZVAL_NEW_STR(retval, smart_str_extract(&host_str));
369
0
  } else if (lexbor_uri->host.type == LXB_URL_HOST_TYPE_EMPTY) {
370
0
    ZVAL_EMPTY_STRING(retval);
371
0
  } else if (lexbor_uri->host.type != LXB_URL_HOST_TYPE__UNDEF) {
372
0
    switch (read_mode) {
373
0
      case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_UNICODE: {
374
0
        smart_str host_str = {0};
375
0
        lxb_url_serialize_host_unicode(&lexbor_idna, &lexbor_uri->host, serialize_to_smart_str_callback, &host_str);
376
0
        lxb_unicode_idna_clean(&lexbor_idna);
377
378
0
        ZVAL_STR(retval, smart_str_extract(&host_str));
379
0
        break;
380
0
      }
381
0
      case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII:
382
0
        ZEND_FALLTHROUGH;
383
0
      case PHP_URI_COMPONENT_READ_MODE_RAW:
384
0
        ZVAL_STRINGL(retval, (const char *) lexbor_uri->host.u.domain.data, lexbor_uri->host.u.domain.length);
385
0
        break;
386
0
      default: ZEND_UNREACHABLE();
387
0
    }
388
0
  } else {
389
0
    ZVAL_NULL(retval);
390
0
  }
391
392
0
  return SUCCESS;
393
0
}
394
395
ZEND_ATTRIBUTE_NONNULL void php_uri_parser_whatwg_host_type_read(const lxb_url_t *lexbor_uri, zval *retval)
396
0
{
397
0
  switch (lexbor_uri->host.type) {
398
0
    case LXB_URL_HOST_TYPE_IPV4:
399
0
      ZVAL_OBJ_COPY(retval, zend_enum_get_case_cstr(php_uri_ce_whatwg_url_host_type, "IPv4"));
400
0
      return;
401
0
    case LXB_URL_HOST_TYPE_IPV6:
402
0
      ZVAL_OBJ_COPY(retval, zend_enum_get_case_cstr(php_uri_ce_whatwg_url_host_type, "IPv6"));
403
0
      return;
404
0
    case LXB_URL_HOST_TYPE_DOMAIN:
405
0
      ZVAL_OBJ_COPY(retval, zend_enum_get_case_cstr(php_uri_ce_whatwg_url_host_type, "Domain"));
406
0
      return;
407
0
    case LXB_URL_HOST_TYPE_EMPTY:
408
0
      ZVAL_OBJ_COPY(retval, zend_enum_get_case_cstr(php_uri_ce_whatwg_url_host_type, "Empty"));
409
0
      return;
410
0
    case LXB_URL_HOST_TYPE_OPAQUE:
411
0
      ZVAL_OBJ_COPY(retval, zend_enum_get_case_cstr(php_uri_ce_whatwg_url_host_type, "Opaque"));
412
0
      return;
413
0
    case LXB_URL_HOST_TYPE__UNDEF:
414
0
      ZVAL_NULL(retval);
415
0
      return;
416
0
    default: ZEND_UNREACHABLE();
417
0
  }
418
0
}
419
420
static zend_result php_uri_parser_whatwg_host_write(void *uri, zval *value, zval *errors)
421
0
{
422
0
  lxb_url_t *lexbor_uri = uri;
423
0
  lexbor_str_t str = {0};
424
425
0
  zval_string_or_null_to_lexbor_str(value, &str);
426
427
0
  lxb_url_parser_clean(&lexbor_parser);
428
429
0
  if (lxb_url_api_hostname_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
430
0
    throw_invalid_url_exception_during_write(errors, "host");
431
432
0
    return FAILURE;
433
0
  }
434
435
0
  return SUCCESS;
436
0
}
437
438
static zend_result php_uri_parser_whatwg_port_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
439
0
{
440
0
  const lxb_url_t *lexbor_uri = uri;
441
442
0
  if (lexbor_uri->has_port) {
443
0
    ZVAL_LONG(retval, lexbor_uri->port);
444
0
  } else {
445
0
    ZVAL_NULL(retval);
446
0
  }
447
448
0
  return SUCCESS;
449
0
}
450
451
static zend_result php_uri_parser_whatwg_port_write(void *uri, zval *value, zval *errors)
452
0
{
453
0
  lxb_url_t *lexbor_uri = uri;
454
0
  lexbor_str_t str = {0};
455
456
0
  zval_long_or_null_to_lexbor_str(value, &str);
457
458
0
  lxb_url_parser_clean(&lexbor_parser);
459
460
0
  if (lxb_url_api_port_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
461
0
    throw_invalid_url_exception_during_write(errors, "port");
462
463
0
    return FAILURE;
464
0
  }
465
466
0
  return SUCCESS;
467
0
}
468
469
static zend_result php_uri_parser_whatwg_path_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
470
0
{
471
0
  const lxb_url_t *lexbor_uri = uri;
472
473
0
  if (lexbor_uri->path.str.length > 0) {
474
0
    ZVAL_STRINGL(retval, (const char *) lexbor_uri->path.str.data, lexbor_uri->path.str.length);
475
0
  } else {
476
0
    ZVAL_EMPTY_STRING(retval);
477
0
  }
478
479
0
  return SUCCESS;
480
0
}
481
482
static zend_result php_uri_parser_whatwg_path_write(void *uri, zval *value, zval *errors)
483
0
{
484
0
  lxb_url_t *lexbor_uri = uri;
485
0
  lexbor_str_t str = {0};
486
487
0
  zval_string_or_null_to_lexbor_str(value, &str);
488
489
0
  lxb_url_parser_clean(&lexbor_parser);
490
491
0
  if (lxb_url_api_pathname_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
492
0
    throw_invalid_url_exception_during_write(errors, "path");
493
494
0
    return FAILURE;
495
0
  }
496
497
0
  return SUCCESS;
498
0
}
499
500
static zend_result php_uri_parser_whatwg_query_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
501
0
{
502
0
  const lxb_url_t *lexbor_uri = uri;
503
504
0
  if (lexbor_uri->query.data != NULL) {
505
0
    ZVAL_STRINGL(retval, (const char *) lexbor_uri->query.data, lexbor_uri->query.length);
506
0
  } else {
507
0
    ZVAL_NULL(retval);
508
0
  }
509
510
0
  return SUCCESS;
511
0
}
512
513
static zend_result php_uri_parser_whatwg_query_write(void *uri, zval *value, zval *errors)
514
0
{
515
0
  lxb_url_t *lexbor_uri = uri;
516
0
  lexbor_str_t str = {0};
517
518
0
  zval_string_or_null_to_lexbor_str(value, &str);
519
520
0
  lxb_url_parser_clean(&lexbor_parser);
521
522
0
  if (lxb_url_api_search_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
523
0
    throw_invalid_url_exception_during_write(errors, "query string");
524
525
0
    return FAILURE;
526
0
  }
527
528
0
  return SUCCESS;
529
0
}
530
531
static zend_result php_uri_parser_whatwg_fragment_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
532
0
{
533
0
  const lxb_url_t *lexbor_uri = uri;
534
535
0
  if (lexbor_uri->fragment.data != NULL) {
536
0
    ZVAL_STRINGL(retval, (const char *) lexbor_uri->fragment.data, lexbor_uri->fragment.length);
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_whatwg_fragment_write(void *uri, zval *value, zval *errors)
545
0
{
546
0
  lxb_url_t *lexbor_uri = uri;
547
0
  lexbor_str_t str = {0};
548
549
0
  zval_string_or_null_to_lexbor_str(value, &str);
550
551
0
  lxb_url_parser_clean(&lexbor_parser);
552
553
0
  if (lxb_url_api_hash_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
554
0
    throw_invalid_url_exception_during_write(errors, "fragment");
555
556
0
    return FAILURE;
557
0
  }
558
559
0
  return SUCCESS;
560
0
}
561
562
PHP_RINIT_FUNCTION(uri_parser_whatwg)
563
229k
{
564
229k
  lxb_status_t status;
565
  
566
229k
  status = lexbor_mraw_init(&lexbor_mraw, lexbor_mraw_byte_size);
567
229k
  if (status != LXB_STATUS_OK) {
568
0
    goto fail;
569
0
  }
570
571
229k
  status = lxb_url_parser_init(&lexbor_parser, &lexbor_mraw);
572
229k
  if (status != LXB_STATUS_OK) {
573
0
    goto fail;
574
0
  }
575
576
229k
  status = lxb_unicode_idna_init(&lexbor_idna);
577
229k
  if (status != LXB_STATUS_OK) {
578
0
    goto fail;
579
0
  }
580
581
229k
  return SUCCESS;
582
583
0
 fail:
584
585
  /* Unconditionally calling the _destroy() functions is
586
   * safe on a zeroed structure. */
587
0
  lxb_unicode_idna_destroy(&lexbor_idna, false);
588
0
  memset(&lexbor_idna, 0, sizeof(lexbor_idna));
589
0
  lxb_url_parser_destroy(&lexbor_parser, false);
590
0
  memset(&lexbor_parser, 0, sizeof(lexbor_parser));
591
0
  lexbor_mraw_destroy(&lexbor_mraw, false);
592
0
  memset(&lexbor_mraw, 0, sizeof(lexbor_mraw));
593
594
0
  return FAILURE;
595
229k
}
596
597
ZEND_MODULE_POST_ZEND_DEACTIVATE_D(uri_parser_whatwg)
598
229k
{
599
229k
  lxb_unicode_idna_destroy(&lexbor_idna, false);
600
229k
  memset(&lexbor_idna, 0, sizeof(lexbor_idna));
601
229k
  lxb_url_parser_destroy(&lexbor_parser, false);
602
229k
  memset(&lexbor_parser, 0, sizeof(lexbor_parser));
603
229k
  lexbor_mraw_destroy(&lexbor_mraw, false);
604
229k
  memset(&lexbor_mraw, 0, sizeof(lexbor_mraw));
605
606
229k
  return SUCCESS;
607
229k
}
608
609
lxb_url_t *php_uri_parser_whatwg_parse_ex(const char *uri_str, size_t uri_str_len, const lxb_url_t *lexbor_base_url, zval *errors, bool silent)
610
0
{
611
0
  lxb_url_parser_clean(&lexbor_parser);
612
613
0
  lxb_url_t *url = lxb_url_parse(&lexbor_parser, lexbor_base_url, (unsigned char *) uri_str, uri_str_len);
614
615
0
  if ((url == NULL && !silent) || errors != NULL) {
616
0
    zval err;
617
0
    const char *reason = fill_errors(&err);
618
0
    if (url == NULL && !silent) {
619
0
      zend_object *exception = zend_throw_exception_ex(php_uri_ce_whatwg_invalid_url_exception, 0, "The specified URI is malformed%s%s%s", reason ? " (" : "", reason ? reason : "", reason ? ")" : "");
620
0
      zend_update_property(exception->ce, exception, ZEND_STRL("errors"), &err);
621
0
    }
622
0
    if (errors != NULL) {
623
0
      zval_ptr_dtor(errors);
624
0
      ZVAL_COPY_VALUE(errors, &err);
625
0
    } else {
626
0
      zval_ptr_dtor(&err);
627
0
    }
628
0
  }
629
630
0
  return url;
631
0
}
632
633
static void *php_uri_parser_whatwg_parse(const char *uri_str, size_t uri_str_len, const void *base_url, zval *errors, bool silent)
634
0
{
635
0
  return php_uri_parser_whatwg_parse_ex(uri_str, uri_str_len, base_url, errors, silent);
636
0
}
637
638
static void *php_uri_parser_whatwg_clone(void *uri)
639
0
{
640
0
  const lxb_url_t *lexbor_uri = uri;
641
642
0
  return lxb_url_clone(lexbor_parser.mraw, lexbor_uri);
643
0
}
644
645
static zend_string *php_uri_parser_whatwg_to_string(void *uri, php_uri_recomposition_mode recomposition_mode, bool exclude_fragment)
646
0
{
647
0
  const lxb_url_t *lexbor_uri = uri;
648
0
  smart_str uri_str = {0};
649
650
0
  switch (recomposition_mode) {
651
0
    case PHP_URI_RECOMPOSITION_MODE_RAW_UNICODE:
652
0
      ZEND_FALLTHROUGH;
653
0
    case PHP_URI_RECOMPOSITION_MODE_NORMALIZED_UNICODE:
654
0
      lxb_url_serialize_idna(&lexbor_idna, lexbor_uri, serialize_to_smart_str_callback, &uri_str, exclude_fragment);
655
0
      lxb_unicode_idna_clean(&lexbor_idna);
656
0
      break;
657
0
    case PHP_URI_RECOMPOSITION_MODE_RAW_ASCII:
658
0
      ZEND_FALLTHROUGH;
659
0
    case PHP_URI_RECOMPOSITION_MODE_NORMALIZED_ASCII:
660
0
      lxb_url_serialize(lexbor_uri, serialize_to_smart_str_callback, &uri_str, exclude_fragment);
661
0
      break;
662
0
    default: ZEND_UNREACHABLE();
663
0
  }
664
665
0
  return smart_str_extract(&uri_str);
666
0
}
667
668
static void php_uri_parser_whatwg_destroy(void *uri)
669
46
{
670
46
  lxb_url_t *lexbor_uri = uri;
671
672
46
  lxb_url_destroy(lexbor_uri);
673
46
}
674
675
PHPAPI const php_uri_parser php_uri_parser_whatwg = {
676
  .name = PHP_URI_PARSER_WHATWG,
677
  .parse = php_uri_parser_whatwg_parse,
678
  .clone = php_uri_parser_whatwg_clone,
679
  .to_string = php_uri_parser_whatwg_to_string,
680
  .destroy = php_uri_parser_whatwg_destroy,
681
  {
682
    .scheme = {.read = php_uri_parser_whatwg_scheme_read, .write = php_uri_parser_whatwg_scheme_write},
683
    .username = {.read = php_uri_parser_whatwg_username_read, .write = php_uri_parser_whatwg_username_write},
684
    .password = {.read = php_uri_parser_whatwg_password_read, .write = php_uri_parser_whatwg_password_write},
685
    .host = {.read = php_uri_parser_whatwg_host_read, .write = php_uri_parser_whatwg_host_write},
686
    .port = {.read = php_uri_parser_whatwg_port_read, .write = php_uri_parser_whatwg_port_write},
687
    .path = {.read = php_uri_parser_whatwg_path_read, .write = php_uri_parser_whatwg_path_write},
688
    .query = {.read = php_uri_parser_whatwg_query_read, .write = php_uri_parser_whatwg_query_write},
689
    .fragment = {.read = php_uri_parser_whatwg_fragment_read, .write = php_uri_parser_whatwg_fragment_write},
690
  }
691
};