Coverage Report

Created: 2026-09-14 06:25

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
ZEND_TLS uint8_t lexbor_custom_url_map[256] = {0};
29
30
static const size_t lexbor_mraw_byte_size = 8192;
31
32
ZEND_ATTRIBUTE_NONNULL static zend_always_inline void zval_string_or_null_to_lexbor_str(const zval *value, lexbor_str_t *lexbor_str)
33
0
{
34
0
  if (Z_TYPE_P(value) == IS_STRING && Z_STRLEN_P(value) > 0) {
35
0
    lexbor_str->data = (lxb_char_t *) Z_STRVAL_P(value);
36
0
    lexbor_str->length = Z_STRLEN_P(value);
37
0
  } else {
38
0
    ZEND_ASSERT(Z_ISNULL_P(value) || (Z_TYPE_P(value) == IS_STRING && Z_STRLEN_P(value) == 0));
39
0
    lexbor_str->data = (lxb_char_t *) "";
40
0
    lexbor_str->length = 0;
41
0
  }
42
0
}
43
44
ZEND_ATTRIBUTE_NONNULL static zend_always_inline void zval_long_or_null_to_lexbor_str(const zval *value, lexbor_str_t *lexbor_str)
45
0
{
46
0
  if (Z_TYPE_P(value) == IS_LONG) {
47
0
    char buf[MAX_LENGTH_OF_LONG + 1];
48
0
    const char *res = zend_print_long_to_buf(buf + sizeof(buf) - 1, Z_LVAL_P(value));
49
0
    lexbor_str_init_append(lexbor_str, lexbor_parser.mraw, (const lxb_char_t *) res, strlen(res));
50
0
  } else {
51
0
    ZEND_ASSERT(Z_ISNULL_P(value));
52
0
    lexbor_str->data = (lxb_char_t *) "";
53
0
    lexbor_str->length = 0;
54
0
  }
55
0
}
56
57
ZEND_ATTRIBUTE_NONNULL static bool get_reason_from_error_type(const lxb_url_error_type_t error_type, const char **error_str)
58
0
{
59
0
  switch (error_type) {
60
0
    case LXB_URL_ERROR_TYPE_DOMAIN_TO_ASCII:
61
0
      *error_str = "DomainToAscii";
62
0
      return true;
63
0
    case LXB_URL_ERROR_TYPE_DOMAIN_TO_UNICODE:
64
0
      *error_str = "DomainToUnicode";
65
0
      return false;
66
0
    case LXB_URL_ERROR_TYPE_DOMAIN_INVALID_CODE_POINT:
67
0
      *error_str = "DomainInvalidCodePoint";
68
0
      return true;
69
0
    case LXB_URL_ERROR_TYPE_HOST_INVALID_CODE_POINT:
70
0
      *error_str = "HostInvalidCodePoint";
71
0
      return true;
72
0
    case LXB_URL_ERROR_TYPE_IPV4_EMPTY_PART:
73
0
      *error_str = "Ipv4EmptyPart";
74
0
      return false;
75
0
    case LXB_URL_ERROR_TYPE_IPV4_TOO_MANY_PARTS:
76
0
      *error_str = "Ipv4TooManyParts";
77
0
      return true;
78
0
    case LXB_URL_ERROR_TYPE_IPV4_NON_NUMERIC_PART:
79
0
      *error_str = "Ipv4NonNumericPart";
80
0
      return true;
81
0
    case LXB_URL_ERROR_TYPE_IPV4_NON_DECIMAL_PART:
82
0
      *error_str = "Ipv4NonDecimalPart";
83
0
      return false;
84
0
    case LXB_URL_ERROR_TYPE_IPV4_OUT_OF_RANGE_PART:
85
0
      *error_str = "Ipv4OutOfRangePart";
86
0
      return true;
87
0
    case LXB_URL_ERROR_TYPE_IPV6_UNCLOSED:
88
0
      *error_str = "Ipv6Unclosed";
89
0
      return true;
90
0
    case LXB_URL_ERROR_TYPE_IPV6_INVALID_COMPRESSION:
91
0
      *error_str = "Ipv6InvalidCompression";
92
0
      return true;
93
0
    case LXB_URL_ERROR_TYPE_IPV6_TOO_MANY_PIECES:
94
0
      *error_str = "Ipv6TooManyPieces";
95
0
      return true;
96
0
    case LXB_URL_ERROR_TYPE_IPV6_MULTIPLE_COMPRESSION:
97
0
      *error_str = "Ipv6MultipleCompression";
98
0
      return true;
99
0
    case LXB_URL_ERROR_TYPE_IPV6_INVALID_CODE_POINT:
100
0
      *error_str = "Ipv6InvalidCodePoint";
101
0
      return true;
102
0
    case LXB_URL_ERROR_TYPE_IPV6_TOO_FEW_PIECES:
103
0
      *error_str = "Ipv6TooFewPieces";
104
0
      return true;
105
0
    case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_TOO_MANY_PIECES:
106
0
      *error_str = "Ipv4InIpv6TooManyPieces";
107
0
      return true;
108
0
    case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_INVALID_CODE_POINT:
109
0
      *error_str = "Ipv4InIpv6InvalidCodePoint";
110
0
      return true;
111
0
    case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_OUT_OF_RANGE_PART:
112
0
      *error_str = "Ipv4InIpv6OutOfRangePart";
113
0
      return true;
114
0
    case LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_TOO_FEW_PARTS:
115
0
      *error_str = "Ipv4InIpv6TooFewParts";
116
0
      return true;
117
0
    case LXB_URL_ERROR_TYPE_INVALID_URL_UNIT:
118
0
      *error_str = "InvalidUrlUnit";
119
0
      return false;
120
0
    case LXB_URL_ERROR_TYPE_SPECIAL_SCHEME_MISSING_FOLLOWING_SOLIDUS:
121
0
      *error_str = "SpecialSchemeMissingFollowingSolidus";
122
0
      return false;
123
0
    case LXB_URL_ERROR_TYPE_MISSING_SCHEME_NON_RELATIVE_URL:
124
0
      *error_str = "MissingSchemeNonRelativeUrl";
125
0
      return true;
126
0
    case LXB_URL_ERROR_TYPE_INVALID_REVERSE_SOLIDUS:
127
0
      *error_str = "InvalidReverseSoldius";
128
0
      return false;
129
0
    case LXB_URL_ERROR_TYPE_INVALID_CREDENTIALS:
130
0
      *error_str = "InvalidCredentials";
131
0
      return false;
132
0
    case LXB_URL_ERROR_TYPE_HOST_MISSING:
133
0
      *error_str = "HostMissing";
134
0
      return true;
135
0
    case LXB_URL_ERROR_TYPE_PORT_OUT_OF_RANGE:
136
0
      *error_str = "PortOutOfRange";
137
0
      return true;
138
0
    case LXB_URL_ERROR_TYPE_PORT_INVALID:
139
0
      *error_str = "PortInvalid";
140
0
      return true;
141
0
    case LXB_URL_ERROR_TYPE_FILE_INVALID_WINDOWS_DRIVE_LETTER:
142
0
      *error_str = "FileInvalidWindowsDriveLetter";
143
0
      return false;
144
0
    case LXB_URL_ERROR_TYPE_FILE_INVALID_WINDOWS_DRIVE_LETTER_HOST:
145
0
      *error_str = "FileInvalidWindowsDriveLetterHost";
146
0
      return false;
147
0
    default: ZEND_UNREACHABLE();
148
0
  }
149
0
}
150
151
/**
152
 * Creates a Uri\WhatWg\UrlValidationError class by mapping error codes listed in
153
 * https://url.spec.whatwg.org/#writing to a Uri\WhatWg\UrlValidationErrorType enum.
154
 * The result is passed by reference to the errors parameter.
155
 */
156
ZEND_ATTRIBUTE_NONNULL static const char *fill_errors_inner(HashTable *errors)
157
0
{
158
0
  const char *result = NULL;
159
160
0
  lexbor_plog_entry_t *lxb_error;
161
0
  while ((lxb_error = lexbor_array_obj_pop(&lexbor_parser.log->list)) != NULL) {
162
0
    zval error;
163
0
    object_init_ex(&error, php_uri_ce_whatwg_url_validation_error);
164
0
    zend_update_property_string(php_uri_ce_whatwg_url_validation_error, Z_OBJ(error), ZEND_STRL("context"), (const char *) lxb_error->data);
165
166
0
    const char *error_str;
167
0
    zval failure;
168
169
0
    ZVAL_BOOL(&failure, get_reason_from_error_type(lxb_error->id, &error_str));
170
171
0
    zval error_type;
172
0
    ZVAL_OBJ(&error_type, zend_enum_get_case_cstr(php_uri_ce_whatwg_url_validation_error_type, error_str));
173
0
    zend_update_property_ex(php_uri_ce_whatwg_url_validation_error, Z_OBJ(error), ZSTR_KNOWN(ZEND_STR_TYPE), &error_type);
174
175
0
    zend_update_property(php_uri_ce_whatwg_url_validation_error, Z_OBJ(error), ZEND_STRL("failure"), &failure);
176
177
0
    if (Z_TYPE(failure) == IS_TRUE) {
178
0
      result = error_str;
179
0
    }
180
181
0
    zend_hash_next_index_insert(errors, &error);
182
0
  }
183
184
0
  return result;
185
0
}
186
187
/**
188
 * Creates a Uri\WhatWg\UrlValidationError class by mapping error codes listed in
189
 * https://url.spec.whatwg.org/#writing to a Uri\WhatWg\UrlValidationErrorType enum.
190
 * The result is passed by reference to the errors parameter.
191
 */
192
ZEND_ATTRIBUTE_NONNULL static const char *fill_errors(zval *errors)
193
0
{
194
0
  size_t log_len;
195
0
  if (lexbor_parser.log == NULL || (log_len = lexbor_plog_length(lexbor_parser.log)) == 0) {
196
0
    ZVAL_EMPTY_ARRAY(errors);
197
0
    return NULL;
198
0
  }
199
200
0
  array_init_size(errors, log_len);
201
202
0
  return fill_errors_inner(Z_ARRVAL_P(errors));
203
0
}
204
205
static void throw_invalid_url_exception_during_write(zval *errors, const char *component)
206
0
{
207
0
  zval err;
208
0
  const char *reason = fill_errors(&err);
209
0
  zend_object *exception = zend_throw_exception_ex(
210
0
    php_uri_ce_whatwg_invalid_url_exception,
211
0
    0,
212
0
    "The specified %s is malformed%s%s%s",
213
0
    component,
214
0
    reason ? " (" : "",
215
0
    reason ? reason : "",
216
0
    reason ? ")" : ""
217
0
  );
218
0
  zend_update_property(exception->ce, exception, ZEND_STRL("errors"), &err);
219
0
  if (errors) {
220
0
    zval_ptr_dtor(errors);
221
0
    ZVAL_COPY_VALUE(errors, &err);
222
0
  } else {
223
0
    zval_ptr_dtor(&err);
224
0
  }
225
0
}
226
227
static lxb_status_t serialize_to_smart_str_callback(const lxb_char_t *data, const size_t length, void *ctx)
228
0
{
229
0
  smart_str *uri_str = ctx;
230
231
0
  if (data != NULL && length > 0) {
232
0
    smart_str_appendl(uri_str, (const char *) data, length);
233
0
  }
234
235
0
  return LXB_STATUS_OK;
236
0
}
237
238
static zend_result php_uri_parser_whatwg_scheme_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval)
239
0
{
240
0
  const lxb_url_t *lexbor_uri = uri;
241
242
0
  ZEND_ASSERT(lexbor_uri->scheme.type != LXB_URL_SCHEMEL_TYPE__UNDEF);
243
244
0
  ZVAL_STRINGL(retval, (const char *) lexbor_uri->scheme.name.data, lexbor_uri->scheme.name.length);
245
246
0
  return SUCCESS;
247
0
}
248
249
static zend_result php_uri_parser_whatwg_scheme_write(void *uri, const zval *value, zval *errors)
250
0
{
251
0
  lxb_url_t *lexbor_uri = uri;
252
0
  lexbor_str_t str = {0};
253
254
0
  zval_string_or_null_to_lexbor_str(value, &str);
255
256
0
  lxb_url_parser_clean(&lexbor_parser);
257
258
0
  if (lxb_url_api_protocol_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
259
0
    throw_invalid_url_exception_during_write(errors, "scheme");
260
261
0
    return FAILURE;
262
0
  }
263
264
0
  return SUCCESS;
265
0
}
266
267
ZEND_ATTRIBUTE_NONNULL bool php_uri_parser_whatwg_is_special(const lxb_url_t *lexbor_uri)
268
0
{
269
0
  return lxb_url_is_special(lexbor_uri);
270
0
}
271
272
/* 4.2. URL miscellaneous: A URL includes credentials if its username or password is not the empty string. */
273
static bool includes_credentials(const lxb_url_t *lexbor_uri)
274
0
{
275
0
  return lexbor_uri->username.length > 0 || lexbor_uri->password.length > 0;
276
0
}
277
278
static zend_result php_uri_parser_whatwg_username_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
279
0
{
280
0
  const lxb_url_t *lexbor_uri = uri;
281
282
0
  if (includes_credentials(lexbor_uri)) {
283
0
    ZVAL_STRINGL_FAST(retval, (const char *) lexbor_uri->username.data, lexbor_uri->username.length);
284
0
  } else {
285
0
    ZVAL_NULL(retval);
286
0
  }
287
288
0
  return SUCCESS;
289
0
}
290
291
static zend_result php_uri_parser_whatwg_username_write(void *uri, const zval *value, zval *errors)
292
0
{
293
0
  lxb_url_t *lexbor_uri = uri;
294
0
  lexbor_str_t str = {0};
295
296
0
  zval_string_or_null_to_lexbor_str(value, &str);
297
298
0
  lxb_url_parser_clean(&lexbor_parser);
299
300
0
  if (lxb_url_api_username_set(lexbor_uri, str.data, str.length) != LXB_STATUS_OK) {
301
0
    throw_invalid_url_exception_during_write(errors, "username");
302
303
0
    return FAILURE;
304
0
  }
305
306
0
  return SUCCESS;
307
0
}
308
309
static zend_result php_uri_parser_whatwg_password_read(void *uri, php_uri_component_read_mode read_mode, zval *retval)
310
0
{
311
0
  const lxb_url_t *lexbor_uri = uri;
312
313
0
  if (includes_credentials(lexbor_uri)) {
314
0
    ZVAL_STRINGL_FAST(retval, (const char *) lexbor_uri->password.data, lexbor_uri->password.length);
315
0
  } else {
316
0
    ZVAL_NULL(retval);
317
0
  }
318
319
0
  return SUCCESS;
320
0
}
321
322
static zend_result php_uri_parser_whatwg_password_write(void *uri, const zval *value, zval *errors)
323
0
{
324
0
  lxb_url_t *lexbor_uri = uri;
325
0
  lexbor_str_t str = {0};
326
327
0
  zval_string_or_null_to_lexbor_str(value, &str);
328
329
0
  lxb_url_parser_clean(&lexbor_parser);
330
331
0
  if (lxb_url_api_password_set(lexbor_uri, str.data, str.length) != LXB_STATUS_OK) {
332
0
    throw_invalid_url_exception_during_write(errors, "password");
333
334
0
    return FAILURE;
335
0
  }
336
337
0
  return SUCCESS;
338
0
}
339
340
static zend_result php_uri_parser_whatwg_host_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval)
341
0
{
342
0
  const lxb_url_t *lexbor_uri = uri;
343
344
0
  if (lexbor_uri->host.type == LXB_URL_HOST_TYPE_IPV4) {
345
0
    smart_str host_str = {0};
346
347
0
    lxb_url_serialize_host_ipv4(lexbor_uri->host.u.ipv4, serialize_to_smart_str_callback, &host_str);
348
349
0
    ZVAL_NEW_STR(retval, smart_str_extract(&host_str));
350
0
  } else if (lexbor_uri->host.type == LXB_URL_HOST_TYPE_IPV6) {
351
0
    smart_str host_str = {0};
352
353
0
    smart_str_appendc(&host_str, '[');
354
0
    lxb_url_serialize_host_ipv6(lexbor_uri->host.u.ipv6, serialize_to_smart_str_callback, &host_str);
355
0
    smart_str_appendc(&host_str, ']');
356
357
0
    ZVAL_NEW_STR(retval, smart_str_extract(&host_str));
358
0
  } else if (lexbor_uri->host.type == LXB_URL_HOST_TYPE_EMPTY) {
359
0
    ZVAL_EMPTY_STRING(retval);
360
0
  } else if (lexbor_uri->host.type != LXB_URL_HOST_TYPE__UNDEF) {
361
0
    switch (read_mode) {
362
0
      case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_UNICODE: {
363
0
        smart_str host_str = {0};
364
0
        lxb_url_serialize_host_unicode(&lexbor_idna, &lexbor_uri->host, serialize_to_smart_str_callback, &host_str);
365
0
        lxb_unicode_idna_clean(&lexbor_idna);
366
367
0
        ZVAL_STR(retval, smart_str_extract(&host_str));
368
0
        break;
369
0
      }
370
0
      case PHP_URI_COMPONENT_READ_MODE_NORMALIZED_ASCII:
371
0
        ZEND_FALLTHROUGH;
372
0
      case PHP_URI_COMPONENT_READ_MODE_RAW:
373
0
        ZVAL_STRINGL(retval, (const char *) lexbor_uri->host.u.domain.data, lexbor_uri->host.u.domain.length);
374
0
        break;
375
0
      default: ZEND_UNREACHABLE();
376
0
    }
377
0
  } else {
378
0
    ZVAL_NULL(retval);
379
0
  }
380
381
0
  return SUCCESS;
382
0
}
383
384
ZEND_ATTRIBUTE_NONNULL void php_uri_parser_whatwg_host_type_read(const lxb_url_t *lexbor_uri, zval *retval)
385
0
{
386
0
  switch (lexbor_uri->host.type) {
387
0
    case LXB_URL_HOST_TYPE_IPV4:
388
0
      ZVAL_OBJ_COPY(retval, zend_enum_get_case_cstr(php_uri_ce_whatwg_url_host_type, "IPv4"));
389
0
      return;
390
0
    case LXB_URL_HOST_TYPE_IPV6:
391
0
      ZVAL_OBJ_COPY(retval, zend_enum_get_case_cstr(php_uri_ce_whatwg_url_host_type, "IPv6"));
392
0
      return;
393
0
    case LXB_URL_HOST_TYPE_DOMAIN:
394
0
      ZVAL_OBJ_COPY(retval, zend_enum_get_case_cstr(php_uri_ce_whatwg_url_host_type, "Domain"));
395
0
      return;
396
0
    case LXB_URL_HOST_TYPE_EMPTY:
397
0
      ZVAL_OBJ_COPY(retval, zend_enum_get_case_cstr(php_uri_ce_whatwg_url_host_type, "Empty"));
398
0
      return;
399
0
    case LXB_URL_HOST_TYPE_OPAQUE:
400
0
      ZVAL_OBJ_COPY(retval, zend_enum_get_case_cstr(php_uri_ce_whatwg_url_host_type, "Opaque"));
401
0
      return;
402
0
    case LXB_URL_HOST_TYPE__UNDEF:
403
0
      ZVAL_NULL(retval);
404
0
      return;
405
0
    default: ZEND_UNREACHABLE();
406
0
  }
407
0
}
408
409
static zend_result php_uri_parser_whatwg_host_write(void *uri, const zval *value, zval *errors)
410
0
{
411
0
  lxb_url_t *lexbor_uri = uri;
412
0
  lexbor_str_t str = {0};
413
414
0
  zval_string_or_null_to_lexbor_str(value, &str);
415
416
0
  lxb_url_parser_clean(&lexbor_parser);
417
418
0
  if (lxb_url_api_hostname_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
419
0
    throw_invalid_url_exception_during_write(errors, "host");
420
421
0
    return FAILURE;
422
0
  }
423
424
0
  return SUCCESS;
425
0
}
426
427
static zend_result php_uri_parser_whatwg_port_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval)
428
0
{
429
0
  const lxb_url_t *lexbor_uri = uri;
430
431
0
  if (lexbor_uri->has_port) {
432
0
    ZVAL_LONG(retval, lexbor_uri->port);
433
0
  } else {
434
0
    ZVAL_NULL(retval);
435
0
  }
436
437
0
  return SUCCESS;
438
0
}
439
440
static zend_result php_uri_parser_whatwg_port_write(void *uri, const zval *value, zval *errors)
441
0
{
442
0
  lxb_url_t *lexbor_uri = uri;
443
0
  lexbor_str_t str = {0};
444
445
0
  zval_long_or_null_to_lexbor_str(value, &str);
446
447
0
  lxb_url_parser_clean(&lexbor_parser);
448
449
0
  if (lxb_url_api_port_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
450
0
    throw_invalid_url_exception_during_write(errors, "port");
451
452
0
    return FAILURE;
453
0
  }
454
455
0
  return SUCCESS;
456
0
}
457
458
static zend_result php_uri_parser_whatwg_path_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval)
459
0
{
460
0
  const lxb_url_t *lexbor_uri = uri;
461
462
0
  if (lexbor_uri->path.str.length > 0) {
463
0
    ZVAL_STRINGL(retval, (const char *) lexbor_uri->path.str.data, lexbor_uri->path.str.length);
464
0
  } else {
465
0
    ZVAL_EMPTY_STRING(retval);
466
0
  }
467
468
0
  return SUCCESS;
469
0
}
470
471
static zend_result php_uri_parser_whatwg_path_write(void *uri, const zval *value, zval *errors)
472
0
{
473
0
  lxb_url_t *lexbor_uri = uri;
474
0
  lexbor_str_t str = {0};
475
476
0
  zval_string_or_null_to_lexbor_str(value, &str);
477
478
0
  lxb_url_parser_clean(&lexbor_parser);
479
480
0
  if (lxb_url_api_pathname_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
481
0
    throw_invalid_url_exception_during_write(errors, "path");
482
483
0
    return FAILURE;
484
0
  }
485
486
0
  return SUCCESS;
487
0
}
488
489
static zend_result php_uri_parser_whatwg_query_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval)
490
0
{
491
0
  const lxb_url_t *lexbor_uri = uri;
492
493
0
  if (lexbor_uri->query.data != NULL) {
494
0
    ZVAL_STRINGL(retval, (const char *) lexbor_uri->query.data, lexbor_uri->query.length);
495
0
  } else {
496
0
    ZVAL_NULL(retval);
497
0
  }
498
499
0
  return SUCCESS;
500
0
}
501
502
static zend_result php_uri_parser_whatwg_query_write(void *uri, const zval *value, zval *errors)
503
0
{
504
0
  lxb_url_t *lexbor_uri = uri;
505
0
  lexbor_str_t str = {0};
506
507
0
  zval_string_or_null_to_lexbor_str(value, &str);
508
509
0
  lxb_url_parser_clean(&lexbor_parser);
510
511
0
  if (lxb_url_api_search_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
512
0
    throw_invalid_url_exception_during_write(errors, "query string");
513
514
0
    return FAILURE;
515
0
  }
516
517
0
  return SUCCESS;
518
0
}
519
520
static zend_result php_uri_parser_whatwg_fragment_read(void *uri, const php_uri_component_read_mode read_mode, zval *retval)
521
0
{
522
0
  const lxb_url_t *lexbor_uri = uri;
523
524
0
  if (lexbor_uri->fragment.data != NULL) {
525
0
    ZVAL_STRINGL(retval, (const char *) lexbor_uri->fragment.data, lexbor_uri->fragment.length);
526
0
  } else {
527
0
    ZVAL_NULL(retval);
528
0
  }
529
530
0
  return SUCCESS;
531
0
}
532
533
static zend_result php_uri_parser_whatwg_fragment_write(void *uri, const zval *value, zval *errors)
534
0
{
535
0
  lxb_url_t *lexbor_uri = uri;
536
0
  lexbor_str_t str = {0};
537
538
0
  zval_string_or_null_to_lexbor_str(value, &str);
539
540
0
  lxb_url_parser_clean(&lexbor_parser);
541
542
0
  if (lxb_url_api_hash_set(lexbor_uri, &lexbor_parser, str.data, str.length) != LXB_STATUS_OK) {
543
0
    throw_invalid_url_exception_during_write(errors, "fragment");
544
545
0
    return FAILURE;
546
0
  }
547
548
0
  return SUCCESS;
549
0
}
550
551
PHP_RINIT_FUNCTION(uri_parser_whatwg)
552
295k
{
553
295k
  lxb_status_t status = lexbor_mraw_init(&lexbor_mraw, lexbor_mraw_byte_size);
554
295k
  if (status != LXB_STATUS_OK) {
555
0
    goto fail;
556
0
  }
557
558
295k
  status = lxb_url_parser_init(&lexbor_parser, &lexbor_mraw);
559
295k
  if (status != LXB_STATUS_OK) {
560
0
    goto fail;
561
0
  }
562
563
295k
  status = lxb_unicode_idna_init(&lexbor_idna);
564
295k
  if (status != LXB_STATUS_OK) {
565
0
    goto fail;
566
0
  }
567
568
295k
  memcpy(lexbor_custom_url_map, lxb_url_get_percent_encoding_map(), sizeof(lexbor_custom_url_map));
569
295k
  lexbor_custom_url_map['%'] = -1; /* % is percent-encoded */
570
571
295k
  return SUCCESS;
572
573
0
 fail:
574
575
  /* Unconditionally calling the _destroy() functions is
576
   * safe on a zeroed structure. */
577
0
  lxb_unicode_idna_destroy(&lexbor_idna, false);
578
0
  memset(&lexbor_idna, 0, sizeof(lexbor_idna));
579
0
  lxb_url_parser_destroy(&lexbor_parser, false);
580
0
  memset(&lexbor_parser, 0, sizeof(lexbor_parser));
581
0
  lexbor_mraw_destroy(&lexbor_mraw, false);
582
0
  memset(&lexbor_mraw, 0, sizeof(lexbor_mraw));
583
584
0
  return FAILURE;
585
295k
}
586
587
ZEND_MODULE_POST_ZEND_DEACTIVATE_D(uri_parser_whatwg)
588
295k
{
589
295k
  lxb_unicode_idna_destroy(&lexbor_idna, false);
590
295k
  memset(&lexbor_idna, 0, sizeof(lexbor_idna));
591
295k
  lxb_url_parser_destroy(&lexbor_parser, false);
592
295k
  memset(&lexbor_parser, 0, sizeof(lexbor_parser));
593
295k
  lexbor_mraw_destroy(&lexbor_mraw, false);
594
295k
  memset(&lexbor_mraw, 0, sizeof(lexbor_mraw));
595
596
295k
  return SUCCESS;
597
295k
}
598
599
lxb_url_t *php_uri_parser_whatwg_parse_ex(const char *uri_str, const size_t uri_str_len, const lxb_url_t *lexbor_base_url, zval *errors, const bool silent)
600
0
{
601
0
  lxb_url_parser_clean(&lexbor_parser);
602
603
0
  lxb_url_t *url = lxb_url_parse(&lexbor_parser, lexbor_base_url, (unsigned char *) uri_str, uri_str_len);
604
605
0
  if ((url == NULL && !silent) || errors != NULL) {
606
0
    zval err;
607
0
    const char *reason = fill_errors(&err);
608
0
    if (url == NULL && !silent) {
609
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 ? ")" : "");
610
0
      zend_update_property(exception->ce, exception, ZEND_STRL("errors"), &err);
611
0
    }
612
0
    if (errors != NULL) {
613
0
      zval_ptr_dtor(errors);
614
0
      ZVAL_COPY_VALUE(errors, &err);
615
0
    } else {
616
0
      zval_ptr_dtor(&err);
617
0
    }
618
0
  }
619
620
0
  return url;
621
0
}
622
623
static void *php_uri_parser_whatwg_parse(const char *uri_str, const size_t uri_str_len, const void *base_url, zval *errors, const bool silent)
624
0
{
625
0
  return php_uri_parser_whatwg_parse_ex(uri_str, uri_str_len, base_url, errors, silent);
626
0
}
627
628
static void *php_uri_parser_whatwg_clone(void *uri)
629
0
{
630
0
  const lxb_url_t *lexbor_uri = uri;
631
632
0
  return lxb_url_clone(lexbor_parser.mraw, lexbor_uri);
633
0
}
634
635
static zend_string *php_uri_parser_whatwg_to_string(void *uri, const php_uri_recomposition_mode recomposition_mode, const bool exclude_fragment)
636
0
{
637
0
  const lxb_url_t *lexbor_uri = uri;
638
0
  smart_str uri_str = {0};
639
640
0
  switch (recomposition_mode) {
641
0
    case PHP_URI_RECOMPOSITION_MODE_RAW_UNICODE:
642
0
      ZEND_FALLTHROUGH;
643
0
    case PHP_URI_RECOMPOSITION_MODE_NORMALIZED_UNICODE:
644
0
      lxb_url_serialize_idna(&lexbor_idna, lexbor_uri, serialize_to_smart_str_callback, &uri_str, exclude_fragment);
645
0
      lxb_unicode_idna_clean(&lexbor_idna);
646
0
      break;
647
0
    case PHP_URI_RECOMPOSITION_MODE_RAW_ASCII:
648
0
      ZEND_FALLTHROUGH;
649
0
    case PHP_URI_RECOMPOSITION_MODE_NORMALIZED_ASCII:
650
0
      lxb_url_serialize(lexbor_uri, serialize_to_smart_str_callback, &uri_str, exclude_fragment);
651
0
      break;
652
0
    default: ZEND_UNREACHABLE();
653
0
  }
654
655
0
  return smart_str_extract(&uri_str);
656
0
}
657
658
static zend_string *php_uri_parser_whatwg_percent_encode_component(const char *str, const size_t str_length, const lxb_url_map_type_t map, const bool space_as_plus)
659
0
{
660
0
  lexbor_str_t lexbor_str = {0};
661
662
0
  const lexbor_status_t status = lxb_url_percent_encode_utf_8(
663
0
    (lxb_char_t *) str, str_length, &lexbor_str, lexbor_parser.mraw, lexbor_custom_url_map, map, space_as_plus
664
0
  );
665
666
0
  if (status != LXB_STATUS_OK) {
667
0
    lexbor_str_destroy(&lexbor_str, lexbor_parser.mraw, false);
668
0
    return NULL;
669
0
  }
670
671
0
  zend_string *result = zend_string_init((const char *) lexbor_str.data, lexbor_str.length, false);
672
673
0
  lexbor_str_destroy(&lexbor_str, lexbor_parser.mraw, false);
674
675
0
  return result;
676
0
}
677
678
ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_userinfo_percent_encode(const char *str, const size_t str_length)
679
0
{
680
0
  return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_USERINFO, false);
681
0
}
682
683
ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_opaque_host_percent_encode(const char *str, const size_t str_length)
684
0
{
685
0
  return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_C0, false);
686
0
}
687
688
ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_path_percent_encode(const char *str, const size_t str_length)
689
0
{
690
0
  return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_PATH, false);
691
0
}
692
693
ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_opaque_path_percent_encode(const char *str, const size_t str_length)
694
0
{
695
0
  return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_C0, false);
696
0
}
697
698
ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_path_segment_percent_encode(const char *str, const size_t str_length)
699
0
{
700
0
  ZEND_ASSERT((lexbor_custom_url_map['/'] & LXB_URL_MAP_PATH) == 0);
701
702
0
  lexbor_custom_url_map['/'] |= LXB_URL_MAP_PATH;
703
704
0
  zend_string *result = php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_PATH, false);
705
706
0
  lexbor_custom_url_map['/'] &= ~LXB_URL_MAP_PATH;
707
708
0
  return result;
709
0
}
710
711
ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_query_percent_encode(const char *str, const size_t str_length)
712
0
{
713
0
  return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_QUERY, false);
714
0
}
715
716
ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_special_query_percent_encode(const char *str, const size_t str_length)
717
0
{
718
0
  return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_SPECIAL_QUERY, false);
719
0
}
720
721
ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_form_query_percent_encode(const char *str, const size_t str_length)
722
0
{
723
0
  return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_X_WWW_FORM, true);
724
0
}
725
726
ZEND_ATTRIBUTE_NONNULL zend_string *php_uri_parser_whatwg_fragment_percent_encode(const char *str, const size_t str_length)
727
0
{
728
0
  return php_uri_parser_whatwg_percent_encode_component(str, str_length, LXB_URL_MAP_FRAGMENT, false);
729
0
}
730
731
static void php_uri_parser_whatwg_destroy(void *uri)
732
77
{
733
77
  lxb_url_t *lexbor_uri = uri;
734
735
77
  lxb_url_destroy(lexbor_uri);
736
77
}
737
738
ZEND_ATTRIBUTE_NONNULL static zend_always_inline zend_result php_uri_parser_whatwg_component_error(
739
  const char *component_name, const lxb_url_error_type_t error_type
740
0
) {
741
0
  const char *reason = "";
742
0
  if (error_type != LXB_URL_ERROR_TYPE__LAST_ENTRY) {
743
0
    get_reason_from_error_type(error_type, &reason);
744
0
  }
745
746
0
  zend_throw_exception_ex(php_uri_ce_whatwg_invalid_url_exception,
747
0
    0,
748
0
    "The specified %s is malformed%s%s%s",
749
0
    component_name,
750
0
    reason ? " (" : "",
751
0
    reason ? reason : "",
752
0
    reason ? ")" : ""
753
0
  );
754
755
0
  return FAILURE;
756
0
}
757
758
ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_none_validate(const zend_string *component)
759
0
{
760
0
  return SUCCESS;
761
0
}
762
763
static zend_always_inline bool php_uri_whatwg_is_ascii_tab_or_newline(const unsigned char c)
764
0
{
765
0
  return c == '\t' || c == '\n' || c == '\r';
766
0
}
767
768
static zend_always_inline bool php_uri_parser_whatwg_is_alpha(const unsigned char c)
769
0
{
770
0
  return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
771
0
}
772
773
static zend_always_inline bool php_uri_parser_whatwg_is_digit(const unsigned char c)
774
0
{
775
0
  return c >= '0' && c <= '9';
776
0
}
777
778
static zend_always_inline unsigned char php_uri_ascii_to_lowercase(const unsigned char c)
779
0
{
780
0
  return c >= 'A' && c <= 'Z' ? (unsigned char) (c + ('a' - 'A')) : c;
781
0
}
782
783
ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_scheme_validate(const zend_string *scheme)
784
0
{
785
0
  const char *p = ZSTR_VAL(scheme);
786
0
  const char *end = p + ZSTR_LEN(scheme);
787
0
  bool seen_first = false;
788
789
0
  for (const char *c = p; c < end; c++) {
790
0
    const unsigned char uc = (unsigned char) *c;
791
792
0
    if (php_uri_whatwg_is_ascii_tab_or_newline(uc)) {
793
0
      continue;
794
0
    }
795
796
0
    if (!seen_first) {
797
0
      if (!php_uri_parser_whatwg_is_alpha(uc)) {
798
0
        return php_uri_parser_whatwg_component_error("scheme", LXB_URL_ERROR_TYPE_MISSING_SCHEME_NON_RELATIVE_URL);
799
0
      }
800
0
      seen_first = true;
801
0
    } else {
802
0
      if (!php_uri_parser_whatwg_is_alpha(uc) && !php_uri_parser_whatwg_is_digit(uc) && uc != '+' && uc != '-' && uc != '.') {
803
0
        return php_uri_parser_whatwg_component_error("scheme", LXB_URL_ERROR_TYPE_MISSING_SCHEME_NON_RELATIVE_URL);
804
0
      }
805
0
    }
806
0
  }
807
808
0
  if (!seen_first) {
809
0
    return php_uri_parser_whatwg_component_error("scheme", LXB_URL_ERROR_TYPE_MISSING_SCHEME_NON_RELATIVE_URL);
810
0
  }
811
812
0
  return SUCCESS;
813
0
}
814
815
ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_host_validate(const zend_string *host)
816
0
{
817
0
  if (ZSTR_LEN(host) == 0) {
818
    /* Skip validation - an empty string may or may not be a valid host depending on whether the URL is special */
819
0
    return SUCCESS;
820
0
  }
821
822
0
  const char *first = ZSTR_VAL(host);
823
0
  const char *last = ZSTR_VAL(host) + ZSTR_LEN(host);
824
825
0
  while (first < last && php_uri_whatwg_is_ascii_tab_or_newline((unsigned char) *first)) {
826
0
    first++;
827
0
  }
828
829
0
  if (*first != '[') {
830
    /* Skip validation - The host is not an IPv6 address */
831
0
    return SUCCESS;
832
0
  }
833
834
0
  while (first < last && php_uri_whatwg_is_ascii_tab_or_newline((unsigned char) *last)) {
835
0
    last--;
836
0
  }
837
838
0
  lxb_char_t *stripped_host = emalloc(last - first + 1);
839
0
  size_t stripped_host_len = 0;
840
841
0
  for (const char *i = first; i < last; i++) {
842
0
    const unsigned char uc = (unsigned char) *i;
843
844
0
    if (!php_uri_whatwg_is_ascii_tab_or_newline(uc)) {
845
0
      stripped_host[stripped_host_len++] = (lxb_char_t) uc;
846
0
    }
847
0
  }
848
0
  stripped_host[stripped_host_len] = '\0';
849
850
0
  uint16_t ipv6[8];
851
0
  const lxb_status_t result = lxb_url_parse_host_ipv6(
852
0
    &lexbor_parser,
853
0
    stripped_host,
854
0
    stripped_host_len,
855
0
    ipv6
856
0
  );
857
858
0
  efree(stripped_host);
859
860
0
  if (result != LXB_STATUS_OK) {
861
0
    lexbor_plog_entry_t *lxb_error;
862
863
0
    if (lexbor_parser.log != NULL && (lxb_error = lexbor_array_obj_pop(&lexbor_parser.log->list)) != NULL) {
864
0
      return php_uri_parser_whatwg_component_error("host", lxb_error->id);
865
0
    } else {
866
0
      return php_uri_parser_whatwg_component_error("host", LXB_URL_ERROR_TYPE__LAST_ENTRY);
867
0
    }
868
0
  }
869
870
0
  return SUCCESS;
871
0
}
872
873
ZEND_ATTRIBUTE_NONNULL zend_result php_uri_parser_whatwg_port_validate(const zend_long port)
874
0
{
875
0
  if (port  < 0) {
876
0
    return php_uri_parser_whatwg_component_error("port", LXB_URL_ERROR_TYPE_PORT_INVALID);
877
0
  }
878
879
0
  if (port > 65535) {
880
0
    return php_uri_parser_whatwg_component_error("port", LXB_URL_ERROR_TYPE_PORT_OUT_OF_RANGE);
881
0
  }
882
883
0
  return SUCCESS;
884
0
}
885
886
ZEND_ATTRIBUTE_NONNULL static lxb_url_scheme_type_t php_uri_parser_whatwg_get_special_scheme(const zend_string *scheme)
887
0
{
888
0
    const char *p = ZSTR_VAL(scheme);
889
0
    const char *end = p + ZSTR_LEN(scheme);
890
891
    /*
892
     * Create a normalized buffer from the scheme by leaving out tab and newline characters.
893
     * The longest special scheme "https" is 5 characters, therefore 6 bytes is enough to store.
894
     */
895
0
    char buf[6];
896
0
    size_t buf_len = 0;
897
898
0
    for (const char *c = p; c < end; c++) {
899
0
        const unsigned char uc = (unsigned char) *c;
900
901
0
        if (php_uri_whatwg_is_ascii_tab_or_newline(uc)) {
902
0
            continue;
903
0
        }
904
905
0
        if (buf_len == sizeof(buf) - 1) {
906
          /* Longer than any special scheme */
907
0
            return false;
908
0
        }
909
910
0
        buf[buf_len++] = (char) php_uri_ascii_to_lowercase(uc);
911
0
    }
912
913
0
    switch (buf_len) {
914
0
      case 2:
915
0
        if (memcmp(buf, "ws", 2) == 0) {
916
0
          return LXB_URL_SCHEMEL_TYPE_WS;
917
0
        }
918
919
0
        break;
920
0
        case 3:
921
0
        if (memcmp(buf, "ftp", 3) == 0) {
922
0
          return LXB_URL_SCHEMEL_TYPE_FTP;
923
0
        }
924
925
0
        if (memcmp(buf, "wss", 3) == 0) {
926
0
          return LXB_URL_SCHEMEL_TYPE_WSS;
927
0
        }
928
929
0
        break;
930
0
      case 4:
931
0
        if (memcmp(buf, "http", 4) == 0) {
932
0
          return LXB_URL_SCHEMEL_TYPE_HTTP;
933
0
        }
934
935
0
        if (memcmp(buf, "file", 4) == 0) {
936
0
          return LXB_URL_SCHEMEL_TYPE_FILE;
937
0
        }
938
939
0
            break;
940
0
        case 5:
941
0
        if (memcmp(buf, "https", 5) == 0) {
942
0
          return LXB_URL_SCHEMEL_TYPE_HTTPS;
943
0
        }
944
945
0
        break;
946
0
  }
947
948
0
  return LXB_URL_SCHEMEL_TYPE__UNDEF;
949
0
}
950
951
ZEND_ATTRIBUTE_NONNULL static void php_uri_parser_whatwg_build_errors(zval *errors)
952
0
{
953
0
  size_t log_len;
954
955
0
  if (lexbor_parser.log == NULL || (log_len = lexbor_plog_length(lexbor_parser.log)) == 0) {
956
0
    return;
957
0
  }
958
959
0
  if (Z_TYPE_P(errors) != IS_ARRAY) {
960
0
    zval_ptr_dtor(errors);
961
0
    array_init_size(errors, log_len);
962
0
  }
963
964
0
  fill_errors_inner(Z_ARRVAL_P(errors));
965
0
}
966
967
ZEND_ATTRIBUTE_NONNULL_ARGS(2, 3, 4, 5, 6, 7, 8, 9) lxb_url_t *php_uri_parser_whatwg_build_from_zval(
968
  lxb_url_t *lexbor_base_url, const zval *scheme, const zval *username, const zval *password,
969
  const zval *host, const zval *port, const zval *path, const zval *query, const zval *fragment,
970
  zval *errors_zv
971
0
) {
972
0
  if (Z_TYPE_P(host) == IS_NULL ||
973
0
    Z_STRLEN_P(host) == 0 ||
974
0
    php_uri_parser_whatwg_get_special_scheme(Z_STR_P(scheme)) == LXB_URL_SCHEMEL_TYPE_FILE
975
0
  ) {
976
0
    if (Z_TYPE_P(username) != IS_NULL) {
977
0
      zend_throw_exception_ex(php_uri_ce_whatwg_invalid_url_exception, 0, "The specified URL cannot have username");
978
0
      return NULL;
979
0
    }
980
981
0
    if (Z_TYPE_P(password) != IS_NULL) {
982
0
      zend_throw_exception_ex(php_uri_ce_whatwg_invalid_url_exception, 0, "The specified URL cannot have password");
983
0
      return NULL;
984
0
    }
985
986
0
    if (Z_TYPE_P(port) != IS_NULL) {
987
0
      zend_throw_exception_ex(php_uri_ce_whatwg_invalid_url_exception, 0, "The specified URL cannot have port");
988
0
      return NULL;
989
0
    }
990
0
  }
991
992
0
  lxb_url_parser_clean(&lexbor_parser);
993
994
0
  lxb_url_t *lexbor_url = lexbor_mraw_calloc(lexbor_parser.mraw, sizeof(*lexbor_url));
995
0
  if (lexbor_url == NULL) {
996
0
    zend_throw_exception(php_uri_ce_whatwg_invalid_url_exception, "Memory allocation error", 0);
997
0
    return NULL;
998
0
  }
999
1000
0
  lexbor_url->mraw = lexbor_parser.mraw;
1001
1002
  /*
1003
   * The URL is initialized as LXB_URL_SCHEMEL_TYPE__UNDEF but this would prevent the scheme to be updated
1004
   * in case of non-special schemes due to https://github.com/php/php-src/blob/27d7b799c0a13578ee0506b428b8ddc209ffb010/ext/lexbor/lexbor/url/url.c#L1402
1005
   */
1006
0
  if (php_uri_parser_whatwg_get_special_scheme(Z_STR_P(scheme)) == LXB_URL_SCHEMEL_TYPE__UNDEF) {
1007
0
    lexbor_url->scheme.type = LXB_URL_SCHEMEL_TYPE__UNKNOWN;
1008
0
  }
1009
1010
0
  zval errors;
1011
0
  ZVAL_UNDEF(&errors);
1012
1013
0
  zend_result result = php_uri_parser_whatwg_scheme_write(lexbor_url, scheme, NULL);
1014
0
  php_uri_parser_whatwg_build_errors(&errors);
1015
0
  if (result == FAILURE) {
1016
0
    goto failure;
1017
0
  }
1018
1019
0
  result = php_uri_parser_whatwg_host_write(lexbor_url, host, NULL);
1020
0
  php_uri_parser_whatwg_build_errors(&errors);
1021
0
  if (result == FAILURE) {
1022
0
    goto failure;
1023
0
  }
1024
1025
  /* Intentionally writing username after host to avoid error when the username is set but the host is missing */
1026
0
  result = php_uri_parser_whatwg_username_write(lexbor_url, username, NULL);
1027
0
  php_uri_parser_whatwg_build_errors(&errors);
1028
0
  if (result == FAILURE) {
1029
0
    goto failure;
1030
0
  }
1031
1032
  /* Intentionally writing password after host to avoid error when the password is set but the password is missing */
1033
0
  result = php_uri_parser_whatwg_password_write(lexbor_url, password, NULL);
1034
0
  php_uri_parser_whatwg_build_errors(&errors);
1035
0
  if (result == FAILURE) {
1036
0
    goto failure;
1037
0
  }
1038
1039
  /* Intentionally writing port after host to avoid error when the port is set but the host is missing */
1040
0
  result = php_uri_parser_whatwg_port_write(lexbor_url, port, NULL);
1041
0
  php_uri_parser_whatwg_build_errors(&errors);
1042
0
  if (result == FAILURE) {
1043
0
    goto failure;
1044
0
  }
1045
1046
0
  result = php_uri_parser_whatwg_path_write(lexbor_url, path, NULL);
1047
0
  php_uri_parser_whatwg_build_errors(&errors);
1048
0
  if (result == FAILURE) {
1049
0
    goto failure;
1050
0
  }
1051
1052
0
  result = php_uri_parser_whatwg_query_write(lexbor_url, query, NULL);
1053
0
  php_uri_parser_whatwg_build_errors(&errors);
1054
0
  if (result == FAILURE) {
1055
0
    goto failure;
1056
0
  }
1057
1058
0
  result = php_uri_parser_whatwg_fragment_write(lexbor_url, fragment, NULL);
1059
0
  php_uri_parser_whatwg_build_errors(&errors);
1060
0
  if (result == FAILURE) {
1061
0
    goto failure;
1062
0
  }
1063
1064
0
  if (lexbor_base_url != NULL) {
1065
    /* TODO */
1066
0
  }
1067
1068
0
  if (php_uri_pass_errors_by_ref_and_free(errors_zv, &errors) == FAILURE) {
1069
0
    goto failure;
1070
0
  }
1071
1072
0
  return lexbor_url;
1073
1074
0
failure:
1075
0
  zval_ptr_dtor(&errors);
1076
0
  lxb_url_destroy(lexbor_url);
1077
  return NULL;
1078
0
}
1079
1080
PHPAPI const php_uri_parser php_uri_parser_whatwg = {
1081
  .name = PHP_URI_PARSER_WHATWG,
1082
  .parse = php_uri_parser_whatwg_parse,
1083
  .clone = php_uri_parser_whatwg_clone,
1084
  .to_string = php_uri_parser_whatwg_to_string,
1085
  .destroy = php_uri_parser_whatwg_destroy,
1086
  {
1087
    .scheme = {.read = php_uri_parser_whatwg_scheme_read, .write = php_uri_parser_whatwg_scheme_write},
1088
    .username = {.read = php_uri_parser_whatwg_username_read, .write = php_uri_parser_whatwg_username_write},
1089
    .password = {.read = php_uri_parser_whatwg_password_read, .write = php_uri_parser_whatwg_password_write},
1090
    .host = {.read = php_uri_parser_whatwg_host_read, .write = php_uri_parser_whatwg_host_write},
1091
    .port = {.read = php_uri_parser_whatwg_port_read, .write = php_uri_parser_whatwg_port_write},
1092
    .path = {.read = php_uri_parser_whatwg_path_read, .write = php_uri_parser_whatwg_path_write},
1093
    .query = {.read = php_uri_parser_whatwg_query_read, .write = php_uri_parser_whatwg_query_write},
1094
    .fragment = {.read = php_uri_parser_whatwg_fragment_read, .write = php_uri_parser_whatwg_fragment_write},
1095
  }
1096
};