0001-Add-url-data-type-to-contrib.patch

text/plain

Filename: 0001-Add-url-data-type-to-contrib.patch
Type: text/plain
Part: 0
Message: Proposal to add a new URL data type.

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch 0001
Subject: Add url data type to contrib.
File+
contrib/Makefile 6 0
contrib/meson.build 1 0
contrib/url/expected/url_1.out 9 0
contrib/url/expected/url.out 478 0
contrib/url/lexbor/core/array.c 210 0
contrib/url/lexbor/core/array.h 100 0
contrib/url/lexbor/core/array_obj.c 218 0
contrib/url/lexbor/core/array_obj.h 134 0
contrib/url/lexbor/core/base.h 103 0
contrib/url/lexbor/core/bst.c 471 0
contrib/url/lexbor/core/bst.h 108 0
contrib/url/lexbor/core/conv.c 346 0
contrib/url/lexbor/core/conv.h 61 0
contrib/url/lexbor/core/def.h 57 0
contrib/url/lexbor/core/diyfp.c 153 0
contrib/url/lexbor/core/diyfp.h 258 0
contrib/url/lexbor/core/dobject.c 187 0
contrib/url/lexbor/core/dobject.h 92 0
contrib/url/lexbor/core/dtoa.c 404 0
contrib/url/lexbor/core/dtoa.h 28 0
contrib/url/lexbor/core/lexbor.h 43 0
contrib/url/lexbor/core/mem.c 228 0
contrib/url/lexbor/core/mem.h 141 0
contrib/url/lexbor/core/memory.c 53 0
contrib/url/lexbor/core/mraw.c 429 0
contrib/url/lexbor/core/mraw.h 114 0
contrib/url/lexbor/core/plog.c 73 0
contrib/url/lexbor/core/plog.h 102 0
contrib/url/lexbor/core/serialize.h 24 0
contrib/url/lexbor/core/shs.c 118 0
contrib/url/lexbor/core/shs.h 82 0
contrib/url/lexbor/core/str.c 642 0
contrib/url/lexbor/core/str.h 252 0
contrib/url/lexbor/core/str_res.h 420 0
contrib/url/lexbor/core/strtod.c 326 0
contrib/url/lexbor/core/strtod.h 28 0
contrib/url/lexbor/core/swar.h 97 0
contrib/url/lexbor/core/types.h 39 0
contrib/url/lexbor/core/utils.h 29 0
contrib/url/lexbor/url/base.h 32 0
contrib/url/lexbor/url/url.c 5049 0
contrib/url/lexbor/url/url.h 581 0
contrib/url/Makefile 37 0
contrib/url/meson.build 52 0
contrib/url/README 137 0
contrib/url/sql/url.sql 126 0
contrib/url/url--1.0.sql 141 0
contrib/url/url.c 994 0
contrib/url/url.control 5 0
From 5fab7a678572c146d59089e35a4682f74670ea2b Mon Sep 17 00:00:00 2001
From: Alexander Borisov <lex.borisov@gmail.com>
Date: Thu, 5 Dec 2024 15:38:01 +0300
Subject: [PATCH] Add url data type to contrib.

---
 contrib/Makefile                    |    6 +
 contrib/meson.build                 |    1 +
 contrib/url/Makefile                |   37 +
 contrib/url/README                  |  137 +
 contrib/url/expected/url.out        |  478 +++
 contrib/url/expected/url_1.out      |    9 +
 contrib/url/lexbor/core/array.c     |  210 ++
 contrib/url/lexbor/core/array.h     |  100 +
 contrib/url/lexbor/core/array_obj.c |  218 ++
 contrib/url/lexbor/core/array_obj.h |  134 +
 contrib/url/lexbor/core/base.h      |  103 +
 contrib/url/lexbor/core/bst.c       |  471 +++
 contrib/url/lexbor/core/bst.h       |  108 +
 contrib/url/lexbor/core/conv.c      |  346 ++
 contrib/url/lexbor/core/conv.h      |   61 +
 contrib/url/lexbor/core/def.h       |   57 +
 contrib/url/lexbor/core/diyfp.c     |  153 +
 contrib/url/lexbor/core/diyfp.h     |  258 ++
 contrib/url/lexbor/core/dobject.c   |  187 +
 contrib/url/lexbor/core/dobject.h   |   92 +
 contrib/url/lexbor/core/dtoa.c      |  404 +++
 contrib/url/lexbor/core/dtoa.h      |   28 +
 contrib/url/lexbor/core/lexbor.h    |   43 +
 contrib/url/lexbor/core/mem.c       |  228 ++
 contrib/url/lexbor/core/mem.h       |  141 +
 contrib/url/lexbor/core/memory.c    |   53 +
 contrib/url/lexbor/core/mraw.c      |  429 +++
 contrib/url/lexbor/core/mraw.h      |  114 +
 contrib/url/lexbor/core/plog.c      |   73 +
 contrib/url/lexbor/core/plog.h      |  102 +
 contrib/url/lexbor/core/serialize.h |   24 +
 contrib/url/lexbor/core/shs.c       |  118 +
 contrib/url/lexbor/core/shs.h       |   82 +
 contrib/url/lexbor/core/str.c       |  642 ++++
 contrib/url/lexbor/core/str.h       |  252 ++
 contrib/url/lexbor/core/str_res.h   |  420 +++
 contrib/url/lexbor/core/strtod.c    |  326 ++
 contrib/url/lexbor/core/strtod.h    |   28 +
 contrib/url/lexbor/core/swar.h      |   97 +
 contrib/url/lexbor/core/types.h     |   39 +
 contrib/url/lexbor/core/utils.h     |   29 +
 contrib/url/lexbor/url/base.h       |   32 +
 contrib/url/lexbor/url/url.c        | 5049 +++++++++++++++++++++++++++
 contrib/url/lexbor/url/url.h        |  581 +++
 contrib/url/meson.build             |   52 +
 contrib/url/sql/url.sql             |  126 +
 contrib/url/url--1.0.sql            |  141 +
 contrib/url/url.c                   |  994 ++++++
 contrib/url/url.control             |    5 +
 49 files changed, 13818 insertions(+)
 create mode 100644 contrib/url/Makefile
 create mode 100644 contrib/url/README
 create mode 100644 contrib/url/expected/url.out
 create mode 100644 contrib/url/expected/url_1.out
 create mode 100644 contrib/url/lexbor/core/array.c
 create mode 100644 contrib/url/lexbor/core/array.h
 create mode 100644 contrib/url/lexbor/core/array_obj.c
 create mode 100644 contrib/url/lexbor/core/array_obj.h
 create mode 100644 contrib/url/lexbor/core/base.h
 create mode 100644 contrib/url/lexbor/core/bst.c
 create mode 100644 contrib/url/lexbor/core/bst.h
 create mode 100644 contrib/url/lexbor/core/conv.c
 create mode 100644 contrib/url/lexbor/core/conv.h
 create mode 100644 contrib/url/lexbor/core/def.h
 create mode 100644 contrib/url/lexbor/core/diyfp.c
 create mode 100644 contrib/url/lexbor/core/diyfp.h
 create mode 100644 contrib/url/lexbor/core/dobject.c
 create mode 100644 contrib/url/lexbor/core/dobject.h
 create mode 100644 contrib/url/lexbor/core/dtoa.c
 create mode 100644 contrib/url/lexbor/core/dtoa.h
 create mode 100644 contrib/url/lexbor/core/lexbor.h
 create mode 100644 contrib/url/lexbor/core/mem.c
 create mode 100644 contrib/url/lexbor/core/mem.h
 create mode 100644 contrib/url/lexbor/core/memory.c
 create mode 100644 contrib/url/lexbor/core/mraw.c
 create mode 100644 contrib/url/lexbor/core/mraw.h
 create mode 100644 contrib/url/lexbor/core/plog.c
 create mode 100644 contrib/url/lexbor/core/plog.h
 create mode 100644 contrib/url/lexbor/core/serialize.h
 create mode 100644 contrib/url/lexbor/core/shs.c
 create mode 100644 contrib/url/lexbor/core/shs.h
 create mode 100644 contrib/url/lexbor/core/str.c
 create mode 100644 contrib/url/lexbor/core/str.h
 create mode 100644 contrib/url/lexbor/core/str_res.h
 create mode 100644 contrib/url/lexbor/core/strtod.c
 create mode 100644 contrib/url/lexbor/core/strtod.h
 create mode 100644 contrib/url/lexbor/core/swar.h
 create mode 100644 contrib/url/lexbor/core/types.h
 create mode 100644 contrib/url/lexbor/core/utils.h
 create mode 100644 contrib/url/lexbor/url/base.h
 create mode 100644 contrib/url/lexbor/url/url.c
 create mode 100644 contrib/url/lexbor/url/url.h
 create mode 100644 contrib/url/meson.build
 create mode 100644 contrib/url/sql/url.sql
 create mode 100644 contrib/url/url--1.0.sql
 create mode 100644 contrib/url/url.c
 create mode 100644 contrib/url/url.control

diff --git a/contrib/Makefile b/contrib/Makefile
index 952855d9b6..2d81ef97f0 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -88,6 +88,12 @@ else
 ALWAYS_SUBDIRS += hstore_plpython jsonb_plpython ltree_plpython
 endif
 
+ifeq ($(with_icu),yes)
+SUBDIRS += url
+else
+ALWAYS_SUBDIRS += url
+endif
+
 # Missing:
 #		start-scripts	\ (does not have a makefile)
 
diff --git a/contrib/meson.build b/contrib/meson.build
index 159ff41555..ad4b21f49b 100644
--- a/contrib/meson.build
+++ b/contrib/meson.build
@@ -67,6 +67,7 @@ subdir('test_decoding')
 subdir('tsm_system_rows')
 subdir('tsm_system_time')
 subdir('unaccent')
+subdir('url')
 subdir('uuid-ossp')
 subdir('vacuumlo')
 subdir('xml2')
diff --git a/contrib/url/Makefile b/contrib/url/Makefile
new file mode 100644
index 0000000000..8645042422
--- /dev/null
+++ b/contrib/url/Makefile
@@ -0,0 +1,37 @@
+# contrib/url/Makefile
+
+EXTENSION = url
+MODULE_big = url
+DATA = url--1.0.sql
+REGRESS = url
+PGFILEDESC = "url - Uniform Resource Locator data type"
+
+url_objs = lexbor/core/array.o \
+	lexbor/core/array_obj.o \
+	lexbor/core/bst.o \
+	lexbor/core/conv.o \
+	lexbor/core/diyfp.o \
+	lexbor/core/dobject.o \
+	lexbor/core/dtoa.o \
+	lexbor/core/mem.o \
+	lexbor/core/memory.o \
+	lexbor/core/mraw.o \
+	lexbor/core/plog.o \
+	lexbor/core/str.o \
+	lexbor/core/strtod.o \
+	lexbor/url/url.o
+
+OBJS = url.o $(url_objs)
+
+SHLIB_LINK_INTERNAL = $(ICU_LIBS)
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/url
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/url/README b/contrib/url/README
new file mode 100644
index 0000000000..48b50b12a9
--- /dev/null
+++ b/contrib/url/README
@@ -0,0 +1,137 @@
+URL type for PostgreSQL
+---------------------------
+
+To register the new SQL type:
+
+    CREATE EXTENSION url;
+
+URL Functions
+
+Function: to_url()
+Return Type: url
+Description: Returns the value as url.
+Example: to_url('https://example.com/'::text)
+Result: "https://example.com/"
+
+Function: url_base()
+Return Type: url
+Description: Returns url on the basis of absolute and relative url.
+Example: url_base('https://example.com/a/b/c/d/e/f'::url, '../../x/y/z'::text)
+Result: "https://example.com/a/b/c/x/y/z"
+
+Getters
+
+Function: scheme()
+Return Type: text
+Description: Returns scheme from the url if exists, otherwise NULL.
+Example: ('https://example.com/'::url).scheme
+Result: "https"
+
+Function: username()
+Return Type: text
+Description: Returns username from the url if exists, otherwise NULL.
+Example: ('https://root:qwerty@example.com/'::url).username
+Result: "root"
+
+Function: password()
+Return Type: text
+Description: Returns password from the url if exists, otherwise NULL.
+Example: ('https://root:qwerty@example.com/'::url).password
+Result: "qwerty"
+
+Function: host()
+Return Type: text
+Description: Returns host from the url if exists, otherwise NULL.
+Example: ('https://事例.com/'::url).host
+Result: "xn--3kq3x.com"
+
+Function: host_unicode()
+Return Type: text
+Description: Returns host as unicode from the url if exists, otherwise NULL.
+Example: ('https://事例.com/'::url).host_unicode
+Result: "事例.com"
+
+Function: port()
+Return Type: integer
+Description: Returns port from the url if exists, otherwise NULL.
+Example: ('https://example.com:8080/'::url).port
+Result: 8080
+
+Function: path()
+Return Type: text
+Description: Returns path from the url.
+Example: ('https://example.com/path/to/home'::url).path
+Result: "/path/to/home"
+
+Function: query()
+Return Type: text
+Description: Returns query from the url if exists, otherwise NULL.
+Example: ('https://example.com?abc=xyz&1=2'::url).query
+Result: "abc=xyz&1=2"
+
+Function: fragment()
+Return Type: text
+Description: Returns fragment from the url if exists, otherwise NULL.
+Example: ('https://example.com#comments'::url).fragment
+Result: "comments"
+
+
+Setters
+
+Function: url_scheme_set(url, text)
+Return Type: url
+Description: Sets a new scheme for the url.
+Example: url_scheme_set('https://example.com'::url, 'wss')
+Result: "wss://example.com/"
+
+Function: url_username_set(url, text)
+Return Type: url
+Description: Sets a new username for the url.  If the username is NULL or an empty value, it deletes it.
+Example: url_username_set('https://root:qwerty@example.com'::url, 'guest')
+Result: "https://guest:qwerty@example.com/"
+
+Function: url_password_set(url, text)
+Return Type: url
+Description: Sets a new password for the url.  If the password is NULL or an empty value, it deletes it.
+Example: url_password_set('https://root:qwerty@example.com'::url, '12345')
+Result: "https://root:12345@example.com/"
+
+Function: url_host_set(url, text)
+Return Type: url
+Description: Sets a new host for the url.
+Example: url_host_set('https://example.com'::url, 'postgresql.org')
+Result: "https://postgresql.org/"
+
+Function: url_hostname_set(url, text)
+Return Type: url
+Description: Sets a new host for the url.
+Example: url_hostname_set('https://example.com'::url, 'postgresql.org')
+Result: "https://postgresql.org/"
+
+Function: url_port_set(url, text or integer)
+Return Type: url
+Description: Sets a new port for the url.  If the port is NULL or an empty value, it deletes it.
+Example: url_port_set('https://example.com:8080'::url, '80')
+Result: "https://example.com:80/"
+
+Function: url_path_set(url, text)
+Return Type: url
+Description: Sets a new path for the url.  If the path is NULL or an empty value, it deletes it.
+Example: url_path_set('https://example.com/path/to/home'::url, '/a/b/c')
+Result: "https://example.com/a/b/c"
+
+Function: url_query_set(url, text)
+Return Type: url
+Description: Sets a new query for the url.  If the query is NULL or an empty value, it deletes it.
+Example: url_query_set('https://example.com?abc=xyz'::url, '123=abc')
+Result: "https://example.com/?123=abc"
+
+Function: url_fragment_set(url, text)
+Return Type: url
+Description: Sets a new fragment for the url.  If the fragment is NULL or an empty value, it deletes it.
+Example: url_fragment_set('https://example.com#comment'::url, 'position')
+Result: "https://example.com/#position"
+
+NOTE:
+If you pass NULL as url, NULL will be returned.
+
diff --git a/contrib/url/expected/url.out b/contrib/url/expected/url.out
new file mode 100644
index 0000000000..8ddcb7581d
--- /dev/null
+++ b/contrib/url/expected/url.out
@@ -0,0 +1,478 @@
+--
+-- Basic URL tests for the behavior of functions.
+-- The tests for compliance with the specification are located separately.
+--
+-- The tests are designed for a UTF-8 database.  Skip otherwise.
+SELECT getdatabaseencoding() NOT IN ('UTF8')
+  AS skip_test \gset
+\if :skip_test
+  \quit
+\endif
+SELECT getdatabaseencoding(); -- label the results files
+ getdatabaseencoding 
+---------------------
+ UTF8
+(1 row)
+
+CREATE EXTENSION url;
+-- Getters
+select ('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url).scheme; -- OK, https
+ scheme 
+--------
+ https
+(1 row)
+
+select ('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url).username; -- OK, root
+ username 
+----------
+ root
+(1 row)
+
+select ('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url).password; -- OK, qwerty
+ password 
+----------
+ qwerty
+(1 row)
+
+select ('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url).host; -- OK, example.com
+    host     
+-------------
+ example.com
+(1 row)
+
+select ('https://root:qwerty@εxαmπle.cθm:8080/path/to/home?abc=xyz#anchor'::url).host; -- OK, xn--xmle-0ldw4f.xn--cm-x9b
+            host            
+----------------------------
+ xn--xmle-0ldw4f.xn--cm-x9b
+(1 row)
+
+select ('https://root:qwerty@εxαmπle.cθm:8080/path/to/home?abc=xyz#anchor'::url).host_unicode; -- OK, εxαmπle.cθm
+ host_unicode 
+--------------
+ εxαmπle.cθm
+(1 row)
+
+select ('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url).port; -- OK, 8080
+ port 
+------
+ 8080
+(1 row)
+
+select ('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url).path; -- OK, /path/to/home
+     path      
+---------------
+ /path/to/home
+(1 row)
+
+select ('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url).query; -- OK, abc=xyz
+  query  
+---------
+ abc=xyz
+(1 row)
+
+select ('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url).fragment; -- OK, anchor
+ fragment 
+----------
+ anchor
+(1 row)
+
+-- Setters
+select url_scheme_set('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, 'wss'); -- OK
+                         url_scheme_set                         
+----------------------------------------------------------------
+ wss://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor
+(1 row)
+
+select url_username_set('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, 'guest'); -- OK
+                         url_username_set                          
+-------------------------------------------------------------------
+ https://guest:qwerty@example.com:8080/path/to/home?abc=xyz#anchor
+(1 row)
+
+select url_password_set('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, '12345'); -- OK
+                        url_password_set                         
+-----------------------------------------------------------------
+ https://root:12345@example.com:8080/path/to/home?abc=xyz#anchor
+(1 row)
+
+select url_host_set('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, 'postgresql.org'); -- OK
+                            url_host_set                             
+---------------------------------------------------------------------
+ https://root:qwerty@postgresql.org:8080/path/to/home?abc=xyz#anchor
+(1 row)
+
+select url_port_set('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, '80'); -- OK
+                          url_port_set                          
+----------------------------------------------------------------
+ https://root:qwerty@example.com:80/path/to/home?abc=xyz#anchor
+(1 row)
+
+select url_path_set('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, '/docs/books/'); -- OK
+                          url_path_set                           
+-----------------------------------------------------------------
+ https://root:qwerty@example.com:8080/docs/books/?abc=xyz#anchor
+(1 row)
+
+select url_query_set('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, 'xyz=abc'); -- OK
+                          url_query_set                           
+------------------------------------------------------------------
+ https://root:qwerty@example.com:8080/path/to/home?xyz=abc#anchor
+(1 row)
+
+select url_fragment_set('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, 'general_questions'); -- OK
+                              url_fragment_set                               
+-----------------------------------------------------------------------------
+ https://root:qwerty@example.com:8080/path/to/home?abc=xyz#general_questions
+(1 row)
+
+-- Base
+select url_base(NULL::url, NULL); -- OK, NULL
+ url_base 
+----------
+ 
+(1 row)
+
+select url_base('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, NULL); -- OK
+                         url_base                          
+-----------------------------------------------------------
+ https://root:qwerty@example.com:8080/path/to/home?abc=xyz
+(1 row)
+
+select url_base('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, '/change/path'); -- OK
+                     url_base                     
+--------------------------------------------------
+ https://root:qwerty@example.com:8080/change/path
+(1 row)
+
+-- Unicode
+select ('https://εxαmπle.cθm/'::url).host; -- OK, xn--xmle-0ldw4f.xn--cm-x9b
+            host            
+----------------------------
+ xn--xmle-0ldw4f.xn--cm-x9b
+(1 row)
+
+select ('https://εxαmπle.cθm/'::url).host_unicode; -- OK, εxαmπle.cθm
+ host_unicode 
+--------------
+ εxαmπle.cθm
+(1 row)
+
+-- Percent Encode
+select ('https://βεst@example.com'::url).username; -- OK
+    username    
+----------------
+ %CE%B2%CE%B5st
+(1 row)
+
+select ('https://:παssφord@example.com'::url).password; -- OK
+        password         
+-------------------------
+ %CF%80%CE%B1ss%CF%86ord
+(1 row)
+
+select ('https://example.com/pαth/to/hθmε'::url).path; -- OK
+             path             
+------------------------------
+ /p%CE%B1th/to/h%CE%B8m%CE%B5
+(1 row)
+
+select ('https://xample.com/?αβγ=χψω'::url).query; -- OK
+                 query                 
+---------------------------------------
+ %CE%B1%CE%B2%CE%B3=%CF%87%CF%88%CF%89
+(1 row)
+
+select ('https://xample.com/#αnchθrΩ'::url).fragment; -- OK
+        fragment        
+------------------------
+ %CE%B1nch%CE%B8r%CE%A9
+(1 row)
+
+-- Getters Ok, Error
+select ''::url;  -- ERROR
+ERROR:  failed to parse the URL ""
+LINE 1: select ''::url;
+               ^
+select NULL::url; -- OK
+ url 
+-----
+ 
+(1 row)
+
+select (NULL::url).scheme; -- OK
+ scheme 
+--------
+ 
+(1 row)
+
+select ('file://path/to'::url).scheme; -- OK
+ scheme 
+--------
+ file
+(1 row)
+
+select ('/bad/url'::url).scheme; -- ERROR
+ERROR:  failed to parse the URL "/bad/url"
+LINE 1: select ('/bad/url'::url).scheme;
+                ^
+select (NULL::url).username; -- OK
+ username 
+----------
+ 
+(1 row)
+
+select ('https://example.com'::url).username; -- OK
+ username 
+----------
+ 
+(1 row)
+
+select (NULL::url).password; -- OK
+ password 
+----------
+ 
+(1 row)
+
+select ('https://example.com'::url).password; -- OK
+ password 
+----------
+ 
+(1 row)
+
+select (NULL::url).host; -- OK
+ host 
+------
+ 
+(1 row)
+
+select ('file://host/to'::url).host; -- OK
+ host 
+------
+ host
+(1 row)
+
+select ('file:/path/to'::url).host; -- OK
+ host 
+------
+ 
+(1 row)
+
+select (NULL::url).host_unicode; -- OK
+ host_unicode 
+--------------
+ 
+(1 row)
+
+select ('file://host/to'::url).host_unicode; -- OK
+ host_unicode 
+--------------
+ host
+(1 row)
+
+select ('file:/path/to'::url).host_unicode; -- OK
+ host_unicode 
+--------------
+ 
+(1 row)
+
+select (NULL::url).port; -- OK
+ port 
+------
+     
+(1 row)
+
+select ('https://example.com'::url).port; -- OK
+ port 
+------
+     
+(1 row)
+
+select (NULL::url).path; -- OK
+ path 
+------
+ 
+(1 row)
+
+select ('https://example.com'::url).path; -- OK
+ path 
+------
+ /
+(1 row)
+
+select ('file:/path/to'::url).path; -- OK
+   path   
+----------
+ /path/to
+(1 row)
+
+select (NULL::url).query; -- OK
+ query 
+-------
+ 
+(1 row)
+
+select ('https://example.com'::url).query; -- OK
+ query 
+-------
+ 
+(1 row)
+
+select (NULL::url).fragment; -- OK
+ fragment 
+----------
+ 
+(1 row)
+
+select ('https://example.com'::url).fragment; -- OK
+ fragment 
+----------
+ 
+(1 row)
+
+-- Setters Ok, Error
+select url_scheme_set('https://example.com'::url, NULL); -- ERROR
+ERROR:  failed to parse "scheme" part "" of URL
+select url_scheme_set('https://example.com'::url, ''); -- ERROR
+ERROR:  failed to parse "scheme" part "" of URL
+select url_scheme_set('https://example.com'::url, '---+'); -- ERROR
+ERROR:  failed to parse "scheme" part "---+" of URL
+select url_username_set('https://root:qwerty@example.com'::url, NULL); -- OK
+       url_username_set       
+------------------------------
+ https://:qwerty@example.com/
+(1 row)
+
+select url_username_set('https://root:qwerty@example.com'::url, ''); -- OK
+       url_username_set       
+------------------------------
+ https://:qwerty@example.com/
+(1 row)
+
+select url_username_set('https://root:qwerty@example.com'::url, 'αβγ'); -- OK
+                url_username_set                
+------------------------------------------------
+ https://%CE%B1%CE%B2%CE%B3:qwerty@example.com/
+(1 row)
+
+select url_password_set('https://root:qwerty@example.com'::url, NULL); -- OK
+     url_password_set      
+---------------------------
+ https://root@example.com/
+(1 row)
+
+select url_password_set('https://root:qwerty@example.com'::url, ''); -- OK
+     url_password_set      
+---------------------------
+ https://root@example.com/
+(1 row)
+
+select url_password_set('https://root:qwerty@example.com'::url, 'αβγ'); -- OK
+               url_password_set               
+----------------------------------------------
+ https://root:%CE%B1%CE%B2%CE%B3@example.com/
+(1 row)
+
+select url_port_set('https://example.com:8080'::url, NULL); -- OK
+     url_port_set     
+----------------------
+ https://example.com/
+(1 row)
+
+select url_port_set('https://example.com:8080'::url, ''); -- OK
+     url_port_set     
+----------------------
+ https://example.com/
+(1 row)
+
+select url_port_set('https://example.com:8080'::url, '80'); -- OK
+      url_port_set       
+-------------------------
+ https://example.com:80/
+(1 row)
+
+select url_port_set('https://example.com:8080'::url, '123456'); -- ERROR
+ERROR:  failed to parse "port" part "123456" of URL
+select url_port_set('https://example.com:8080'::url, 80); -- OK
+      url_port_set       
+-------------------------
+ https://example.com:80/
+(1 row)
+
+select url_port_set('https://example.com:8080'::url, 123456); -- ERROR
+ERROR:  failed to parse "port" part "123456" of URL
+select url_host_set('https://example.com'::url, NULL); -- ERROR
+ERROR:  failed to parse "host" part "" of URL
+select url_host_set('https://example.com'::url, ''); -- ERROR
+ERROR:  failed to parse "host" part "" of URL
+select url_host_set('https://example.com'::url, '123'); -- OK
+    url_host_set    
+--------------------
+ https://0.0.0.123/
+(1 row)
+
+select url_host_set('https://example.com'::url, 'αβγ'); -- OK
+    url_host_set    
+--------------------
+ https://xn--mxacd/
+(1 row)
+
+select url_path_set('https://example.com/path/to/home'::url, NULL); -- OK
+     url_path_set     
+----------------------
+ https://example.com/
+(1 row)
+
+select url_path_set('https://example.com/path/to/home'::url, ''); -- OK
+     url_path_set     
+----------------------
+ https://example.com/
+(1 row)
+
+select url_path_set('https://example.com/path/to/home'::url, '/'); -- OK
+     url_path_set     
+----------------------
+ https://example.com/
+(1 row)
+
+select url_path_set('https://example.com/path/to/home'::url, 'αβγ'); -- OK
+              url_path_set              
+----------------------------------------
+ https://example.com/%CE%B1%CE%B2%CE%B3
+(1 row)
+
+select url_query_set('https://example.com?abc=xyz'::url, NULL); -- OK
+    url_query_set     
+----------------------
+ https://example.com/
+(1 row)
+
+select url_query_set('https://example.com?abc=xyz'::url, ''); -- OK
+    url_query_set     
+----------------------
+ https://example.com/
+(1 row)
+
+select url_query_set('https://example.com?abc=xyz'::url, 'αβγ'); -- OK
+              url_query_set              
+-----------------------------------------
+ https://example.com/?%CE%B1%CE%B2%CE%B3
+(1 row)
+
+select url_fragment_set('https://example.com#anchor'::url, NULL); -- OK
+   url_fragment_set   
+----------------------
+ https://example.com/
+(1 row)
+
+select url_fragment_set('https://example.com#anchor'::url, ''); -- OK
+   url_fragment_set   
+----------------------
+ https://example.com/
+(1 row)
+
+select url_fragment_set('https://example.com#anchor'::url, 'αβγ'); -- OK
+            url_fragment_set             
+-----------------------------------------
+ https://example.com/#%CE%B1%CE%B2%CE%B3
+(1 row)
+
diff --git a/contrib/url/expected/url_1.out b/contrib/url/expected/url_1.out
new file mode 100644
index 0000000000..59772488fd
--- /dev/null
+++ b/contrib/url/expected/url_1.out
@@ -0,0 +1,9 @@
+--
+-- Basic URL tests for the behavior of functions.
+-- The tests for compliance with the specification are located separately.
+--
+-- The tests are designed for a UTF-8 database.  Skip otherwise.
+SELECT getdatabaseencoding() NOT IN ('UTF8')
+  AS skip_test \gset
+\if :skip_test
+  \quit
diff --git a/contrib/url/lexbor/core/array.c b/contrib/url/lexbor/core/array.c
new file mode 100644
index 0000000000..2fe801fbe7
--- /dev/null
+++ b/contrib/url/lexbor/core/array.c
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#include "lexbor/core/array.h"
+
+
+lexbor_array_t *
+lexbor_array_create(void)
+{
+    return lexbor_calloc(1, sizeof(lexbor_array_t));
+}
+
+lxb_status_t
+lexbor_array_init(lexbor_array_t *array, size_t size)
+{
+    if (array == NULL) {
+        return LXB_STATUS_ERROR_OBJECT_IS_NULL;
+    }
+
+    if (size == 0) {
+        return LXB_STATUS_ERROR_TOO_SMALL_SIZE;
+    }
+
+    array->length = 0;
+    array->size = size;
+
+    array->list = lexbor_malloc(sizeof(void *) * size);
+    if (array->list == NULL) {
+        return LXB_STATUS_ERROR_MEMORY_ALLOCATION;
+    }
+
+    return LXB_STATUS_OK;
+}
+
+void
+lexbor_array_clean(lexbor_array_t *array)
+{
+    if (array != NULL) {
+        array->length = 0;
+    }
+}
+
+lexbor_array_t *
+lexbor_array_destroy(lexbor_array_t *array, bool self_destroy)
+{
+    if (array == NULL)
+        return NULL;
+
+    if (array->list) {
+        array->length = 0;
+        array->size = 0;
+        array->list = lexbor_free(array->list);
+    }
+
+    if (self_destroy) {
+        return lexbor_free(array);
+    }
+
+    return array;
+}
+
+void **
+lexbor_array_expand(lexbor_array_t *array, size_t up_to)
+{
+    void **list;
+    size_t new_size;
+
+    if (array->length > (SIZE_MAX - up_to))
+        return NULL;
+
+    new_size = array->length + up_to;
+    list = lexbor_realloc(array->list, sizeof(void *) * new_size);
+
+    if (list == NULL)
+        return NULL;
+
+    array->list = list;
+    array->size = new_size;
+
+    return list;
+}
+
+lxb_status_t
+lexbor_array_push(lexbor_array_t *array, void *value)
+{
+    if (array->length >= array->size) {
+        if ((lexbor_array_expand(array, 128) == NULL)) {
+            return LXB_STATUS_ERROR_MEMORY_ALLOCATION;
+        }
+    }
+
+    array->list[ array->length ] = value;
+    array->length++;
+
+    return LXB_STATUS_OK;
+}
+
+void *
+lexbor_array_pop(lexbor_array_t *array)
+{
+    if (array->length == 0) {
+        return NULL;
+    }
+
+    array->length--;
+    return array->list[ array->length ];
+}
+
+lxb_status_t
+lexbor_array_insert(lexbor_array_t *array, size_t idx, void *value)
+{
+    if (idx >= array->length) {
+        size_t up_to = (idx - array->length) + 1;
+
+        if (idx >= array->size) {
+            if ((lexbor_array_expand(array, up_to) == NULL)) {
+                return LXB_STATUS_ERROR_MEMORY_ALLOCATION;
+            }
+        }
+
+        memset(&array->list[array->length], 0, sizeof(void *) * up_to);
+
+        array->list[ idx ] = value;
+        array->length += up_to;
+
+        return LXB_STATUS_OK;
+    }
+
+    if (array->length >= array->size) {
+        if ((lexbor_array_expand(array, 32) == NULL)) {
+            return LXB_STATUS_ERROR_MEMORY_ALLOCATION;
+        }
+    }
+
+    memmove(&array->list[idx + 1], &array->list[idx],
+            sizeof(void *) * (array->length - idx));
+
+    array->list[ idx ] = value;
+    array->length++;
+
+    return LXB_STATUS_OK;
+}
+
+lxb_status_t
+lexbor_array_set(lexbor_array_t *array, size_t idx, void *value)
+{
+    if (idx >= array->length) {
+        size_t up_to = (idx - array->length) + 1;
+
+        if (idx >= array->size) {
+            if ((lexbor_array_expand(array, up_to) == NULL)) {
+                return LXB_STATUS_ERROR_MEMORY_ALLOCATION;
+            }
+        }
+
+        memset(&array->list[array->length], 0, sizeof(void *) * up_to);
+
+        array->length += up_to;
+    }
+
+    array->list[idx] = value;
+
+    return LXB_STATUS_OK;
+}
+
+void
+lexbor_array_delete(lexbor_array_t *array, size_t begin, size_t length)
+{
+    size_t end_len;
+
+    if (begin >= array->length || length == 0) {
+        return;
+    }
+
+    end_len = begin + length;
+
+    if (end_len >= array->length) {
+        array->length = begin;
+        return;
+    }
+
+    memmove(&array->list[begin], &array->list[end_len],
+            sizeof(void *) * (array->length - end_len));
+
+    array->length -= length;
+}
+
+/*
+ * No inline functions.
+ */
+void *
+lexbor_array_get_noi(lexbor_array_t *array, size_t idx)
+{
+    return lexbor_array_get(array, idx);
+}
+
+size_t
+lexbor_array_length_noi(lexbor_array_t *array)
+{
+    return lexbor_array_length(array);
+}
+
+size_t
+lexbor_array_size_noi(lexbor_array_t *array)
+{
+    return lexbor_array_size(array);
+}
diff --git a/contrib/url/lexbor/core/array.h b/contrib/url/lexbor/core/array.h
new file mode 100644
index 0000000000..5dbdac7d1e
--- /dev/null
+++ b/contrib/url/lexbor/core/array.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#ifndef LEXBOR_ARRAY_H
+#define LEXBOR_ARRAY_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "lexbor/core/base.h"
+
+
+typedef struct {
+    void   **list;
+    size_t size;
+    size_t length;
+}
+lexbor_array_t;
+
+
+LXB_API lexbor_array_t *
+lexbor_array_create(void);
+
+LXB_API lxb_status_t
+lexbor_array_init(lexbor_array_t *array, size_t size);
+
+LXB_API void
+lexbor_array_clean(lexbor_array_t *array);
+
+LXB_API lexbor_array_t *
+lexbor_array_destroy(lexbor_array_t *array, bool self_destroy);
+
+
+LXB_API void **
+lexbor_array_expand(lexbor_array_t *array, size_t up_to);
+
+
+LXB_API lxb_status_t
+lexbor_array_push(lexbor_array_t *array, void *value);
+
+LXB_API void *
+lexbor_array_pop(lexbor_array_t *array);
+
+LXB_API lxb_status_t
+lexbor_array_insert(lexbor_array_t *array, size_t idx, void *value);
+
+LXB_API lxb_status_t
+lexbor_array_set(lexbor_array_t *array, size_t idx, void *value);
+
+LXB_API void
+lexbor_array_delete(lexbor_array_t *array, size_t begin, size_t length);
+
+
+/*
+ * Inline functions
+ */
+lxb_inline void *
+lexbor_array_get(lexbor_array_t *array, size_t idx)
+{
+    if (idx >= array->length) {
+        return NULL;
+    }
+
+    return array->list[idx];
+}
+
+lxb_inline size_t
+lexbor_array_length(lexbor_array_t *array)
+{
+    return array->length;
+}
+
+lxb_inline size_t
+lexbor_array_size(lexbor_array_t *array)
+{
+    return array->size;
+}
+
+/*
+ * No inline functions for ABI.
+ */
+LXB_API void *
+lexbor_array_get_noi(lexbor_array_t *array, size_t idx);
+
+LXB_API size_t
+lexbor_array_length_noi(lexbor_array_t *array);
+
+LXB_API size_t
+lexbor_array_size_noi(lexbor_array_t *array);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_ARRAY_H */
diff --git a/contrib/url/lexbor/core/array_obj.c b/contrib/url/lexbor/core/array_obj.c
new file mode 100644
index 0000000000..eb1fd452c3
--- /dev/null
+++ b/contrib/url/lexbor/core/array_obj.c
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#include "lexbor/core/array_obj.h"
+
+
+lexbor_array_obj_t *
+lexbor_array_obj_create(void)
+{
+    return lexbor_calloc(1, sizeof(lexbor_array_obj_t));
+}
+
+lxb_status_t
+lexbor_array_obj_init(lexbor_array_obj_t *array,
+                      size_t size, size_t struct_size)
+{
+    if (array == NULL) {
+        return LXB_STATUS_ERROR_OBJECT_IS_NULL;
+    }
+
+    if (size == 0 || struct_size == 0) {
+        return LXB_STATUS_ERROR_TOO_SMALL_SIZE;
+    }
+
+    array->length = 0;
+    array->size = size;
+    array->struct_size = struct_size;
+
+    array->list = lexbor_malloc(sizeof(uint8_t *)
+                                * (array->size * struct_size));
+    if (array->list == NULL) {
+        return LXB_STATUS_ERROR_MEMORY_ALLOCATION;
+    }
+
+    return LXB_STATUS_OK;
+}
+
+void
+lexbor_array_obj_clean(lexbor_array_obj_t *array)
+{
+    if (array != NULL) {
+        array->length = 0;
+    }
+}
+
+lexbor_array_obj_t *
+lexbor_array_obj_destroy(lexbor_array_obj_t *array, bool self_destroy)
+{
+    if (array == NULL)
+        return NULL;
+
+    if (array->list) {
+        array->length = 0;
+        array->size = 0;
+        array->list = lexbor_free(array->list);
+    }
+
+    if (self_destroy) {
+        return lexbor_free(array);
+    }
+
+    return array;
+}
+
+uint8_t *
+lexbor_array_obj_expand(lexbor_array_obj_t *array, size_t up_to)
+{
+    uint8_t *list;
+    size_t new_size;
+
+    if (array->length > (SIZE_MAX - up_to)) {
+        return NULL;
+    }
+
+    new_size = array->length + up_to;
+
+    list = lexbor_realloc(array->list, sizeof(uint8_t *)
+                          * (new_size * array->struct_size));
+    if (list == NULL) {
+        return NULL;
+    }
+
+    array->list = list;
+    array->size = new_size;
+
+    return list;
+}
+
+void *
+lexbor_array_obj_push(lexbor_array_obj_t *array)
+{
+    void *entry;
+
+    if (array->length >= array->size)
+    {
+        if ((lexbor_array_obj_expand(array, 128) == NULL)) {
+            return NULL;
+        }
+    }
+
+    entry = array->list + (array->length * array->struct_size);
+    array->length++;
+
+    memset(entry, 0, array->struct_size);
+
+    return entry;
+}
+
+void *
+lexbor_array_obj_push_wo_cls(lexbor_array_obj_t *array)
+{
+    void *entry;
+
+    if (array->length >= array->size) {
+        if ((lexbor_array_obj_expand(array, 128) == NULL)) {
+            return NULL;
+        }
+    }
+
+    entry = array->list + (array->length * array->struct_size);
+    array->length++;
+
+    return entry;
+}
+
+void *
+lexbor_array_obj_push_n(lexbor_array_obj_t *array, size_t count)
+{
+    void *entry;
+
+    if ((array->length + count) > array->size) {
+        if ((lexbor_array_obj_expand(array, count + 128) == NULL)) {
+            return NULL;
+        }
+    }
+
+    entry = array->list + (array->length * array->struct_size);
+    array->length += count;
+
+    return entry;
+}
+
+void *
+lexbor_array_obj_pop(lexbor_array_obj_t *array)
+{
+    if (array->length == 0) {
+        return NULL;
+    }
+
+    array->length--;
+    return array->list + (array->length * array->struct_size);
+}
+
+void
+lexbor_array_obj_delete(lexbor_array_obj_t *array, size_t begin, size_t length)
+{
+    size_t end_len;
+
+    if (begin >= array->length || length == 0) {
+        return;
+    }
+
+    end_len = begin + length;
+
+    if (end_len >= array->length) {
+        array->length = begin;
+        return;
+    }
+
+    memmove(&array->list[ begin * array->struct_size ],
+            &array->list[ end_len * array->struct_size ],
+            sizeof(uint8_t *)
+            * ((array->length - end_len) * array->struct_size));
+
+    array->length -= length;
+}
+
+/*
+ * No inline functions.
+ */
+void
+lexbor_array_obj_erase_noi(lexbor_array_obj_t *array)
+{
+    lexbor_array_obj_erase(array);
+}
+
+void *
+lexbor_array_obj_get_noi(lexbor_array_obj_t *array, size_t idx)
+{
+    return lexbor_array_obj_get(array, idx);
+}
+
+size_t
+lexbor_array_obj_length_noi(lexbor_array_obj_t *array)
+{
+    return lexbor_array_obj_length(array);
+}
+
+size_t
+lexbor_array_obj_size_noi(lexbor_array_obj_t *array)
+{
+    return lexbor_array_obj_size(array);
+}
+
+size_t
+lexbor_array_obj_struct_size_noi(lexbor_array_obj_t *array)
+{
+    return lexbor_array_obj_struct_size(array);
+}
+
+void *
+lexbor_array_obj_last_noi(lexbor_array_obj_t *array)
+{
+    return lexbor_array_obj_last(array);
+}
diff --git a/contrib/url/lexbor/core/array_obj.h b/contrib/url/lexbor/core/array_obj.h
new file mode 100644
index 0000000000..de6202b221
--- /dev/null
+++ b/contrib/url/lexbor/core/array_obj.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#ifndef LEXBOR_ARRAY_OBJ_H
+#define LEXBOR_ARRAY_OBJ_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "lexbor/core/base.h"
+
+
+typedef struct {
+    uint8_t *list;
+    size_t  size;
+    size_t  length;
+    size_t  struct_size;
+}
+lexbor_array_obj_t;
+
+
+LXB_API lexbor_array_obj_t *
+lexbor_array_obj_create(void);
+
+LXB_API lxb_status_t
+lexbor_array_obj_init(lexbor_array_obj_t *array,
+                      size_t size, size_t struct_size);
+
+LXB_API void
+lexbor_array_obj_clean(lexbor_array_obj_t *array);
+
+LXB_API lexbor_array_obj_t *
+lexbor_array_obj_destroy(lexbor_array_obj_t *array, bool self_destroy);
+
+
+LXB_API uint8_t *
+lexbor_array_obj_expand(lexbor_array_obj_t *array, size_t up_to);
+
+
+LXB_API void *
+lexbor_array_obj_push(lexbor_array_obj_t *array);
+
+LXB_API void *
+lexbor_array_obj_push_wo_cls(lexbor_array_obj_t *array);
+
+LXB_API void *
+lexbor_array_obj_push_n(lexbor_array_obj_t *array, size_t count);
+
+LXB_API void *
+lexbor_array_obj_pop(lexbor_array_obj_t *array);
+
+LXB_API void
+lexbor_array_obj_delete(lexbor_array_obj_t *array, size_t begin, size_t length);
+
+
+/*
+ * Inline functions
+ */
+lxb_inline void
+lexbor_array_obj_erase(lexbor_array_obj_t *array)
+{
+    memset(array, 0, sizeof(lexbor_array_obj_t));
+}
+
+lxb_inline void *
+lexbor_array_obj_get(const lexbor_array_obj_t *array, size_t idx)
+{
+    if (idx >= array->length) {
+        return NULL;
+    }
+
+    return array->list + (idx * array->struct_size);
+}
+
+lxb_inline size_t
+lexbor_array_obj_length(lexbor_array_obj_t *array)
+{
+    return array->length;
+}
+
+lxb_inline size_t
+lexbor_array_obj_size(lexbor_array_obj_t *array)
+{
+    return array->size;
+}
+
+lxb_inline size_t
+lexbor_array_obj_struct_size(lexbor_array_obj_t *array)
+{
+    return array->struct_size;
+}
+
+lxb_inline void *
+lexbor_array_obj_last(lexbor_array_obj_t *array)
+{
+    if (array->length == 0) {
+        return NULL;
+    }
+
+    return array->list + ((array->length - 1) * array->struct_size);
+}
+
+
+/*
+ * No inline functions for ABI.
+ */
+LXB_API void
+lexbor_array_obj_erase_noi(lexbor_array_obj_t *array);
+
+LXB_API void *
+lexbor_array_obj_get_noi(lexbor_array_obj_t *array, size_t idx);
+
+LXB_API size_t
+lexbor_array_obj_length_noi(lexbor_array_obj_t *array);
+
+LXB_API size_t
+lexbor_array_obj_size_noi(lexbor_array_obj_t *array);
+
+LXB_API size_t
+lexbor_array_obj_struct_size_noi(lexbor_array_obj_t *array);
+
+LXB_API void *
+lexbor_array_obj_last_noi(lexbor_array_obj_t *array);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_ARRAY_OBJ_H */
diff --git a/contrib/url/lexbor/core/base.h b/contrib/url/lexbor/core/base.h
new file mode 100644
index 0000000000..3c14dcff4c
--- /dev/null
+++ b/contrib/url/lexbor/core/base.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2018-2024 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#ifndef LEXBOR_BASE_H
+#define LEXBOR_BASE_H
+
+#ifdef __cplusplus
+#define __STDC_LIMIT_MACROS
+#define __STDC_CONSTANT_MACROS
+
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <memory.h>
+#include <limits.h>
+#include <string.h>
+
+#include "lexbor/core/def.h"
+#include "lexbor/core/types.h"
+#include "lexbor/core/lexbor.h"
+
+#define LEXBOR_VERSION_MAJOR 1
+#define LEXBOR_VERSION_MINOR 8
+#define LEXBOR_VERSION_PATCH 0
+
+#define LEXBOR_VERSION_STRING LEXBOR_STRINGIZE(LEXBOR_VERSION_MAJOR) "."       \
+                              LEXBOR_STRINGIZE(LEXBOR_VERSION_MINOR) "."       \
+                              LEXBOR_STRINGIZE(LEXBOR_VERSION_PATCH)
+
+#define lexbor_assert(val)
+
+#define lexbor_max(val1, val2) ((val1) > (val2) ? (val1) : (val2))
+#define lexbor_min(val1, val2) ((val1) < (val2) ? (val1) : (val2))
+
+
+/*
+ * Very important!!!
+ *
+ * for lexbor 0..00AFFF; LXB_STATUS_OK == 0x000000
+ */
+typedef enum {
+    LXB_STATUS_OK                       = 0x0000,
+    LXB_STATUS_ERROR                    = 0x0001,
+    LXB_STATUS_ERROR_MEMORY_ALLOCATION,
+    LXB_STATUS_ERROR_OBJECT_IS_NULL,
+    LXB_STATUS_ERROR_SMALL_BUFFER,
+    LXB_STATUS_ERROR_INCOMPLETE_OBJECT,
+    LXB_STATUS_ERROR_NO_FREE_SLOT,
+    LXB_STATUS_ERROR_TOO_SMALL_SIZE,
+    LXB_STATUS_ERROR_NOT_EXISTS,
+    LXB_STATUS_ERROR_WRONG_ARGS,
+    LXB_STATUS_ERROR_WRONG_STAGE,
+    LXB_STATUS_ERROR_UNEXPECTED_RESULT,
+    LXB_STATUS_ERROR_UNEXPECTED_DATA,
+    LXB_STATUS_ERROR_OVERFLOW,
+    LXB_STATUS_CONTINUE,
+    LXB_STATUS_SMALL_BUFFER,
+    LXB_STATUS_ABORTED,
+    LXB_STATUS_STOPPED,
+    LXB_STATUS_NEXT,
+    LXB_STATUS_STOP,
+    LXB_STATUS_WARNING
+}
+lexbor_status_t;
+
+typedef enum {
+    LEXBOR_ACTION_OK    = 0x00,
+    LEXBOR_ACTION_STOP  = 0x01,
+    LEXBOR_ACTION_NEXT  = 0x02
+}
+lexbor_action_t;
+
+
+typedef lxb_status_t
+(*lexbor_serialize_cb_f)(const lxb_char_t *data, size_t len, void *ctx);
+
+typedef lxb_status_t
+(*lexbor_serialize_cb_cp_f)(const lxb_codepoint_t *cps, size_t len, void *ctx);
+
+
+typedef struct {
+    lexbor_serialize_cb_f cb;
+    void                  *ctx;
+
+    intptr_t              opt;
+    size_t                count;
+}
+lexbor_serialize_ctx_t;
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_BASE_H */
+
diff --git a/contrib/url/lexbor/core/bst.c b/contrib/url/lexbor/core/bst.c
new file mode 100644
index 0000000000..60e79cfb6e
--- /dev/null
+++ b/contrib/url/lexbor/core/bst.c
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#include "lexbor/core/bst.h"
+#include "lexbor/core/conv.h"
+
+
+lexbor_bst_t *
+lexbor_bst_create(void)
+{
+    return lexbor_calloc(1, sizeof(lexbor_bst_t));
+}
+
+lxb_status_t
+lexbor_bst_init(lexbor_bst_t *bst, size_t size)
+{
+    lxb_status_t status;
+
+    if (bst == NULL) {
+        return LXB_STATUS_ERROR_OBJECT_IS_NULL;
+    }
+
+    if (size == 0) {
+        return LXB_STATUS_ERROR_WRONG_ARGS;
+    }
+
+    bst->dobject = lexbor_dobject_create();
+    status = lexbor_dobject_init(bst->dobject, size,
+                                 sizeof(lexbor_bst_entry_t));
+    if (status != LXB_STATUS_OK) {
+        return status;
+    }
+
+    bst->root = 0;
+    bst->tree_length = 0;
+
+    return LXB_STATUS_OK;
+}
+
+void
+lexbor_bst_clean(lexbor_bst_t *bst)
+{
+    if (bst != NULL) {
+        lexbor_dobject_clean(bst->dobject);
+
+        bst->root = 0;
+        bst->tree_length = 0;
+    }
+}
+
+lexbor_bst_t *
+lexbor_bst_destroy(lexbor_bst_t *bst, bool self_destroy)
+{
+    if (bst == NULL) {
+        return NULL;
+    }
+
+    bst->dobject = lexbor_dobject_destroy(bst->dobject, true);
+
+    if (self_destroy) {
+        return lexbor_free(bst);
+    }
+
+    return bst;
+}
+
+lexbor_bst_entry_t *
+lexbor_bst_entry_make(lexbor_bst_t *bst, size_t size)
+{
+    lexbor_bst_entry_t *new_entry = lexbor_dobject_calloc(bst->dobject);
+    if (new_entry == NULL) {
+        return NULL;
+    }
+
+    new_entry->size = size;
+
+    bst->tree_length++;
+
+    return new_entry;
+}
+
+lexbor_bst_entry_t *
+lexbor_bst_insert(lexbor_bst_t *bst, lexbor_bst_entry_t **scope,
+                  size_t size, void *value)
+{
+    lexbor_bst_entry_t *new_entry, *entry;
+
+    new_entry = lexbor_dobject_calloc(bst->dobject);
+    if (new_entry == NULL) {
+        return NULL;
+    }
+
+    new_entry->size  = size;
+    new_entry->value = value;
+
+    bst->tree_length++;
+
+    if (*scope == NULL) {
+        *scope = new_entry;
+        return new_entry;
+    }
+
+    entry = *scope;
+
+    while (entry != NULL) {
+        if (size == entry->size) {
+            if (entry->next) {
+                new_entry->next = entry->next;
+            }
+
+            entry->next = new_entry;
+            new_entry->parent = entry->parent;
+
+            return new_entry;
+        }
+        else if (size > entry->size) {
+            if (entry->right == NULL) {
+                entry->right = new_entry;
+                new_entry->parent = entry;
+
+                return new_entry;
+            }
+
+            entry = entry->right;
+        }
+        else {
+            if (entry->left == NULL) {
+                entry->left = new_entry;
+                new_entry->parent = entry;
+
+                return new_entry;
+            }
+
+            entry = entry->left;
+        }
+    }
+
+    return NULL;
+}
+
+lexbor_bst_entry_t *
+lexbor_bst_insert_not_exists(lexbor_bst_t *bst, lexbor_bst_entry_t **scope,
+                             size_t size)
+{
+    lexbor_bst_entry_t *entry;
+
+    if (*scope == NULL) {
+        *scope = lexbor_bst_entry_make(bst, size);
+
+        return *scope;
+    }
+
+    entry = *scope;
+
+    while (entry != NULL) {
+        if (size == entry->size) {
+            return entry;
+        }
+        else if (size > entry->size) {
+            if (entry->right == NULL) {
+                entry->right = lexbor_bst_entry_make(bst, size);
+                entry->right->parent = entry;
+
+                return entry->right;
+            }
+
+            entry = entry->right;
+        }
+        else {
+            if (entry->left == NULL) {
+                entry->left = lexbor_bst_entry_make(bst, size);
+                entry->left->parent = entry;
+
+                return entry->left;
+            }
+
+            entry = entry->left;
+        }
+    }
+
+    return NULL;
+}
+
+lexbor_bst_entry_t *
+lexbor_bst_search(lexbor_bst_t *bst, lexbor_bst_entry_t *scope, size_t size)
+{
+    while (scope != NULL) {
+        if (scope->size == size) {
+            return scope;
+        }
+        else if (size > scope->size) {
+            scope = scope->right;
+        }
+        else {
+            scope = scope->left;
+        }
+    }
+
+    return NULL;
+}
+
+lexbor_bst_entry_t *
+lexbor_bst_search_close(lexbor_bst_t *bst, lexbor_bst_entry_t *scope,
+                        size_t size)
+{
+    lexbor_bst_entry_t *max = NULL;
+
+    while (scope != NULL) {
+        if (scope->size == size) {
+            return scope;
+        }
+        else if (size > scope->size) {
+            scope = scope->right;
+        }
+        else {
+            max = scope;
+            scope = scope->left;
+        }
+    }
+
+    return max;
+}
+
+void *
+lexbor_bst_remove(lexbor_bst_t *bst, lexbor_bst_entry_t **scope, size_t size)
+{
+    lexbor_bst_entry_t *entry = *scope;
+
+    while (entry != NULL) {
+        if (entry->size == size) {
+            return lexbor_bst_remove_by_pointer(bst, entry, scope);
+        }
+        else if (size > entry->size) {
+            entry = entry->right;
+        }
+        else {
+            entry = entry->left;
+        }
+    }
+
+    return NULL;
+}
+
+void *
+lexbor_bst_remove_close(lexbor_bst_t *bst, lexbor_bst_entry_t **scope,
+                        size_t size, size_t *found_size)
+{
+    lexbor_bst_entry_t *entry = *scope;
+    lexbor_bst_entry_t *max = NULL;
+
+    while (entry != NULL) {
+        if (entry->size == size) {
+            if (found_size) {
+                *found_size = entry->size;
+            }
+
+            return lexbor_bst_remove_by_pointer(bst, entry, scope);
+        }
+        else if (size > entry->size) {
+            entry = entry->right;
+        }
+        else {
+            max = entry;
+            entry = entry->left;
+        }
+    }
+
+    if (max != NULL) {
+        if (found_size != NULL) {
+            *found_size = max->size;
+        }
+
+        return lexbor_bst_remove_by_pointer(bst, max, scope);
+    }
+
+    if (found_size != NULL) {
+        *found_size = 0;
+    }
+
+    return NULL;
+}
+
+void *
+lexbor_bst_remove_by_pointer(lexbor_bst_t *bst, lexbor_bst_entry_t *entry,
+                             lexbor_bst_entry_t **root)
+{
+    void *value;
+    lexbor_bst_entry_t *next, *right, *left;
+
+    bst->tree_length--;
+
+    if (entry->next != NULL) {
+        next = entry->next;
+        entry->next = entry->next->next;
+
+        value = next->value;
+
+        lexbor_dobject_free(bst->dobject, next);
+
+        return value;
+    }
+
+    value = entry->value;
+
+    if (entry->left == NULL && entry->right == NULL) {
+        if (entry->parent != NULL) {
+            if (entry->parent->left == entry)  entry->parent->left  = NULL;
+            if (entry->parent->right == entry) entry->parent->right = NULL;
+        }
+        else {
+            *root = NULL;
+        }
+
+        lexbor_dobject_free(bst->dobject, entry);
+    }
+    else if (entry->left == NULL) {
+        if (entry->parent == NULL) {
+            entry->right->parent = NULL;
+
+            *root = entry->right;
+
+            lexbor_dobject_free(bst->dobject, entry);
+
+            entry = *root;
+        }
+        else {
+            right = entry->right;
+            right->parent = entry->parent;
+
+            memcpy(entry, right, sizeof(lexbor_bst_entry_t));
+
+            lexbor_dobject_free(bst->dobject, right);
+        }
+
+        if (entry->right != NULL) {
+            entry->right->parent = entry;
+        }
+
+        if (entry->left != NULL) {
+            entry->left->parent = entry;
+        }
+    }
+    else if (entry->right == NULL) {
+        if (entry->parent == NULL) {
+            entry->left->parent = NULL;
+
+            *root = entry->left;
+
+            lexbor_dobject_free(bst->dobject, entry);
+
+            entry = *root;
+        }
+        else {
+            left = entry->left;
+            left->parent = entry->parent;
+
+            memcpy(entry, left, sizeof(lexbor_bst_entry_t));
+
+            lexbor_dobject_free(bst->dobject, left);
+        }
+
+        if (entry->right != NULL) {
+            entry->right->parent = entry;
+        }
+
+        if (entry->left != NULL) {
+            entry->left->parent = entry;
+        }
+    }
+    else {
+        left = entry->right;
+
+        while (left->left != NULL) {
+            left = left->left;
+        }
+
+        /* Swap */
+        entry->size  = left->size;
+        entry->next  = left->next;
+        entry->value = left->value;
+
+        /* Change parrent */
+        if (entry->right == left) {
+            entry->right = left->right;
+
+            if (entry->right != NULL) {
+                left->right->parent = entry;
+            }
+        }
+        else {
+            left->parent->left = left->right;
+
+            if (left->right != NULL) {
+                left->right->parent = left->parent;
+            }
+        }
+
+        lexbor_dobject_free(bst->dobject, left);
+    }
+
+    return value;
+}
+
+void
+lexbor_bst_serialize(lexbor_bst_t *bst, lexbor_callback_f callback, void *ctx)
+{
+    lexbor_bst_serialize_entry(bst->root, callback, ctx, 0);
+}
+
+void
+lexbor_bst_serialize_entry(lexbor_bst_entry_t *entry,
+                           lexbor_callback_f callback, void *ctx, size_t tabs)
+{
+    size_t len;
+    lxb_char_t buff[1024];
+
+    if (entry == NULL) {
+        return;
+    }
+
+    /* Left */
+    for (size_t i = 0; i < tabs; i++) {
+        callback((lxb_char_t *) "\t", 1, ctx);
+    }
+    callback((lxb_char_t *) "<left ", 6, ctx);
+
+    if (entry->left) {
+        len = lexbor_conv_int64_to_data((int64_t) entry->left->size,
+                                        buff, sizeof(buff));
+        callback(buff, len, ctx);
+
+        callback((lxb_char_t *) ">\n", 2, ctx);
+        lexbor_bst_serialize_entry(entry->left, callback, ctx, (tabs + 1));
+
+        for (size_t i = 0; i < tabs; i++) {
+            callback((lxb_char_t *) "\t", 1, ctx);
+        }
+    }
+    else {
+        callback((lxb_char_t *) "NULL>", 5, ctx);
+    }
+
+    callback((lxb_char_t *) "</left>\n", 8, ctx);
+
+    /* Right */
+    for (size_t i = 0; i < tabs; i++) {
+        callback((lxb_char_t *) "\t", 1, ctx);
+    }
+    callback((lxb_char_t *) "<right ", 7, ctx);
+
+    if (entry->right) {
+        len = lexbor_conv_int64_to_data((int64_t) entry->right->size,
+                                        buff, sizeof(buff));
+        callback(buff, len, ctx);
+
+        callback((lxb_char_t *) ">\n", 2, ctx);
+        lexbor_bst_serialize_entry(entry->right, callback, ctx, (tabs + 1));
+
+        for (size_t i = 0; i < tabs; i++) {
+            callback((lxb_char_t *) "\t", 1, ctx);
+        }
+    }
+    else {
+        callback((lxb_char_t *) "NULL>", 5, ctx);
+    }
+
+    callback((lxb_char_t *) "</right>\n", 9, ctx);
+}
diff --git a/contrib/url/lexbor/core/bst.h b/contrib/url/lexbor/core/bst.h
new file mode 100644
index 0000000000..3d7bf88c38
--- /dev/null
+++ b/contrib/url/lexbor/core/bst.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#ifndef LEXBOR_BST_H
+#define LEXBOR_BST_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <string.h>
+
+#include "lexbor/core/base.h"
+#include "lexbor/core/dobject.h"
+
+
+#define lexbor_bst_root(bst) (bst)->root
+#define lexbor_bst_root_ref(bst) &((bst)->root)
+
+
+typedef struct lexbor_bst_entry lexbor_bst_entry_t;
+typedef struct lexbor_bst lexbor_bst_t;
+
+typedef bool (*lexbor_bst_entry_f)(lexbor_bst_t *bst,
+                                   lexbor_bst_entry_t *entry, void *ctx);
+
+struct lexbor_bst_entry {
+    void               *value;
+
+    lexbor_bst_entry_t *right;
+    lexbor_bst_entry_t *left;
+    lexbor_bst_entry_t *next;
+    lexbor_bst_entry_t *parent;
+
+    size_t             size;
+};
+
+struct lexbor_bst {
+    lexbor_dobject_t   *dobject;
+    lexbor_bst_entry_t *root;
+
+    size_t             tree_length;
+};
+
+
+LXB_API lexbor_bst_t *
+lexbor_bst_create(void);
+
+LXB_API lxb_status_t
+lexbor_bst_init(lexbor_bst_t *bst, size_t size);
+
+LXB_API void
+lexbor_bst_clean(lexbor_bst_t *bst);
+
+LXB_API lexbor_bst_t *
+lexbor_bst_destroy(lexbor_bst_t *bst, bool self_destroy);
+
+LXB_API lexbor_bst_entry_t *
+lexbor_bst_entry_make(lexbor_bst_t *bst, size_t size);
+
+LXB_API lexbor_bst_entry_t *
+lexbor_bst_insert(lexbor_bst_t *bst, lexbor_bst_entry_t **scope,
+                  size_t size, void *value);
+
+LXB_API lexbor_bst_entry_t *
+lexbor_bst_insert_not_exists(lexbor_bst_t *bst, lexbor_bst_entry_t **scope,
+                             size_t size);
+
+
+LXB_API lexbor_bst_entry_t *
+lexbor_bst_search(lexbor_bst_t *bst, lexbor_bst_entry_t *scope, size_t size);
+
+LXB_API lexbor_bst_entry_t *
+lexbor_bst_search_close(lexbor_bst_t *bst, lexbor_bst_entry_t *scope,
+                        size_t size);
+
+
+LXB_API void *
+lexbor_bst_remove(lexbor_bst_t *bst, lexbor_bst_entry_t **root, size_t size);
+
+LXB_API void *
+lexbor_bst_remove_close(lexbor_bst_t *bst, lexbor_bst_entry_t **root,
+                        size_t size, size_t *found_size);
+
+LXB_API void *
+lexbor_bst_remove_by_pointer(lexbor_bst_t *bst, lexbor_bst_entry_t *entry,
+                             lexbor_bst_entry_t **root);
+
+
+LXB_API void
+lexbor_bst_serialize(lexbor_bst_t *bst, lexbor_callback_f callback, void *ctx);
+
+LXB_API void
+lexbor_bst_serialize_entry(lexbor_bst_entry_t *entry,
+                           lexbor_callback_f callback, void *ctx, size_t tabs);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_BST_H */
+
+
+
diff --git a/contrib/url/lexbor/core/conv.c b/contrib/url/lexbor/core/conv.c
new file mode 100644
index 0000000000..5cc17c872f
--- /dev/null
+++ b/contrib/url/lexbor/core/conv.c
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#include <math.h>
+#include <float.h>
+
+#include "lexbor/core/conv.h"
+#include "lexbor/core/dtoa.h"
+#include "lexbor/core/strtod.h"
+
+
+size_t
+lexbor_conv_float_to_data(double num, lxb_char_t *buf, size_t len)
+{
+    return lexbor_dtoa(num, buf, len);
+}
+
+size_t
+lexbor_conv_long_to_data(long num, lxb_char_t *buf, size_t len)
+{
+    return lexbor_conv_int64_to_data((int64_t) num, buf, len);
+}
+
+size_t
+lexbor_conv_int64_to_data(int64_t num, lxb_char_t *buf, size_t len)
+{
+    int64_t tmp;
+    size_t have_minus, i, length;
+
+    static const lxb_char_t *digits = (const lxb_char_t *) "0123456789";
+
+    if (num != 0) {
+        tmp = num;
+        length = 0;
+        have_minus = 0;
+
+        if (num < 0) {
+            length = 1;
+            num = -num;
+            have_minus = 1;
+        }
+
+        while (tmp != 0) {
+            length += 1;
+            tmp /= 10;
+        }
+
+        /* length += (size_t) floor(log10(labs((long) num))) + 1; */
+    }
+    else {
+        if (len > 0) {
+            buf[0] = '0';
+            return 1;
+        }
+
+        return 0;
+    }
+
+    if (len < length) {
+        i = (length + have_minus) - len;
+
+        while (i != have_minus) {
+            i -= 1;
+            num /= 10;
+        }
+
+        length = len;
+    }
+
+    if (have_minus) {
+        buf[0] = '-';
+    }
+
+    i = length;
+    buf[length] = '\0';
+
+    while (i != have_minus) {
+        i -= 1;
+        buf[i] = digits[ num % 10 ];
+        num /= 10;
+    }
+
+    return length;
+}
+
+double
+lexbor_conv_data_to_double(const lxb_char_t **start, size_t len)
+{
+    int exponent, exp, insignf;
+    lxb_char_t c, *pos;
+    bool minus, ex_minus;
+    double num;
+    const lxb_char_t  *e, *p, *last, *end;
+    lxb_char_t data[128];
+
+    end = *start + len;
+
+    exponent = 0;
+    insignf = 0;
+
+    pos = data;
+    last = data + sizeof(data);
+
+    minus = false;
+
+    switch (**start) {
+        case '-':
+            minus = true;
+            /* fall through */
+        case '+':
+            (*start)++;
+            /* fall through */
+        default:
+            break;
+    }
+
+    for (p = *start; p < end; p++) {
+        /* Values less than '0' become >= 208. */
+        c = *p - '0';
+
+        if (c > 9) {
+            break;
+        }
+
+        if (pos < last) {
+            *pos++ = *p;
+        }
+        else {
+            insignf++;
+        }
+    }
+
+    /* Do not emit a '.', but adjust the exponent instead. */
+    if (p < end && *p == '.') {
+
+        for (p++; p < end; p++) {
+            /* Values less than '0' become >= 208. */
+            c = *p - '0';
+
+            if (c > 9) {
+                break;
+            }
+
+            if (pos < last) {
+                *pos++ = *p;
+                exponent--;
+            }
+            else {
+                /* Ignore insignificant digits in the fractional part. */
+            }
+        }
+    }
+
+    e = p + 1;
+
+    if (e < end && (*p == 'e' || *p == 'E')) {
+        ex_minus = 0;
+
+        if (e + 1 < end) {
+            if (*e == '-') {
+                e++;
+                ex_minus = 1;
+            }
+            else if (*e == '+') {
+                e++;
+            }
+        }
+
+        /* Values less than '0' become >= 208. */
+        c = *e - '0';
+
+        if (c <= 9) {
+            exp = c;
+
+            for (p = e + 1; p < end; p++) {
+                /* Values less than '0' become >= 208. */
+                c = *p - '0';
+
+                if (c > 9) {
+                    break;
+                }
+
+                exp = exp * 10 + c;
+            }
+
+            exponent += ex_minus ? -exp : exp;
+        }
+    }
+
+    *start = p;
+
+    exponent += insignf;
+
+    num = lexbor_strtod_internal(data, pos - data, exponent);
+
+    if (minus) {
+        num = -num;
+    }
+
+    return num;
+}
+
+unsigned long
+lexbor_conv_data_to_ulong(const lxb_char_t **data, size_t length)
+{
+    const lxb_char_t *p = *data;
+    const lxb_char_t *end = p + length;
+    unsigned long last_number = 0, number = 0;
+
+    for (; p < end; p++) {
+        if (*p < '0' || *p > '9') {
+            goto done;
+        }
+
+        number = (*p - '0') + number * 10;
+
+        if (last_number > number) {
+            *data = p - 1;
+            return last_number;
+        }
+
+        last_number = number;
+    }
+
+done:
+
+    *data = p;
+
+    return number;
+}
+
+long
+lexbor_conv_data_to_long(const lxb_char_t **data, size_t length)
+{
+    bool minus;
+    const lxb_char_t *p;
+    const lxb_char_t *end;
+    unsigned long n = 0, number = 0;
+
+    minus = false;
+    p = *data;
+    end = p + length;
+
+    switch (*p) {
+        case '-':
+            minus = true;
+            /* fall through */
+        case '+':
+            p++;
+            /* fall through */
+        default:
+            break;
+    }
+
+    for (; p < end; p++) {
+        if (*p < '0' || *p > '9') {
+            break;
+        }
+
+        n = (*p - '0') + number * 10;
+
+        if (n > LONG_MAX) {
+            p -= 1;
+            break;
+        }
+
+        number = n;
+    }
+
+    *data = p;
+
+    return (minus) ? -number : number;
+}
+
+unsigned
+lexbor_conv_data_to_uint(const lxb_char_t **data, size_t length)
+{
+    const lxb_char_t *p = *data;
+    const lxb_char_t *end = p + length;
+    unsigned last_number = 0, number = 0;
+
+    for (; p < end; p++) {
+        if (*p < '0' || *p > '9') {
+            goto done;
+        }
+
+        number = (*p - '0') + number * 10;
+
+        if (last_number > number) {
+            *data = p - 1;
+            return last_number;
+        }
+
+        last_number = number;
+    }
+
+done:
+
+    *data = p;
+
+    return number;
+}
+
+size_t
+lexbor_conv_dec_to_hex(uint32_t number, lxb_char_t *out, size_t length)
+{
+    lxb_char_t c;
+    size_t len;
+    uint32_t tmp;
+
+    static const lxb_char_t map_str[] = "0123456789abcdef";
+
+    if(number != 0) {
+        tmp = number;
+        len = 0;
+
+        while (tmp != 0) {
+            len += 1;
+            tmp /= 16;
+        }
+
+        /* len = (size_t) floor(log10(labs((long) number))) + 1; */
+    }
+    else {
+        if (length > 0) {
+            out[0] = '0';
+            return 1;
+        }
+
+        return 0;
+    }
+
+    length = len - 1;
+
+    while (number != 0) {
+        c = number % 16;
+        number = number / 16;
+
+        out[ length-- ] = map_str[c];
+    }
+
+    return len;
+}
diff --git a/contrib/url/lexbor/core/conv.h b/contrib/url/lexbor/core/conv.h
new file mode 100644
index 0000000000..f902cd5964
--- /dev/null
+++ b/contrib/url/lexbor/core/conv.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#ifndef LEXBOR_CONV_H
+#define LEXBOR_CONV_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#include "lexbor/core/base.h"
+
+
+LXB_API size_t
+lexbor_conv_float_to_data(double num, lxb_char_t *buf, size_t len);
+
+LXB_API size_t
+lexbor_conv_long_to_data(long num, lxb_char_t *buf, size_t len);
+
+LXB_API size_t
+lexbor_conv_int64_to_data(int64_t num, lxb_char_t *buf, size_t len);
+
+LXB_API double
+lexbor_conv_data_to_double(const lxb_char_t **start, size_t len);
+
+LXB_API unsigned long
+lexbor_conv_data_to_ulong(const lxb_char_t **data, size_t length);
+
+LXB_API long
+lexbor_conv_data_to_long(const lxb_char_t **data, size_t length);
+
+LXB_API unsigned
+lexbor_conv_data_to_uint(const lxb_char_t **data, size_t length);
+
+LXB_API size_t
+lexbor_conv_dec_to_hex(uint32_t number, lxb_char_t *out, size_t length);
+
+lxb_inline long
+lexbor_conv_double_to_long(double number)
+{
+    if (number > (double) LONG_MAX) {
+        return LONG_MAX;
+    }
+
+    if (number < (double) LONG_MIN) {
+        return -LONG_MAX;
+    }
+
+    return (long) number;
+}
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_CONV_H */
diff --git a/contrib/url/lexbor/core/def.h b/contrib/url/lexbor/core/def.h
new file mode 100644
index 0000000000..5a48f59e99
--- /dev/null
+++ b/contrib/url/lexbor/core/def.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#ifndef LEXBOR_DEF_H
+#define LEXBOR_DEF_H
+
+#define LEXBOR_STRINGIZE_HELPER(x) #x
+#define LEXBOR_STRINGIZE(x) LEXBOR_STRINGIZE_HELPER(x)
+
+/* Format */
+#ifdef _WIN32
+    #define LEXBOR_FORMAT_Z "%Iu"
+#else
+    #define LEXBOR_FORMAT_Z "%zu"
+#endif
+
+/* Deprecated */
+#ifdef _MSC_VER
+    #define LXB_DEPRECATED(func) __declspec(deprecated) func
+#elif defined(__GNUC__) || defined(__INTEL_COMPILER)
+    #define LXB_DEPRECATED(func) func __attribute__((deprecated))
+#else
+    #define LXB_DEPRECATED(func) func
+#endif
+
+/* Debug */
+//#define LEXBOR_DEBUG(...) do {} while (0)
+//#define LEXBOR_DEBUG_ERROR(...) do {} while (0)
+
+#define LEXBOR_MEM_ALIGN_STEP sizeof(void *)
+
+#ifndef LEXBOR_STATIC
+    #ifdef _WIN32
+        #ifdef LEXBOR_BUILDING
+            #define LXB_API __declspec(dllexport)
+        #else
+            #define LXB_API __declspec(dllimport)
+        #endif
+    #elif (defined(__SUNPRO_C)  || defined(__SUNPRO_CC))
+        #define LXB_API __global
+    #else
+        #if (defined(__GNUC__) && __GNUC__ >= 4) || defined(__INTEL_COMPILER)
+            #define LXB_API __attribute__ ((visibility("default")))
+        #else
+            #define LXB_API
+        #endif
+    #endif
+#else
+    #define LXB_API
+#endif
+
+#define LXB_EXTERN extern LXB_API
+
+#endif /* LEXBOR_DEF_H */
diff --git a/contrib/url/lexbor/core/diyfp.c b/contrib/url/lexbor/core/diyfp.c
new file mode 100644
index 0000000000..62020bbc64
--- /dev/null
+++ b/contrib/url/lexbor/core/diyfp.c
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) Alexander Borisov
+ *
+ * Based on nxt_diyfp.c from NGINX NJS project
+ *
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) NGINX, Inc.
+ *
+ * An internal diy_fp implementation.
+ * For details, see Loitsch, Florian. "Printing floating-point numbers quickly
+ * and accurately with integers." ACM Sigplan Notices 45.6 (2010): 233-243.
+ */
+
+#include "lexbor/core/diyfp.h"
+
+
+typedef struct {
+  uint64_t significand;
+  int16_t  bin_exp;
+  int16_t  dec_exp;
+}
+lexbor_diyfp_cpe_t;
+
+
+static const lexbor_diyfp_cpe_t lexbor_cached_powers[] = {
+  { lexbor_uint64_hl(0xfa8fd5a0, 0x081c0288), -1220, -348 },
+  { lexbor_uint64_hl(0xbaaee17f, 0xa23ebf76), -1193, -340 },
+  { lexbor_uint64_hl(0x8b16fb20, 0x3055ac76), -1166, -332 },
+  { lexbor_uint64_hl(0xcf42894a, 0x5dce35ea), -1140, -324 },
+  { lexbor_uint64_hl(0x9a6bb0aa, 0x55653b2d), -1113, -316 },
+  { lexbor_uint64_hl(0xe61acf03, 0x3d1a45df), -1087, -308 },
+  { lexbor_uint64_hl(0xab70fe17, 0xc79ac6ca), -1060, -300 },
+  { lexbor_uint64_hl(0xff77b1fc, 0xbebcdc4f), -1034, -292 },
+  { lexbor_uint64_hl(0xbe5691ef, 0x416bd60c), -1007, -284 },
+  { lexbor_uint64_hl(0x8dd01fad, 0x907ffc3c),  -980, -276 },
+  { lexbor_uint64_hl(0xd3515c28, 0x31559a83),  -954, -268 },
+  { lexbor_uint64_hl(0x9d71ac8f, 0xada6c9b5),  -927, -260 },
+  { lexbor_uint64_hl(0xea9c2277, 0x23ee8bcb),  -901, -252 },
+  { lexbor_uint64_hl(0xaecc4991, 0x4078536d),  -874, -244 },
+  { lexbor_uint64_hl(0x823c1279, 0x5db6ce57),  -847, -236 },
+  { lexbor_uint64_hl(0xc2109436, 0x4dfb5637),  -821, -228 },
+  { lexbor_uint64_hl(0x9096ea6f, 0x3848984f),  -794, -220 },
+  { lexbor_uint64_hl(0xd77485cb, 0x25823ac7),  -768, -212 },
+  { lexbor_uint64_hl(0xa086cfcd, 0x97bf97f4),  -741, -204 },
+  { lexbor_uint64_hl(0xef340a98, 0x172aace5),  -715, -196 },
+  { lexbor_uint64_hl(0xb23867fb, 0x2a35b28e),  -688, -188 },
+  { lexbor_uint64_hl(0x84c8d4df, 0xd2c63f3b),  -661, -180 },
+  { lexbor_uint64_hl(0xc5dd4427, 0x1ad3cdba),  -635, -172 },
+  { lexbor_uint64_hl(0x936b9fce, 0xbb25c996),  -608, -164 },
+  { lexbor_uint64_hl(0xdbac6c24, 0x7d62a584),  -582, -156 },
+  { lexbor_uint64_hl(0xa3ab6658, 0x0d5fdaf6),  -555, -148 },
+  { lexbor_uint64_hl(0xf3e2f893, 0xdec3f126),  -529, -140 },
+  { lexbor_uint64_hl(0xb5b5ada8, 0xaaff80b8),  -502, -132 },
+  { lexbor_uint64_hl(0x87625f05, 0x6c7c4a8b),  -475, -124 },
+  { lexbor_uint64_hl(0xc9bcff60, 0x34c13053),  -449, -116 },
+  { lexbor_uint64_hl(0x964e858c, 0x91ba2655),  -422, -108 },
+  { lexbor_uint64_hl(0xdff97724, 0x70297ebd),  -396, -100 },
+  { lexbor_uint64_hl(0xa6dfbd9f, 0xb8e5b88f),  -369,  -92 },
+  { lexbor_uint64_hl(0xf8a95fcf, 0x88747d94),  -343,  -84 },
+  { lexbor_uint64_hl(0xb9447093, 0x8fa89bcf),  -316,  -76 },
+  { lexbor_uint64_hl(0x8a08f0f8, 0xbf0f156b),  -289,  -68 },
+  { lexbor_uint64_hl(0xcdb02555, 0x653131b6),  -263,  -60 },
+  { lexbor_uint64_hl(0x993fe2c6, 0xd07b7fac),  -236,  -52 },
+  { lexbor_uint64_hl(0xe45c10c4, 0x2a2b3b06),  -210,  -44 },
+  { lexbor_uint64_hl(0xaa242499, 0x697392d3),  -183,  -36 },
+  { lexbor_uint64_hl(0xfd87b5f2, 0x8300ca0e),  -157,  -28 },
+  { lexbor_uint64_hl(0xbce50864, 0x92111aeb),  -130,  -20 },
+  { lexbor_uint64_hl(0x8cbccc09, 0x6f5088cc),  -103,  -12 },
+  { lexbor_uint64_hl(0xd1b71758, 0xe219652c),   -77,   -4 },
+  { lexbor_uint64_hl(0x9c400000, 0x00000000),   -50,    4 },
+  { lexbor_uint64_hl(0xe8d4a510, 0x00000000),   -24,   12 },
+  { lexbor_uint64_hl(0xad78ebc5, 0xac620000),     3,   20 },
+  { lexbor_uint64_hl(0x813f3978, 0xf8940984),    30,   28 },
+  { lexbor_uint64_hl(0xc097ce7b, 0xc90715b3),    56,   36 },
+  { lexbor_uint64_hl(0x8f7e32ce, 0x7bea5c70),    83,   44 },
+  { lexbor_uint64_hl(0xd5d238a4, 0xabe98068),   109,   52 },
+  { lexbor_uint64_hl(0x9f4f2726, 0x179a2245),   136,   60 },
+  { lexbor_uint64_hl(0xed63a231, 0xd4c4fb27),   162,   68 },
+  { lexbor_uint64_hl(0xb0de6538, 0x8cc8ada8),   189,   76 },
+  { lexbor_uint64_hl(0x83c7088e, 0x1aab65db),   216,   84 },
+  { lexbor_uint64_hl(0xc45d1df9, 0x42711d9a),   242,   92 },
+  { lexbor_uint64_hl(0x924d692c, 0xa61be758),   269,  100 },
+  { lexbor_uint64_hl(0xda01ee64, 0x1a708dea),   295,  108 },
+  { lexbor_uint64_hl(0xa26da399, 0x9aef774a),   322,  116 },
+  { lexbor_uint64_hl(0xf209787b, 0xb47d6b85),   348,  124 },
+  { lexbor_uint64_hl(0xb454e4a1, 0x79dd1877),   375,  132 },
+  { lexbor_uint64_hl(0x865b8692, 0x5b9bc5c2),   402,  140 },
+  { lexbor_uint64_hl(0xc83553c5, 0xc8965d3d),   428,  148 },
+  { lexbor_uint64_hl(0x952ab45c, 0xfa97a0b3),   455,  156 },
+  { lexbor_uint64_hl(0xde469fbd, 0x99a05fe3),   481,  164 },
+  { lexbor_uint64_hl(0xa59bc234, 0xdb398c25),   508,  172 },
+  { lexbor_uint64_hl(0xf6c69a72, 0xa3989f5c),   534,  180 },
+  { lexbor_uint64_hl(0xb7dcbf53, 0x54e9bece),   561,  188 },
+  { lexbor_uint64_hl(0x88fcf317, 0xf22241e2),   588,  196 },
+  { lexbor_uint64_hl(0xcc20ce9b, 0xd35c78a5),   614,  204 },
+  { lexbor_uint64_hl(0x98165af3, 0x7b2153df),   641,  212 },
+  { lexbor_uint64_hl(0xe2a0b5dc, 0x971f303a),   667,  220 },
+  { lexbor_uint64_hl(0xa8d9d153, 0x5ce3b396),   694,  228 },
+  { lexbor_uint64_hl(0xfb9b7cd9, 0xa4a7443c),   720,  236 },
+  { lexbor_uint64_hl(0xbb764c4c, 0xa7a44410),   747,  244 },
+  { lexbor_uint64_hl(0x8bab8eef, 0xb6409c1a),   774,  252 },
+  { lexbor_uint64_hl(0xd01fef10, 0xa657842c),   800,  260 },
+  { lexbor_uint64_hl(0x9b10a4e5, 0xe9913129),   827,  268 },
+  { lexbor_uint64_hl(0xe7109bfb, 0xa19c0c9d),   853,  276 },
+  { lexbor_uint64_hl(0xac2820d9, 0x623bf429),   880,  284 },
+  { lexbor_uint64_hl(0x80444b5e, 0x7aa7cf85),   907,  292 },
+  { lexbor_uint64_hl(0xbf21e440, 0x03acdd2d),   933,  300 },
+  { lexbor_uint64_hl(0x8e679c2f, 0x5e44ff8f),   960,  308 },
+  { lexbor_uint64_hl(0xd433179d, 0x9c8cb841),   986,  316 },
+  { lexbor_uint64_hl(0x9e19db92, 0xb4e31ba9),  1013,  324 },
+  { lexbor_uint64_hl(0xeb96bf6e, 0xbadf77d9),  1039,  332 },
+  { lexbor_uint64_hl(0xaf87023b, 0x9bf0ee6b),  1066,  340 },
+};
+
+
+#define LEXBOR_DIYFP_D_1_LOG2_10 0.30102999566398114 /* 1 / log2(10). */
+
+
+lexbor_diyfp_t
+lexbor_cached_power_dec(int exp, int *dec_exp)
+{
+    unsigned int index;
+    const lexbor_diyfp_cpe_t *cp;
+
+    index = (exp + LEXBOR_DECIMAL_EXPONENT_OFF) / LEXBOR_DECIMAL_EXPONENT_DIST;
+    cp = &lexbor_cached_powers[index];
+
+    *dec_exp = cp->dec_exp;
+
+    return lexbor_diyfp(cp->significand, cp->bin_exp);
+}
+
+lexbor_diyfp_t
+lexbor_cached_power_bin(int exp, int *dec_exp)
+{
+    int k;
+    unsigned int index;
+    const lexbor_diyfp_cpe_t *cp;
+
+    k = (int) ceil((-61 - exp) * LEXBOR_DIYFP_D_1_LOG2_10)
+        + LEXBOR_DECIMAL_EXPONENT_OFF - 1;
+
+    index = (unsigned) (k >> 3) + 1;
+
+    cp = &lexbor_cached_powers[index];
+
+    *dec_exp = -(LEXBOR_DECIMAL_EXPONENT_MIN + (int) (index << 3));
+
+    return lexbor_diyfp(cp->significand, cp->bin_exp);
+}
+
+
+#undef LEXBOR_DIYFP_D_1_LOG2_10
diff --git a/contrib/url/lexbor/core/diyfp.h b/contrib/url/lexbor/core/diyfp.h
new file mode 100644
index 0000000000..47fedb9da9
--- /dev/null
+++ b/contrib/url/lexbor/core/diyfp.h
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) Alexander Borisov
+ *
+ * Based on nxt_diyfp.h from NGINX NJS project
+ *
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) NGINX, Inc.
+ *
+ * An internal diy_fp implementation.
+ * For details, see Loitsch, Florian. "Printing floating-point numbers quickly
+ * and accurately with integers." ACM Sigplan Notices 45.6 (2010): 233-243.
+ */
+
+#ifndef LEXBOR_DIYFP_H
+#define LEXBOR_DIYFP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "lexbor/core/base.h"
+
+#include <math.h>
+
+
+#ifdef __cplusplus
+#define lexbor_diyfp(_s, _e)           { .significand = (_s), .exp = (int) (_e) }
+#else
+#define lexbor_diyfp(_s, _e)           (lexbor_diyfp_t)                        \
+                                       { .significand = (_s), .exp = (int) (_e) }
+#endif
+#define lexbor_uint64_hl(h, l)   (((uint64_t) (h) << 32) + (l))
+
+
+#define LEXBOR_DBL_SIGNIFICAND_SIZE    52
+#define LEXBOR_DBL_EXPONENT_BIAS       (0x3FF + LEXBOR_DBL_SIGNIFICAND_SIZE)
+#define LEXBOR_DBL_EXPONENT_MIN        (-LEXBOR_DBL_EXPONENT_BIAS)
+#define LEXBOR_DBL_EXPONENT_MAX        (0x7FF - LEXBOR_DBL_EXPONENT_BIAS)
+#define LEXBOR_DBL_EXPONENT_DENORMAL   (-LEXBOR_DBL_EXPONENT_BIAS + 1)
+
+#define LEXBOR_DBL_SIGNIFICAND_MASK    lexbor_uint64_hl(0x000FFFFF, 0xFFFFFFFF)
+#define LEXBOR_DBL_HIDDEN_BIT          lexbor_uint64_hl(0x00100000, 0x00000000)
+#define LEXBOR_DBL_EXPONENT_MASK       lexbor_uint64_hl(0x7FF00000, 0x00000000)
+
+#define LEXBOR_DIYFP_SIGNIFICAND_SIZE  64
+
+#define LEXBOR_SIGNIFICAND_SIZE        53
+#define LEXBOR_SIGNIFICAND_SHIFT       (LEXBOR_DIYFP_SIGNIFICAND_SIZE          \
+                                       - LEXBOR_DBL_SIGNIFICAND_SIZE)
+
+#define LEXBOR_DECIMAL_EXPONENT_OFF    348
+#define LEXBOR_DECIMAL_EXPONENT_MIN    (-348)
+#define LEXBOR_DECIMAL_EXPONENT_MAX    340
+#define LEXBOR_DECIMAL_EXPONENT_DIST   8
+
+
+typedef struct {
+    uint64_t significand;
+    int      exp;
+}
+lexbor_diyfp_t;
+
+
+LXB_API lexbor_diyfp_t
+lexbor_cached_power_dec(int exp, int *dec_exp);
+
+LXB_API lexbor_diyfp_t
+lexbor_cached_power_bin(int exp, int *dec_exp);
+
+
+/*
+ * Inline functions
+ */
+#ifdef LEXBOR_HAVE_BUILTIN_CLZLL
+#define nxt_leading_zeros64(x)  (((x) == 0) ? 64 : __builtin_clzll(x))
+
+#else
+
+lxb_inline uint64_t
+lexbor_diyfp_leading_zeros64(uint64_t x)
+{
+    uint64_t  n;
+
+    if (x == 0) {
+        return 64;
+    }
+
+    n = 0;
+
+    while ((x & 0x8000000000000000) == 0) {
+        n++;
+        x <<= 1;
+    }
+
+    return n;
+}
+
+#endif
+
+lxb_inline lexbor_diyfp_t
+lexbor_diyfp_from_d2(double d)
+{
+    int biased_exp;
+    uint64_t significand;
+    lexbor_diyfp_t r;
+
+    union {
+        double   d;
+        uint64_t u64;
+    }
+    u;
+
+    u.d = d;
+
+    biased_exp = (u.u64 & LEXBOR_DBL_EXPONENT_MASK)
+                 >> LEXBOR_DBL_SIGNIFICAND_SIZE;
+    significand = u.u64 & LEXBOR_DBL_SIGNIFICAND_MASK;
+
+    if (biased_exp != 0) {
+        r.significand = significand + LEXBOR_DBL_HIDDEN_BIT;
+        r.exp = biased_exp - LEXBOR_DBL_EXPONENT_BIAS;
+    }
+    else {
+        r.significand = significand;
+        r.exp = LEXBOR_DBL_EXPONENT_MIN + 1;
+    }
+
+    return r;
+}
+
+lxb_inline double
+lexbor_diyfp_2d(lexbor_diyfp_t v)
+{
+    int exp;
+    uint64_t significand, biased_exp;
+
+    union {
+        double   d;
+        uint64_t u64;
+    }
+    u;
+
+    exp = v.exp;
+    significand = v.significand;
+
+    while (significand > LEXBOR_DBL_HIDDEN_BIT + LEXBOR_DBL_SIGNIFICAND_MASK) {
+        significand >>= 1;
+        exp++;
+    }
+
+    if (exp >= LEXBOR_DBL_EXPONENT_MAX) {
+        return INFINITY;
+    }
+
+    if (exp < LEXBOR_DBL_EXPONENT_DENORMAL) {
+        return 0.0;
+    }
+
+    while (exp > LEXBOR_DBL_EXPONENT_DENORMAL
+           && (significand & LEXBOR_DBL_HIDDEN_BIT) == 0)
+    {
+        significand <<= 1;
+        exp--;
+    }
+
+    if (exp == LEXBOR_DBL_EXPONENT_DENORMAL
+        && (significand & LEXBOR_DBL_HIDDEN_BIT) == 0)
+    {
+        biased_exp = 0;
+
+    } else {
+        biased_exp = (uint64_t) (exp + LEXBOR_DBL_EXPONENT_BIAS);
+    }
+
+    u.u64 = (significand & LEXBOR_DBL_SIGNIFICAND_MASK)
+            | (biased_exp << LEXBOR_DBL_SIGNIFICAND_SIZE);
+
+    return u.d;
+}
+
+lxb_inline lexbor_diyfp_t
+lexbor_diyfp_shift_left(lexbor_diyfp_t v, unsigned shift)
+{
+    return lexbor_diyfp(v.significand << shift, v.exp - shift);
+}
+
+lxb_inline lexbor_diyfp_t
+lexbor_diyfp_shift_right(lexbor_diyfp_t v, unsigned shift)
+{
+    return lexbor_diyfp(v.significand >> shift, v.exp + shift);
+}
+
+lxb_inline lexbor_diyfp_t
+lexbor_diyfp_sub(lexbor_diyfp_t lhs, lexbor_diyfp_t rhs)
+{
+    return lexbor_diyfp(lhs.significand - rhs.significand, lhs.exp);
+}
+
+lxb_inline lexbor_diyfp_t
+lexbor_diyfp_mul(lexbor_diyfp_t lhs, lexbor_diyfp_t rhs)
+{
+#ifdef LEXBOR_HAVE_UNSIGNED_INT128
+
+    uint64_t l, h;
+    lxb_uint128_t u128;
+
+    u128 = (lxb_uint128_t) (lhs.significand)
+           * (lxb_uint128_t) (rhs.significand);
+
+    h = u128 >> 64;
+    l = (uint64_t) u128;
+
+    /* rounding. */
+
+    if (l & ((uint64_t) 1 << 63)) {
+        h++;
+    }
+
+    return lexbor_diyfp(h, lhs.exp + rhs.exp + 64);
+
+#else
+
+    uint64_t a, b, c, d, ac, bc, ad, bd, tmp;
+
+    a = lhs.significand >> 32;
+    b = lhs.significand & 0xffffffff;
+    c = rhs.significand >> 32;
+    d = rhs.significand & 0xffffffff;
+
+    ac = a * c;
+    bc = b * c;
+    ad = a * d;
+    bd = b * d;
+
+    tmp = (bd >> 32) + (ad & 0xffffffff) + (bc & 0xffffffff);
+
+    /* mult_round. */
+
+    tmp += 1U << 31;
+
+    return lexbor_diyfp(ac + (ad >> 32) + (bc >> 32) + (tmp >> 32),
+                        lhs.exp + rhs.exp + 64);
+#endif
+}
+
+lxb_inline lexbor_diyfp_t
+lexbor_diyfp_normalize(lexbor_diyfp_t v)
+{
+    return lexbor_diyfp_shift_left(v,
+                        (unsigned) lexbor_diyfp_leading_zeros64(v.significand));
+}
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_DIYFP_H */
diff --git a/contrib/url/lexbor/core/dobject.c b/contrib/url/lexbor/core/dobject.c
new file mode 100644
index 0000000000..0f5468fd58
--- /dev/null
+++ b/contrib/url/lexbor/core/dobject.c
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2018-2019 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#include "lexbor/core/dobject.h"
+
+
+#if defined(LEXBOR_HAVE_ADDRESS_SANITIZER)
+    #include <sanitizer/asan_interface.h>
+#endif
+
+
+lexbor_dobject_t *
+lexbor_dobject_create(void)
+{
+    return lexbor_calloc(1, sizeof(lexbor_dobject_t));
+}
+
+lxb_status_t
+lexbor_dobject_init(lexbor_dobject_t *dobject,
+                    size_t chunk_size, size_t struct_size)
+{
+    lxb_status_t status;
+
+    if (dobject == NULL) {
+        return LXB_STATUS_ERROR_OBJECT_IS_NULL;
+    }
+
+    if (chunk_size == 0 || struct_size == 0) {
+        return LXB_STATUS_ERROR_WRONG_ARGS;
+    }
+
+    /* Set params */
+    dobject->allocated = 0UL;
+    dobject->struct_size = struct_size;
+
+    /* Init memory */
+    dobject->mem = lexbor_mem_create();
+
+    status = lexbor_mem_init(dobject->mem,
+                           lexbor_mem_align(chunk_size * dobject->struct_size));
+    if (status) {
+        return status;
+    }
+
+#if defined(LEXBOR_HAVE_ADDRESS_SANITIZER)
+    ASAN_POISON_MEMORY_REGION(dobject->mem->chunk->data,
+                              dobject->mem->chunk->size);
+#endif
+
+    /* Array */
+    dobject->cache = lexbor_array_create();
+
+    status = lexbor_array_init(dobject->cache, chunk_size);
+    if (status)
+        return status;
+
+    return LXB_STATUS_OK;
+}
+
+void
+lexbor_dobject_clean(lexbor_dobject_t *dobject)
+{
+    if (dobject != NULL) {
+        dobject->allocated = 0UL;
+
+        lexbor_mem_clean(dobject->mem);
+        lexbor_array_clean(dobject->cache);
+    }
+}
+
+lexbor_dobject_t *
+lexbor_dobject_destroy(lexbor_dobject_t *dobject, bool destroy_self)
+{
+    if (dobject == NULL)
+        return NULL;
+
+    dobject->mem = lexbor_mem_destroy(dobject->mem, true);
+    dobject->cache = lexbor_array_destroy(dobject->cache, true);
+
+    if (destroy_self == true) {
+        return lexbor_free(dobject);
+    }
+
+    return dobject;
+}
+
+void *
+lexbor_dobject_alloc(lexbor_dobject_t *dobject)
+{
+    void *data;
+
+    if (lexbor_array_length(dobject->cache) != 0) {
+        dobject->allocated++;
+
+#if defined(LEXBOR_HAVE_ADDRESS_SANITIZER)
+        data = lexbor_array_pop(dobject->cache);
+        ASAN_UNPOISON_MEMORY_REGION(data, dobject->struct_size);
+
+        return data;
+#else
+        return lexbor_array_pop(dobject->cache);
+#endif
+    }
+
+    data = lexbor_mem_alloc(dobject->mem, dobject->struct_size);
+    if (data == NULL) {
+        return NULL;
+    }
+
+#if defined(LEXBOR_HAVE_ADDRESS_SANITIZER)
+    ASAN_UNPOISON_MEMORY_REGION(data, dobject->struct_size);
+#endif
+
+    dobject->allocated++;
+
+    return data;
+}
+
+void *
+lexbor_dobject_calloc(lexbor_dobject_t *dobject)
+{
+    void *data = lexbor_dobject_alloc(dobject);
+
+    if (data != NULL) {
+        memset(data, 0, dobject->struct_size);
+    }
+
+    return data;
+}
+
+void *
+lexbor_dobject_free(lexbor_dobject_t *dobject, void *data)
+{
+    if (data == NULL) {
+        return NULL;
+    }
+
+#if defined(LEXBOR_HAVE_ADDRESS_SANITIZER)
+    ASAN_POISON_MEMORY_REGION(data, dobject->struct_size);
+#endif
+
+    if (lexbor_array_push(dobject->cache, data) == LXB_STATUS_OK) {
+        dobject->allocated--;
+        return NULL;
+    }
+
+    return data;
+}
+
+void *
+lexbor_dobject_by_absolute_position(lexbor_dobject_t *dobject, size_t pos)
+{
+    size_t chunk_idx, chunk_pos, i;
+    lexbor_mem_chunk_t *chunk;
+
+    if (pos >= dobject->allocated) {
+        return NULL;
+    }
+
+    chunk = dobject->mem->chunk_first;
+    chunk_pos = pos * dobject->struct_size;
+    chunk_idx = chunk_pos / dobject->mem->chunk_min_size;
+
+    for (i = 0; i < chunk_idx; i++) {
+        chunk = chunk->next;
+    }
+
+    return &chunk->data[chunk_pos % chunk->size];
+}
+
+/*
+ * No inline functions for ABI.
+ */
+size_t
+lexbor_dobject_allocated_noi(lexbor_dobject_t *dobject)
+{
+    return lexbor_dobject_allocated(dobject);
+}
+
+size_t
+lexbor_dobject_cache_length_noi(lexbor_dobject_t *dobject)
+{
+    return lexbor_dobject_cache_length(dobject);
+}
diff --git a/contrib/url/lexbor/core/dobject.h b/contrib/url/lexbor/core/dobject.h
new file mode 100644
index 0000000000..c92930cfa9
--- /dev/null
+++ b/contrib/url/lexbor/core/dobject.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#ifndef LEXBOR_DOBJECT_H
+#define LEXBOR_DOBJECT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "lexbor/core/base.h"
+#include "lexbor/core/mem.h"
+#include "lexbor/core/array.h"
+
+
+typedef struct {
+    lexbor_mem_t   *mem;
+    lexbor_array_t *cache;
+
+    size_t         allocated;
+    size_t         struct_size;
+}
+lexbor_dobject_t;
+
+
+LXB_API lexbor_dobject_t *
+lexbor_dobject_create(void);
+
+LXB_API lxb_status_t
+lexbor_dobject_init(lexbor_dobject_t *dobject,
+                    size_t chunk_size, size_t struct_size);
+
+LXB_API void
+lexbor_dobject_clean(lexbor_dobject_t *dobject);
+
+LXB_API lexbor_dobject_t *
+lexbor_dobject_destroy(lexbor_dobject_t *dobject, bool destroy_self);
+
+
+LXB_API uint8_t *
+lexbor_dobject_init_list_entries(lexbor_dobject_t *dobject, size_t pos);
+
+
+LXB_API void *
+lexbor_dobject_alloc(lexbor_dobject_t *dobject);
+
+LXB_API void *
+lexbor_dobject_calloc(lexbor_dobject_t *dobject);
+
+LXB_API void *
+lexbor_dobject_free(lexbor_dobject_t *dobject, void *data);
+
+
+LXB_API void *
+lexbor_dobject_by_absolute_position(lexbor_dobject_t *dobject, size_t pos);
+
+
+/*
+ * Inline functions
+ */
+lxb_inline size_t
+lexbor_dobject_allocated(lexbor_dobject_t *dobject)
+{
+    return dobject->allocated;
+}
+
+lxb_inline size_t
+lexbor_dobject_cache_length(lexbor_dobject_t *dobject)
+{
+    return lexbor_array_length(dobject->cache);
+}
+
+/*
+ * No inline functions for ABI.
+ */
+LXB_API size_t
+lexbor_dobject_allocated_noi(lexbor_dobject_t *dobject);
+
+LXB_API size_t
+lexbor_dobject_cache_length_noi(lexbor_dobject_t *dobject);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_DOBJECT_H */
+
+
diff --git a/contrib/url/lexbor/core/dtoa.c b/contrib/url/lexbor/core/dtoa.c
new file mode 100644
index 0000000000..b1a4ee0325
--- /dev/null
+++ b/contrib/url/lexbor/core/dtoa.c
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) Alexander Borisov
+ *
+ * Based on nxt_dtoa.c from NGINX NJS project
+ *
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) NGINX, Inc.
+ *
+ * Grisu2 algorithm implementation for printing floating-point numbers based
+ * upon the work of Milo Yip and Doug Currie.
+ *
+ * For algorithm information, see Loitsch, Florian. "Printing
+ * floating-point numbers quickly and accurately with integers." ACM Sigplan
+ * Notices 45.6 (2010): 233-243.
+ *
+ * Copyright (C) 2015 Doug Currie
+ * based on dtoa_milo.h
+ * Copyright (C) 2014 Milo Yip
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "lexbor/core/str.h"
+#include "lexbor/core/diyfp.h"
+#include "lexbor/core/dtoa.h"
+
+#include <math.h>
+#include <string.h>
+
+
+lxb_inline void
+lexbor_grisu2_round(lxb_char_t *start, size_t len, uint64_t delta, uint64_t rest,
+                    uint64_t ten_kappa, uint64_t wp_w)
+{
+    while (rest < wp_w && delta - rest >= ten_kappa
+           && (rest + ten_kappa < wp_w ||  /* closer */
+               wp_w - rest > rest + ten_kappa - wp_w))
+    {
+        start[len - 1]--;
+        rest += ten_kappa;
+    }
+}
+
+lxb_inline int
+lexbor_dec_count(uint32_t n)
+{
+    if (n < 10) return 1;
+    if (n < 100) return 2;
+    if (n < 1000) return 3;
+    if (n < 10000) return 4;
+    if (n < 100000) return 5;
+    if (n < 1000000) return 6;
+    if (n < 10000000) return 7;
+    if (n < 100000000) return 8;
+    if (n < 1000000000) return 9;
+
+    return 10;
+}
+
+lxb_inline size_t
+lexbor_grisu2_gen(lexbor_diyfp_t W, lexbor_diyfp_t Mp, uint64_t delta,
+                  lxb_char_t *begin, lxb_char_t *end, int *dec_exp)
+{
+    int kappa;
+    lxb_char_t c, *p;
+    uint32_t p1, d;
+    uint64_t p2, tmp;
+    lexbor_diyfp_t one, wp_w;
+
+    static const uint64_t pow10[] = {
+        1,
+        10,
+        100,
+        1000,
+        10000,
+        100000,
+        1000000,
+        10000000,
+        100000000,
+        1000000000
+    };
+
+    wp_w = lexbor_diyfp_sub(Mp, W);
+
+    one = lexbor_diyfp((uint64_t) 1 << -Mp.exp, Mp.exp);
+    p1 = (uint32_t) (Mp.significand >> -one.exp);
+    p2 = Mp.significand & (one.significand - 1);
+
+    p = begin;
+
+    /* GCC 4.2 complains about uninitialized d. */
+    d = 0;
+
+    kappa = lexbor_dec_count(p1);
+
+    while (kappa > 0) {
+        switch (kappa) {
+            case 10: d = p1 / 1000000000; p1 %= 1000000000; break;
+            case  9: d = p1 /  100000000; p1 %=  100000000; break;
+            case  8: d = p1 /   10000000; p1 %=   10000000; break;
+            case  7: d = p1 /    1000000; p1 %=    1000000; break;
+            case  6: d = p1 /     100000; p1 %=     100000; break;
+            case  5: d = p1 /      10000; p1 %=      10000; break;
+            case  4: d = p1 /       1000; p1 %=       1000; break;
+            case  3: d = p1 /        100; p1 %=        100; break;
+            case  2: d = p1 /         10; p1 %=         10; break;
+            case  1: d = p1;              p1 =           0; break;
+            default:
+                /* Never go here. */
+                return 0;
+        }
+
+        if (d != 0 || p != begin) {
+            *p = '0' + d;
+
+            p += 1;
+            if (p == end) {
+                return p - begin;
+            }
+        }
+
+        kappa--;
+
+        tmp = ((uint64_t) p1 << -one.exp) + p2;
+
+        if (tmp <= delta) {
+            *dec_exp += kappa;
+            lexbor_grisu2_round(begin, p - begin, delta, tmp,
+                                pow10[kappa] << -one.exp, wp_w.significand);
+            return p - begin;
+        }
+    }
+
+    /* kappa = 0. */
+
+    for ( ;; ) {
+        p2 *= 10;
+        delta *= 10;
+        c = (char) (p2 >> -one.exp);
+
+        if (c != 0 || p != begin) {
+            *p = '0' + c;
+
+            p += 1;
+            if (p == end) {
+                return p - begin;
+            }
+        }
+
+        p2 &= one.significand - 1;
+        kappa--;
+
+        if (p2 < delta) {
+            *dec_exp += kappa;
+            tmp = (-kappa < 10) ? pow10[-kappa] : 0;
+            lexbor_grisu2_round(begin, p - begin, delta, p2, one.significand,
+                                wp_w.significand * tmp);
+            break;
+        }
+    }
+
+    return p - begin;
+}
+
+lxb_inline lexbor_diyfp_t
+lexbor_diyfp_normalize_boundary(lexbor_diyfp_t v)
+{
+    while ((v.significand & (LEXBOR_DBL_HIDDEN_BIT << 1)) == 0) {
+            v.significand <<= 1;
+            v.exp--;
+    }
+
+    return lexbor_diyfp_shift_left(v, LEXBOR_SIGNIFICAND_SHIFT - 2);
+}
+
+lxb_inline void
+lexbor_diyfp_normalize_boundaries(lexbor_diyfp_t v, lexbor_diyfp_t* minus,
+                                  lexbor_diyfp_t* plus)
+{
+    lexbor_diyfp_t pl, mi;
+
+    pl = lexbor_diyfp_normalize_boundary(lexbor_diyfp((v.significand << 1) + 1,
+                                                      v.exp - 1));
+
+    if (v.significand == LEXBOR_DBL_HIDDEN_BIT) {
+        mi = lexbor_diyfp((v.significand << 2) - 1, v.exp - 2);
+
+    } else {
+        mi = lexbor_diyfp((v.significand << 1) - 1, v.exp - 1);
+    }
+
+    mi.significand <<= mi.exp - pl.exp;
+    mi.exp = pl.exp;
+
+    *plus = pl;
+    *minus = mi;
+}
+
+lxb_inline size_t
+lexbor_grisu2(double value, lxb_char_t *begin, lxb_char_t *end, int *dec_exp)
+{
+    lexbor_diyfp_t v, w_m, w_p, c_mk, W, Wp, Wm;
+
+    v = lexbor_diyfp_from_d2(value);
+
+    lexbor_diyfp_normalize_boundaries(v, &w_m, &w_p);
+
+    c_mk = lexbor_cached_power_bin(w_p.exp, dec_exp);
+    W = lexbor_diyfp_mul(lexbor_diyfp_normalize(v), c_mk);
+
+    Wp = lexbor_diyfp_mul(w_p, c_mk);
+    Wm = lexbor_diyfp_mul(w_m, c_mk);
+
+    Wm.significand++;
+    Wp.significand--;
+
+   return lexbor_grisu2_gen(W, Wp, Wp.significand - Wm.significand, begin, end,
+                            dec_exp);
+}
+
+lxb_inline size_t
+lexbor_write_exponent(int exp, lxb_char_t *begin, lxb_char_t *end)
+{
+    char *p;
+    size_t len;
+    uint32_t u32;
+    char buf[4];
+
+    /* -324 <= exp <= 308. */
+
+    if ((begin + (sizeof(buf) - 1) + 1) >= end) {
+        return 0;
+    }
+
+    if (exp < 0) {
+        *begin = '-';
+        begin += 1;
+
+        exp = -exp;
+    }
+    else {
+        *begin++ = '+';
+    }
+
+    u32 = exp;
+    p = buf + (sizeof(buf) - 1);
+
+    do {
+        *--p = u32 % 10 + '0';
+        u32 /= 10;
+    }
+    while (u32 != 0);
+
+    len = buf + (sizeof(buf) - 1) - p;
+
+    memcpy(begin, p, len);
+
+    return len + 1;
+}
+
+lxb_inline size_t
+lexbor_prettify(lxb_char_t *begin, lxb_char_t *end, size_t len, int dec_exp)
+{
+    int kk, offset, length;
+    size_t size;
+
+    /* 10^(kk-1) <= v < 10^kk */
+
+    length = (int) len;
+    kk = length + dec_exp;
+
+    if (length <= kk && kk <= 21) {
+        /* 1234e7 -> 12340000000 */
+
+        if (kk - length > 0) {
+            if ((&begin[length] + (kk - length)) < end) {
+                memset(&begin[length], '0', kk - length);
+            }
+            else {
+                memset(&begin[length], '0', (end - &begin[length]));
+            }
+        }
+
+        return kk;
+    }
+    else if (0 < kk && kk <= 21) {
+        /* 1234e-2 -> 12.34 */
+
+        if ((&begin[kk + 1] + (length - kk)) >= end) {
+            return length;
+        }
+
+        memmove(&begin[kk + 1], &begin[kk], length - kk);
+        begin[kk] = '.';
+
+        return (length + 1);
+    }
+    else if (-6 < kk && kk <= 0) {
+        /* 1234e-6 -> 0.001234 */
+
+        offset = 2 - kk;
+        if ((&begin[offset] + length) >= end
+            || (begin + 2) >= end)
+        {
+            return length;
+        }
+
+        memmove(&begin[offset], begin, length);
+        begin[0] = '0';
+        begin[1] = '.';
+
+        if (offset - 2 > 0) {
+            if ((&begin[2] + (offset - 2)) >= end) {
+                return length;
+            }
+
+            memset(&begin[2], '0', offset - 2);
+        }
+
+        return (length + offset);
+    }
+    else if (length == 1) {
+        /* 1e30 */
+
+        if ((begin + 1) >= end) {
+            return length;
+        }
+
+        begin[1] = 'e';
+
+        size =  lexbor_write_exponent(kk - 1, &begin[2], end);
+
+        return (size + 2);
+    }
+
+    /* 1234e30 -> 1.234e33 */
+
+    if ((&begin[2] + (length - 1)) >= end) {
+        return length;
+    }
+
+    memmove(&begin[2], &begin[1], length - 1);
+    begin[1] = '.';
+    begin[length + 1] = 'e';
+
+    size = lexbor_write_exponent(kk - 1, &begin[length + 2], end);
+
+    return (size + length + 2);
+}
+
+size_t
+lexbor_dtoa(double value, lxb_char_t *begin, size_t len)
+{
+    int dec_exp, minus;
+    size_t length;
+    lxb_char_t *end = begin + len;
+
+    if (len == 0) {
+        return 0;
+    }
+
+    /* Not handling NaN and inf. */
+
+    minus = 0;
+
+    if (value == 0) {
+        *begin = '0';
+
+        return 1;
+    }
+
+    if (signbit(value)) {
+        *begin = '-';
+
+        begin += 1;
+        if (begin == end) {
+            return 1;
+        }
+
+        value = -value;
+        minus = 1;
+    }
+
+    length = lexbor_grisu2(value, begin, end, &dec_exp);
+    length = lexbor_prettify(begin, end, length, dec_exp);
+
+    return (minus + length);
+}
diff --git a/contrib/url/lexbor/core/dtoa.h b/contrib/url/lexbor/core/dtoa.h
new file mode 100644
index 0000000000..c60c28803b
--- /dev/null
+++ b/contrib/url/lexbor/core/dtoa.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Alexander Borisov
+ *
+ * Based on nxt_dtoa.h from NGINX NJS project
+ *
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) Nginx, Inc.
+ */
+
+#ifndef LEXBOR_DTOA_H
+#define LEXBOR_DTOA_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "lexbor/core/base.h"
+
+
+LXB_API size_t
+lexbor_dtoa(double value, lxb_char_t *begin, size_t len);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_DTOA_H */
diff --git a/contrib/url/lexbor/core/lexbor.h b/contrib/url/lexbor/core/lexbor.h
new file mode 100644
index 0000000000..29c4035674
--- /dev/null
+++ b/contrib/url/lexbor/core/lexbor.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#ifndef LEXBOR_H
+#define LEXBOR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "lexbor/core/def.h"
+
+typedef void *(*lexbor_memory_malloc_f)(size_t size);
+typedef void *(*lexbor_memory_realloc_f)(void *dst, size_t size);
+typedef void *(*lexbor_memory_calloc_f)(size_t num, size_t size);
+typedef void (*lexbor_memory_free_f)(void *dst);
+
+LXB_API void *
+lexbor_malloc(size_t size);
+
+LXB_API void *
+lexbor_realloc(void *dst, size_t size);
+
+LXB_API void *
+lexbor_calloc(size_t num, size_t size);
+
+LXB_API void *
+lexbor_free(void *dst);
+
+LXB_API lxb_status_t
+lexbor_memory_setup(lexbor_memory_malloc_f new_malloc, lexbor_memory_realloc_f new_realloc,
+                    lexbor_memory_calloc_f new_calloc, lexbor_memory_free_f new_free);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_H */
+
diff --git a/contrib/url/lexbor/core/mem.c b/contrib/url/lexbor/core/mem.c
new file mode 100644
index 0000000000..9192af42ed
--- /dev/null
+++ b/contrib/url/lexbor/core/mem.c
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#include "lexbor/core/mem.h"
+
+
+lexbor_mem_t *
+lexbor_mem_create(void)
+{
+    return lexbor_calloc(1, sizeof(lexbor_mem_t));
+}
+
+lxb_status_t
+lexbor_mem_init(lexbor_mem_t *mem, size_t min_chunk_size)
+{
+    if (mem == NULL) {
+        return LXB_STATUS_ERROR_OBJECT_IS_NULL;
+    }
+
+    if (min_chunk_size == 0) {
+        return LXB_STATUS_ERROR_WRONG_ARGS;
+    }
+
+    mem->chunk_min_size = lexbor_mem_align(min_chunk_size);
+
+    /* Create first chunk */
+    mem->chunk = lexbor_mem_chunk_make(mem, mem->chunk_min_size);
+    if (mem->chunk == NULL) {
+        return LXB_STATUS_ERROR_MEMORY_ALLOCATION;
+    }
+
+    mem->chunk_length = 1;
+    mem->chunk_first = mem->chunk;
+
+    return LXB_STATUS_OK;
+}
+
+void
+lexbor_mem_clean(lexbor_mem_t *mem)
+{
+    lexbor_mem_chunk_t *prev, *chunk;
+
+    if (mem == NULL) {
+        return;
+    }
+
+    chunk = mem->chunk;
+
+    while (chunk->prev) {
+        prev = chunk->prev;
+
+        chunk->data = lexbor_free(chunk->data);
+        lexbor_free(chunk);
+
+        chunk = prev;
+    }
+
+    chunk->next = NULL;
+    chunk->length = 0;
+
+    mem->chunk = mem->chunk_first;
+    mem->chunk_length = 1;
+}
+
+lexbor_mem_t *
+lexbor_mem_destroy(lexbor_mem_t *mem, bool destroy_self)
+{
+    lexbor_mem_chunk_t *chunk, *prev;
+
+    if (mem == NULL) {
+        return NULL;
+    }
+
+    /* Destroy all chunk */
+    if (mem->chunk) {
+        chunk = mem->chunk;
+
+        while (chunk) {
+            prev = chunk->prev;
+            lexbor_mem_chunk_destroy(mem, chunk, true);
+            chunk = prev;
+        }
+
+        mem->chunk = NULL;
+    }
+
+    if (destroy_self) {
+        return lexbor_free(mem);
+    }
+
+    return mem;
+}
+
+uint8_t *
+lexbor_mem_chunk_init(lexbor_mem_t *mem,
+                      lexbor_mem_chunk_t *chunk, size_t length)
+{
+    length = lexbor_mem_align(length);
+
+    if (length > mem->chunk_min_size) {
+        if (mem->chunk_min_size > (SIZE_MAX - length)) {
+            chunk->size = length;
+        }
+        else {
+            chunk->size = length + mem->chunk_min_size;
+        }
+    }
+    else {
+        chunk->size = mem->chunk_min_size;
+    }
+
+    chunk->length = 0;
+    chunk->data = lexbor_malloc(chunk->size * sizeof(uint8_t));
+
+    return chunk->data;
+}
+
+lexbor_mem_chunk_t *
+lexbor_mem_chunk_make(lexbor_mem_t *mem, size_t length)
+{
+    lexbor_mem_chunk_t *chunk = lexbor_calloc(1, sizeof(lexbor_mem_chunk_t));
+
+    if (chunk == NULL) {
+        return NULL;
+    }
+
+    if (lexbor_mem_chunk_init(mem, chunk, length) == NULL) {
+        return lexbor_free(chunk);
+    }
+
+    return chunk;
+}
+
+lexbor_mem_chunk_t *
+lexbor_mem_chunk_destroy(lexbor_mem_t *mem,
+                         lexbor_mem_chunk_t *chunk, bool self_destroy)
+{
+    if (chunk == NULL || mem == NULL) {
+        return NULL;
+    }
+
+    if (chunk->data) {
+        chunk->data = lexbor_free(chunk->data);
+    }
+
+    if (self_destroy) {
+        return lexbor_free(chunk);
+    }
+
+    return chunk;
+}
+
+void *
+lexbor_mem_alloc(lexbor_mem_t *mem, size_t length)
+{
+    if (length == 0) {
+        return NULL;
+    }
+
+    length = lexbor_mem_align(length);
+
+    if ((mem->chunk->length + length) > mem->chunk->size) {
+        if ((SIZE_MAX - mem->chunk_length) == 0) {
+            return NULL;
+        }
+
+        mem->chunk->next = lexbor_mem_chunk_make(mem, length);
+        if (mem->chunk->next == NULL) {
+            return NULL;
+        }
+
+        mem->chunk->next->prev = mem->chunk;
+        mem->chunk = mem->chunk->next;
+
+        mem->chunk_length++;
+    }
+
+    mem->chunk->length += length;
+
+    return &mem->chunk->data[(mem->chunk->length - length)];
+}
+
+void *
+lexbor_mem_calloc(lexbor_mem_t *mem, size_t length)
+{
+    void *data = lexbor_mem_alloc(mem, length);
+
+    if (data != NULL) {
+        memset(data, 0, length);
+    }
+
+    return data;
+}
+
+/*
+ * No inline functions for ABI.
+ */
+size_t
+lexbor_mem_current_length_noi(lexbor_mem_t *mem)
+{
+    return lexbor_mem_current_length(mem);
+}
+
+size_t
+lexbor_mem_current_size_noi(lexbor_mem_t *mem)
+{
+    return lexbor_mem_current_size(mem);
+}
+
+size_t
+lexbor_mem_chunk_length_noi(lexbor_mem_t *mem)
+{
+    return lexbor_mem_chunk_length(mem);
+}
+size_t
+lexbor_mem_align_noi(size_t size)
+{
+    return lexbor_mem_align(size);
+}
+
+size_t
+lexbor_mem_align_floor_noi(size_t size)
+{
+    return lexbor_mem_align_floor(size);
+}
diff --git a/contrib/url/lexbor/core/mem.h b/contrib/url/lexbor/core/mem.h
new file mode 100644
index 0000000000..f245528d52
--- /dev/null
+++ b/contrib/url/lexbor/core/mem.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#ifndef LEXBOR_MEM_H
+#define LEXBOR_MEM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <string.h>
+
+#include "lexbor/core/base.h"
+
+
+typedef struct lexbor_mem_chunk lexbor_mem_chunk_t;
+typedef struct lexbor_mem lexbor_mem_t;
+
+struct lexbor_mem_chunk {
+    uint8_t            *data;
+    size_t             length;
+    size_t             size;
+
+    lexbor_mem_chunk_t *next;
+    lexbor_mem_chunk_t *prev;
+};
+
+struct lexbor_mem {
+    lexbor_mem_chunk_t *chunk;
+    lexbor_mem_chunk_t *chunk_first;
+
+    size_t             chunk_min_size;
+    size_t             chunk_length;
+};
+
+
+LXB_API lexbor_mem_t *
+lexbor_mem_create(void);
+
+LXB_API lxb_status_t
+lexbor_mem_init(lexbor_mem_t *mem, size_t min_chunk_size);
+
+LXB_API void
+lexbor_mem_clean(lexbor_mem_t *mem);
+
+LXB_API lexbor_mem_t *
+lexbor_mem_destroy(lexbor_mem_t *mem, bool destroy_self);
+
+
+/*
+ * The memory allocated in lexbor_mem_chunk_* functions needs to be freed
+ * by lexbor_mem_chunk_destroy function.
+ *
+ * This memory will not be automatically freed by a function lexbor_mem_destroy.
+ */
+LXB_API uint8_t *
+lexbor_mem_chunk_init(lexbor_mem_t *mem,
+                      lexbor_mem_chunk_t *chunk, size_t length);
+
+LXB_API lexbor_mem_chunk_t *
+lexbor_mem_chunk_make(lexbor_mem_t *mem, size_t length);
+
+LXB_API lexbor_mem_chunk_t *
+lexbor_mem_chunk_destroy(lexbor_mem_t *mem,
+                         lexbor_mem_chunk_t *chunk, bool self_destroy);
+
+/*
+ * The memory allocated in lexbor_mem_alloc and lexbor_mem_calloc function
+ * will be freeds after calling lexbor_mem_destroy function.
+ */
+LXB_API void *
+lexbor_mem_alloc(lexbor_mem_t *mem, size_t length);
+
+LXB_API void *
+lexbor_mem_calloc(lexbor_mem_t *mem, size_t length);
+
+
+/*
+ * Inline functions
+ */
+lxb_inline size_t
+lexbor_mem_current_length(lexbor_mem_t *mem)
+{
+    return mem->chunk->length;
+}
+
+lxb_inline size_t
+lexbor_mem_current_size(lexbor_mem_t *mem)
+{
+    return mem->chunk->size;
+}
+
+lxb_inline size_t
+lexbor_mem_chunk_length(lexbor_mem_t *mem)
+{
+    return mem->chunk_length;
+}
+
+lxb_inline size_t
+lexbor_mem_align(size_t size)
+{
+    return ((size % LEXBOR_MEM_ALIGN_STEP) != 0)
+           ? size + (LEXBOR_MEM_ALIGN_STEP - (size % LEXBOR_MEM_ALIGN_STEP))
+           : size;
+}
+
+lxb_inline size_t
+lexbor_mem_align_floor(size_t size)
+{
+    return ((size % LEXBOR_MEM_ALIGN_STEP) != 0)
+           ? size - (size % LEXBOR_MEM_ALIGN_STEP)
+           : size;
+}
+
+/*
+ * No inline functions for ABI.
+ */
+LXB_API size_t
+lexbor_mem_current_length_noi(lexbor_mem_t *mem);
+
+LXB_API size_t
+lexbor_mem_current_size_noi(lexbor_mem_t *mem);
+
+LXB_API size_t
+lexbor_mem_chunk_length_noi(lexbor_mem_t *mem);
+
+LXB_API size_t
+lexbor_mem_align_noi(size_t size);
+
+LXB_API size_t
+lexbor_mem_align_floor_noi(size_t size);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_MEM_H */
diff --git a/contrib/url/lexbor/core/memory.c b/contrib/url/lexbor/core/memory.c
new file mode 100644
index 0000000000..038e040440
--- /dev/null
+++ b/contrib/url/lexbor/core/memory.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#include "lexbor/core/base.h"
+
+static lexbor_memory_malloc_f lexbor_memory_malloc = malloc;
+static lexbor_memory_realloc_f lexbor_memory_realloc = realloc;
+static lexbor_memory_calloc_f lexbor_memory_calloc = calloc;
+static lexbor_memory_free_f lexbor_memory_free = free;
+
+void *
+lexbor_malloc(size_t size)
+{
+    return lexbor_memory_malloc(size);
+}
+
+void *
+lexbor_realloc(void *dst, size_t size)
+{
+    return lexbor_memory_realloc(dst, size);
+}
+
+void *
+lexbor_calloc(size_t num, size_t size)
+{
+    return lexbor_memory_calloc(num, size);
+}
+
+void *
+lexbor_free(void *dst)
+{
+    lexbor_memory_free(dst);
+    return NULL;
+}
+
+lxb_status_t
+lexbor_memory_setup(lexbor_memory_malloc_f new_malloc, lexbor_memory_realloc_f new_realloc,
+                    lexbor_memory_calloc_f new_calloc, lexbor_memory_free_f new_free)
+{
+    if (new_malloc == NULL || new_realloc == NULL || new_calloc == NULL || new_free == NULL) {
+        return LXB_STATUS_ERROR_OBJECT_IS_NULL;
+    }
+
+    lexbor_memory_malloc = new_malloc;
+    lexbor_memory_realloc = new_realloc;
+    lexbor_memory_calloc = new_calloc;
+    lexbor_memory_free = new_free;
+
+    return LXB_STATUS_OK;
+}
diff --git a/contrib/url/lexbor/core/mraw.c b/contrib/url/lexbor/core/mraw.c
new file mode 100644
index 0000000000..29d0383c4b
--- /dev/null
+++ b/contrib/url/lexbor/core/mraw.c
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2018-2019 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#include "lexbor/core/mraw.h"
+
+
+#if defined(LEXBOR_HAVE_ADDRESS_SANITIZER)
+    #include <sanitizer/asan_interface.h>
+#endif
+
+
+#define lexbor_mraw_meta_set(data, size)                                       \
+    do {                                                                       \
+        memcpy(data, size, sizeof(size_t));                                    \
+    }                                                                          \
+    while (0)
+
+#define lexbor_mraw_data_begin(data)                                           \
+    &((uint8_t *) (data))[ lexbor_mraw_meta_size() ]
+
+
+lxb_inline void *
+lexbor_mraw_realloc_tail(lexbor_mraw_t *mraw, void *data, void *begin,
+                         size_t size, size_t begin_len, size_t new_size,
+                         bool *is_valid);
+
+
+lexbor_mraw_t *
+lexbor_mraw_create(void)
+{
+    return lexbor_calloc(1, sizeof(lexbor_mraw_t));
+}
+
+lxb_status_t
+lexbor_mraw_init(lexbor_mraw_t *mraw, size_t chunk_size)
+{
+    lxb_status_t status;
+
+    if (mraw == NULL) {
+        return LXB_STATUS_ERROR_OBJECT_IS_NULL;
+    }
+
+    if (chunk_size == 0) {
+        return LXB_STATUS_ERROR_WRONG_ARGS;
+    }
+
+    /* Init memory */
+    mraw->mem = lexbor_mem_create();
+
+    status = lexbor_mem_init(mraw->mem, chunk_size + lexbor_mraw_meta_size());
+    if (status) {
+        return status;
+    }
+
+#if defined(LEXBOR_HAVE_ADDRESS_SANITIZER)
+    ASAN_POISON_MEMORY_REGION(mraw->mem->chunk->data, mraw->mem->chunk->size);
+#endif
+
+    /* Cache */
+    mraw->cache = lexbor_bst_create();
+
+    status = lexbor_bst_init(mraw->cache, 512);
+    if (status) {
+        return status;
+    }
+
+    mraw->ref_count = 0;
+
+    return LXB_STATUS_OK;
+}
+
+void
+lexbor_mraw_clean(lexbor_mraw_t *mraw)
+{
+    if (mraw != NULL) {
+        lexbor_mem_clean(mraw->mem);
+        lexbor_bst_clean(mraw->cache);
+
+        mraw->ref_count = 0;
+    }
+}
+
+lexbor_mraw_t *
+lexbor_mraw_destroy(lexbor_mraw_t *mraw, bool destroy_self)
+{
+    if (mraw == NULL) {
+        return NULL;
+    }
+
+    mraw->mem = lexbor_mem_destroy(mraw->mem, true);
+    mraw->cache = lexbor_bst_destroy(mraw->cache, true);
+
+    if (destroy_self) {
+        return lexbor_free(mraw);
+    }
+
+    return mraw;
+}
+
+lxb_inline void *
+lexbor_mraw_mem_alloc(lexbor_mraw_t *mraw, size_t length)
+{
+    size_t diff;
+    uint8_t *data;
+    lexbor_mem_t *mem = mraw->mem;
+
+    if (length == 0) {
+        return NULL;
+    }
+
+    if ((mem->chunk->length + length) > mem->chunk->size) {
+        lexbor_mem_chunk_t *chunk = mem->chunk;
+
+        if ((SIZE_MAX - mem->chunk_length) == 0) {
+            return NULL;
+        }
+
+        if (chunk->length == 0) {
+            lexbor_mem_chunk_destroy(mem, chunk, false);
+            lexbor_mem_chunk_init(mem, chunk, length);
+
+            chunk->length = length;
+
+#if defined(LEXBOR_HAVE_ADDRESS_SANITIZER)
+            ASAN_POISON_MEMORY_REGION(chunk->data, chunk->size);
+#endif
+
+            return chunk->data;
+        }
+
+        diff = lexbor_mem_align_floor(chunk->size - chunk->length);
+
+        /* Save tail to cache */
+        if (diff > lexbor_mraw_meta_size()) {
+            diff -= lexbor_mraw_meta_size();
+
+#if defined(LEXBOR_HAVE_ADDRESS_SANITIZER)
+            ASAN_UNPOISON_MEMORY_REGION(&chunk->data[chunk->length],
+                                        lexbor_mraw_meta_size());
+#endif
+
+            lexbor_mraw_meta_set(&chunk->data[chunk->length], &diff);
+
+#if defined(LEXBOR_HAVE_ADDRESS_SANITIZER)
+            ASAN_POISON_MEMORY_REGION(&chunk->data[chunk->length],
+                                      diff + lexbor_mraw_meta_size());
+#endif
+
+            lexbor_bst_insert(mraw->cache,
+                              lexbor_bst_root_ref(mraw->cache), diff,
+                              lexbor_mraw_data_begin(&chunk->data[chunk->length]));
+
+            chunk->length = chunk->size;
+        }
+
+        chunk->next = lexbor_mem_chunk_make(mem, length);
+        if (chunk->next == NULL) {
+            return NULL;
+        }
+
+        chunk->next->prev = chunk;
+        mem->chunk = chunk->next;
+
+        mem->chunk_length++;
+
+#if defined(LEXBOR_HAVE_ADDRESS_SANITIZER)
+        ASAN_POISON_MEMORY_REGION(mem->chunk->data, mem->chunk->size);
+#endif
+    }
+
+    data = &mem->chunk->data[ mem->chunk->length ];
+    mem->chunk->length += length;
+
+    return data;
+}
+
+void *
+lexbor_mraw_alloc(lexbor_mraw_t *mraw, size_t size)
+{
+    void *data;
+
+    size = lexbor_mem_align(size);
+
+    if (mraw->cache->tree_length != 0) {
+        data = lexbor_bst_remove_close(mraw->cache,
+                                       lexbor_bst_root_ref(mraw->cache),
+                                       size, NULL);
+        if (data != NULL) {
+
+#if defined(LEXBOR_HAVE_ADDRESS_SANITIZER)
+            uint8_t *real_data = ((uint8_t *) data) - lexbor_mraw_meta_size();
+
+            /* Set unpoison for current data size */
+            ASAN_UNPOISON_MEMORY_REGION(real_data, lexbor_mraw_meta_size());
+
+            size_t cur_size = lexbor_mraw_data_size(data);
+
+            ASAN_UNPOISON_MEMORY_REGION(real_data,
+                                        (cur_size + lexbor_mraw_meta_size()));
+#endif
+
+            mraw->ref_count++;
+
+            return data;
+        }
+    }
+
+    data = lexbor_mraw_mem_alloc(mraw, (size + lexbor_mraw_meta_size()));
+
+    if (data == NULL) {
+        return NULL;
+    }
+
+#if defined(LEXBOR_HAVE_ADDRESS_SANITIZER)
+    ASAN_UNPOISON_MEMORY_REGION(data, (size + lexbor_mraw_meta_size()));
+#endif
+
+    mraw->ref_count++;
+
+    lexbor_mraw_meta_set(data, &size);
+    return lexbor_mraw_data_begin(data);
+}
+
+void *
+lexbor_mraw_calloc(lexbor_mraw_t *mraw, size_t size)
+{
+    void *data = lexbor_mraw_alloc(mraw, size);
+
+    if (data != NULL) {
+        memset(data, 0, lexbor_mraw_data_size(data));
+    }
+
+    return data;
+}
+
+/*
+ * TODO: I don't really like this interface. Perhaps need to simplify.
+ */
+lxb_inline void *
+lexbor_mraw_realloc_tail(lexbor_mraw_t *mraw, void *data, void *begin,
+                         size_t size, size_t begin_len, size_t new_size,
+                         bool *is_valid)
+{
+    lexbor_mem_chunk_t *chunk = mraw->mem->chunk;
+
+    if (chunk->size > (begin_len + new_size)) {
+        *is_valid = true;
+
+        if (new_size == 0) {
+            chunk->length = begin_len - lexbor_mraw_meta_size();
+            return NULL;
+        }
+
+#if defined(LEXBOR_HAVE_ADDRESS_SANITIZER)
+        ASAN_UNPOISON_MEMORY_REGION(begin, new_size + lexbor_mraw_meta_size());
+#endif
+
+        chunk->length = begin_len + new_size;
+        memcpy(begin, &new_size, sizeof(size_t));
+
+        return data;
+    }
+
+    /*
+     * If the tail is short then we increase the current data.
+     */
+    if (begin_len == lexbor_mraw_meta_size()) {
+        void *new_data;
+        lexbor_mem_chunk_t new_chunk;
+
+        *is_valid = true;
+
+        lexbor_mem_chunk_init(mraw->mem, &new_chunk,
+                              new_size + lexbor_mraw_meta_size());
+        if(new_chunk.data == NULL) {
+            return NULL;
+        }
+
+        lexbor_mraw_meta_set(new_chunk.data, &new_size);
+        new_data = lexbor_mraw_data_begin(new_chunk.data);
+
+        if (size != 0) {
+            memcpy(new_data, data, sizeof(uint8_t) * size);
+        }
+
+#if defined(LEXBOR_HAVE_ADDRESS_SANITIZER)
+        ASAN_UNPOISON_MEMORY_REGION(chunk->data, chunk->size);
+#endif
+
+        lexbor_mem_chunk_destroy(mraw->mem, chunk, false);
+
+        chunk->data = new_chunk.data;
+        chunk->size = new_chunk.size;
+        chunk->length = new_size + lexbor_mraw_meta_size();
+
+        return new_data;
+    }
+
+    *is_valid = false;
+
+    /*
+     * Next, this piece will go into the cache.
+     */
+    size = lexbor_mem_align_floor(size + (chunk->size - chunk->length));
+    memcpy(begin, &size, sizeof(size_t));
+
+    chunk->length = chunk->size;
+
+    return NULL;
+}
+
+void *
+lexbor_mraw_realloc(lexbor_mraw_t *mraw, void *data, size_t new_size)
+{
+    void *begin;
+    size_t size, begin_len, diff;
+    lexbor_mem_chunk_t *chunk = mraw->mem->chunk;
+
+    begin = ((uint8_t *) data) - lexbor_mraw_meta_size();
+    memcpy(&size, begin, sizeof(size_t));
+
+    new_size = lexbor_mem_align(new_size);
+
+    /*
+     * Look, whether there is an opportunity
+     * to prolong the current data in chunk?
+     */
+    if (chunk->length >= size) {
+        begin_len = chunk->length - size;
+
+        if (&chunk->data[begin_len] == data) {
+            bool is_valid;
+            void *ptr = lexbor_mraw_realloc_tail(mraw, data, begin,
+                                                 size, begin_len, new_size,
+                                                 &is_valid);
+            if (is_valid == true) {
+                return ptr;
+            }
+        }
+    }
+
+    if (new_size < size) {
+        if (new_size == 0) {
+
+#if defined(LEXBOR_HAVE_ADDRESS_SANITIZER)
+            ASAN_POISON_MEMORY_REGION(begin, size + lexbor_mraw_meta_size());
+#endif
+            mraw->ref_count--;
+
+            lexbor_bst_insert(mraw->cache, lexbor_bst_root_ref(mraw->cache),
+                              size, data);
+            return NULL;
+        }
+
+        diff = lexbor_mem_align_floor(size - new_size);
+
+        if (diff > lexbor_mraw_meta_size()) {
+            memcpy(begin, &new_size, sizeof(size_t));
+
+            new_size = diff - lexbor_mraw_meta_size();
+            begin = &((uint8_t *) data)[diff];
+
+            lexbor_mraw_meta_set(begin, &new_size);
+
+#if defined(LEXBOR_HAVE_ADDRESS_SANITIZER)
+            ASAN_POISON_MEMORY_REGION(begin, new_size + lexbor_mraw_meta_size());
+#endif
+            lexbor_bst_insert(mraw->cache, lexbor_bst_root_ref(mraw->cache),
+                              new_size, lexbor_mraw_data_begin(begin));
+        }
+
+        return data;
+    }
+
+    begin = lexbor_mraw_alloc(mraw, new_size);
+    if (begin == NULL) {
+        return NULL;
+    }
+
+    if (size != 0) {
+        memcpy(begin, data, sizeof(uint8_t) * size);
+    }
+
+    lexbor_mraw_free(mraw, data);
+
+    return begin;
+}
+
+void *
+lexbor_mraw_free(lexbor_mraw_t *mraw, void *data)
+{
+    size_t size = lexbor_mraw_data_size(data);
+
+#if defined(LEXBOR_HAVE_ADDRESS_SANITIZER)
+    uint8_t *real_data = ((uint8_t *) data) - lexbor_mraw_meta_size();
+    ASAN_POISON_MEMORY_REGION(real_data, size + lexbor_mraw_meta_size());
+#endif
+
+    lexbor_bst_insert(mraw->cache, lexbor_bst_root_ref(mraw->cache),
+                      size, data);
+
+    mraw->ref_count--;
+
+    return NULL;
+}
+
+/*
+ * No inline functions for ABI.
+ */
+size_t
+lexbor_mraw_data_size_noi(void *data)
+{
+    return lexbor_mraw_data_size(data);
+}
+
+void
+lexbor_mraw_data_size_set_noi(void *data, size_t size)
+{
+    lexbor_mraw_data_size_set(data, size);
+}
+
+void *
+lexbor_mraw_dup_noi(lexbor_mraw_t *mraw, const void *src, size_t size)
+{
+    return lexbor_mraw_dup(mraw, src, size);
+}
diff --git a/contrib/url/lexbor/core/mraw.h b/contrib/url/lexbor/core/mraw.h
new file mode 100644
index 0000000000..1ffb64dca2
--- /dev/null
+++ b/contrib/url/lexbor/core/mraw.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#ifndef LEXBOR_MRAW_H
+#define LEXBOR_MRAW_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <string.h>
+
+#include "lexbor/core/base.h"
+#include "lexbor/core/mem.h"
+#include "lexbor/core/bst.h"
+
+
+#define lexbor_mraw_meta_size()                                                \
+    (((sizeof(size_t) % LEXBOR_MEM_ALIGN_STEP) != 0)                           \
+    ? sizeof(size_t)                                                           \
+        + (LEXBOR_MEM_ALIGN_STEP - (sizeof(size_t) % LEXBOR_MEM_ALIGN_STEP))   \
+    : sizeof(size_t))
+
+
+typedef struct {
+    lexbor_mem_t *mem;
+    lexbor_bst_t *cache;
+    size_t       ref_count;
+}
+lexbor_mraw_t;
+
+
+LXB_API lexbor_mraw_t *
+lexbor_mraw_create(void);
+
+LXB_API lxb_status_t
+lexbor_mraw_init(lexbor_mraw_t *mraw, size_t chunk_size);
+
+LXB_API void
+lexbor_mraw_clean(lexbor_mraw_t *mraw);
+
+LXB_API lexbor_mraw_t *
+lexbor_mraw_destroy(lexbor_mraw_t *mraw, bool destroy_self);
+
+
+LXB_API void *
+lexbor_mraw_alloc(lexbor_mraw_t *mraw, size_t size);
+
+LXB_API void *
+lexbor_mraw_calloc(lexbor_mraw_t *mraw, size_t size);
+
+LXB_API void *
+lexbor_mraw_realloc(lexbor_mraw_t *mraw, void *data, size_t new_size);
+
+LXB_API void *
+lexbor_mraw_free(lexbor_mraw_t *mraw, void *data);
+
+
+/*
+ * Inline functions
+ */
+lxb_inline size_t
+lexbor_mraw_data_size(void *data)
+{
+    return *((size_t *) (((uint8_t *) data) - lexbor_mraw_meta_size()));
+}
+
+lxb_inline void
+lexbor_mraw_data_size_set(void *data, size_t size)
+{
+    data = (((uint8_t *) data) - lexbor_mraw_meta_size());
+    memcpy(data, &size, sizeof(size_t));
+}
+
+lxb_inline void *
+lexbor_mraw_dup(lexbor_mraw_t *mraw, const void *src, size_t size)
+{
+    void *data = lexbor_mraw_alloc(mraw, size);
+
+    if (data != NULL) {
+        memcpy(data, src, size);
+    }
+
+    return data;
+}
+
+lxb_inline size_t
+lexbor_mraw_reference_count(lexbor_mraw_t *mraw)
+{
+    return mraw->ref_count;
+}
+
+
+/*
+ * No inline functions for ABI.
+ */
+LXB_API size_t
+lexbor_mraw_data_size_noi(void *data);
+
+LXB_API void
+lexbor_mraw_data_size_set_noi(void *data, size_t size);
+
+LXB_API void *
+lexbor_mraw_dup_noi(lexbor_mraw_t *mraw, const void *src, size_t size);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_MRAW_H */
diff --git a/contrib/url/lexbor/core/plog.c b/contrib/url/lexbor/core/plog.c
new file mode 100644
index 0000000000..71344b0a2a
--- /dev/null
+++ b/contrib/url/lexbor/core/plog.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#include "lexbor/core/plog.h"
+
+
+lxb_status_t
+lexbor_plog_init(lexbor_plog_t *plog, size_t init_size, size_t struct_size)
+{
+    lxb_status_t status;
+
+    if (plog == NULL) {
+        return LXB_STATUS_ERROR_OBJECT_IS_NULL;
+    }
+
+    if (struct_size < sizeof(lexbor_plog_entry_t)) {
+        struct_size = sizeof(lexbor_plog_entry_t);
+    }
+
+    status = lexbor_array_obj_init(&plog->list, init_size, struct_size);
+    if (status != LXB_STATUS_OK) {
+        return status;
+    }
+
+    return LXB_STATUS_OK;
+}
+
+lexbor_plog_t *
+lexbor_plog_destroy(lexbor_plog_t *plog, bool self_destroy)
+{
+    if (plog == NULL) {
+        return NULL;
+    }
+
+    lexbor_array_obj_destroy(&plog->list, false);
+
+    if (self_destroy) {
+        return lexbor_free(plog);
+    }
+
+    return plog;
+}
+
+/*
+ * No inline functions.
+ */
+lexbor_plog_t *
+lexbor_plog_create_noi(void)
+{
+    return lexbor_plog_create();
+}
+
+void
+lexbor_plog_clean_noi(lexbor_plog_t *plog)
+{
+    lexbor_plog_clean(plog);
+}
+
+void *
+lexbor_plog_push_noi(lexbor_plog_t *plog, const lxb_char_t *data, void *ctx,
+                     unsigned id)
+{
+    return lexbor_plog_push(plog, data, ctx, id);
+}
+
+size_t
+lexbor_plog_length_noi(lexbor_plog_t *plog)
+{
+    return lexbor_plog_length(plog);
+}
diff --git a/contrib/url/lexbor/core/plog.h b/contrib/url/lexbor/core/plog.h
new file mode 100644
index 0000000000..91b35bfcdf
--- /dev/null
+++ b/contrib/url/lexbor/core/plog.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2019 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#ifndef LEXBOR_PLOG_H
+#define LEXBOR_PLOG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "lexbor/core/array_obj.h"
+
+
+typedef struct {
+    const lxb_char_t *data;
+    void             *context;
+    unsigned         id;
+}
+lexbor_plog_entry_t;
+
+typedef struct {
+    lexbor_array_obj_t list;
+}
+lexbor_plog_t;
+
+
+LXB_API lxb_status_t
+lexbor_plog_init(lexbor_plog_t *plog, size_t init_size, size_t struct_size);
+
+LXB_API lexbor_plog_t *
+lexbor_plog_destroy(lexbor_plog_t *plog, bool self_destroy);
+
+
+/*
+ * Inline functions
+ */
+lxb_inline lexbor_plog_t *
+lexbor_plog_create(void)
+{
+    return (lexbor_plog_t *) lexbor_calloc(1, sizeof(lexbor_plog_t));
+}
+
+lxb_inline void
+lexbor_plog_clean(lexbor_plog_t *plog)
+{
+    lexbor_array_obj_clean(&plog->list);
+}
+
+lxb_inline void *
+lexbor_plog_push(lexbor_plog_t *plog, const lxb_char_t *data, void *ctx,
+                 unsigned id)
+{
+    lexbor_plog_entry_t *entry;
+
+    if (plog == NULL) {
+        return NULL;
+    }
+
+    entry = (lexbor_plog_entry_t *) lexbor_array_obj_push(&plog->list);
+    if (entry == NULL) {
+        return NULL;
+    }
+
+    entry->data = data;
+    entry->context = ctx;
+    entry->id = id;
+
+    return (void *) entry;
+}
+
+lxb_inline size_t
+lexbor_plog_length(lexbor_plog_t *plog)
+{
+    return lexbor_array_obj_length(&plog->list);
+}
+
+/*
+ * No inline functions for ABI.
+ */
+LXB_API lexbor_plog_t *
+lexbor_plog_create_noi(void);
+
+LXB_API void
+lexbor_plog_clean_noi(lexbor_plog_t *plog);
+
+LXB_API void *
+lexbor_plog_push_noi(lexbor_plog_t *plog, const lxb_char_t *data, void *ctx,
+                     unsigned id);
+
+LXB_API size_t
+lexbor_plog_length_noi(lexbor_plog_t *plog);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_PLOG_H */
+
diff --git a/contrib/url/lexbor/core/serialize.h b/contrib/url/lexbor/core/serialize.h
new file mode 100644
index 0000000000..1efdd8d664
--- /dev/null
+++ b/contrib/url/lexbor/core/serialize.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#ifndef LEXBOR_SERIALIZE_H
+#define LEXBOR_SERIALIZE_H
+
+
+#include "lexbor/core/base.h"
+
+
+#define lexbor_serialize_write(cb, data, length, ctx, status)                 \
+    do {                                                                      \
+        (status) = (cb)((lxb_char_t *) (data), (length), (ctx));              \
+        if ((status) != LXB_STATUS_OK) {                                      \
+            return (status);                                                  \
+        }                                                                     \
+    }                                                                         \
+    while (false)
+
+
+#endif /* LEXBOR_SERIALIZE_H */
diff --git a/contrib/url/lexbor/core/shs.c b/contrib/url/lexbor/core/shs.c
new file mode 100644
index 0000000000..679143bf45
--- /dev/null
+++ b/contrib/url/lexbor/core/shs.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2018-2019 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#include "lexbor/core/shs.h"
+#include "lexbor/core/str.h"
+
+#define LEXBOR_STR_RES_MAP_LOWERCASE
+#define LEXBOR_STR_RES_MAP_UPPERCASE
+#include "lexbor/core/str_res.h"
+
+
+#define lexbor_shs_make_id_m(key, size, table_size)                            \
+    (((((key[0] * key[size - 1]) * key[0]) + size) % table_size) + 0x01)
+
+#define lexbor_shs_make_id_lower_m(key, size, table_size)                      \
+    (((((lexbor_str_res_map_lowercase[key[0]]                                  \
+     * lexbor_str_res_map_lowercase[key[size - 1]])                            \
+     * lexbor_str_res_map_lowercase[key[0]])                                   \
+     + size)                                                                   \
+     % table_size) + 0x01)
+
+#define lexbor_shs_make_id_upper_m(key, size, table_size)                      \
+    (((((lexbor_str_res_map_uppercase[key[0]]                                  \
+     * lexbor_str_res_map_uppercase[key[size - 1]])                            \
+     * lexbor_str_res_map_uppercase[key[0]])                                   \
+     + size)                                                                   \
+     % table_size) + 0x01)
+
+
+const lexbor_shs_entry_t *
+lexbor_shs_entry_get_static(const lexbor_shs_entry_t *root,
+                            const lxb_char_t *key, size_t key_len)
+{
+    const lexbor_shs_entry_t *entry;
+    entry = root + lexbor_shs_make_id_m(key, key_len, root->key_len);
+
+    while (entry->key != NULL)
+    {
+        if (entry->key_len == key_len) {
+            if (lexbor_str_data_ncmp((const lxb_char_t *) entry->key,
+                                     key, key_len))
+            {
+                return entry;
+            }
+
+            entry = &root[entry->next];
+        }
+        else if (entry->key_len > key_len) {
+            return NULL;
+        }
+        else {
+            entry = &root[entry->next];
+        }
+    }
+
+    return NULL;
+}
+
+const lexbor_shs_entry_t *
+lexbor_shs_entry_get_lower_static(const lexbor_shs_entry_t *root,
+                                  const lxb_char_t *key, size_t key_len)
+{
+    const lexbor_shs_entry_t *entry;
+    entry = root + lexbor_shs_make_id_lower_m(key, key_len, root->key_len);
+
+    while (entry->key != NULL)
+    {
+        if (entry->key_len == key_len) {
+            if (lexbor_str_data_nlocmp_right((const lxb_char_t *) entry->key,
+                                             key, key_len))
+            {
+                return entry;
+            }
+
+            entry = &root[entry->next];
+        }
+        else if (entry->key_len > key_len) {
+            return NULL;
+        }
+        else {
+            entry = &root[entry->next];
+        }
+    }
+
+    return NULL;
+}
+
+const lexbor_shs_entry_t *
+lexbor_shs_entry_get_upper_static(const lexbor_shs_entry_t *root,
+                                  const lxb_char_t *key, size_t key_len)
+{
+    const lexbor_shs_entry_t *entry;
+    entry = root + lexbor_shs_make_id_upper_m(key, key_len, root->key_len);
+
+    while (entry->key != NULL)
+    {
+        if (entry->key_len == key_len) {
+            if (lexbor_str_data_nupcmp_right((const lxb_char_t *) entry->key,
+                                             key, key_len))
+            {
+                return entry;
+            }
+
+            entry = &root[entry->next];
+        }
+        else if (entry->key_len > key_len) {
+            return NULL;
+        }
+        else {
+            entry = &root[entry->next];
+        }
+    }
+
+    return NULL;
+}
diff --git a/contrib/url/lexbor/core/shs.h b/contrib/url/lexbor/core/shs.h
new file mode 100644
index 0000000000..7a63a07409
--- /dev/null
+++ b/contrib/url/lexbor/core/shs.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#ifndef LEXBOR_SHS_H
+#define LEXBOR_SHS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <string.h>
+
+#include "lexbor/core/base.h"
+
+
+typedef struct {
+    char   *key;
+    void   *value;
+
+    size_t key_len;
+    size_t next;
+}
+lexbor_shs_entry_t;
+
+typedef struct {
+    uint32_t key;
+    void     *value;
+
+    size_t   next;
+}
+lexbor_shs_hash_t;
+
+
+LXB_API const lexbor_shs_entry_t *
+lexbor_shs_entry_get_static(const lexbor_shs_entry_t *tree,
+                            const lxb_char_t *key, size_t size);
+
+LXB_API const lexbor_shs_entry_t *
+lexbor_shs_entry_get_lower_static(const lexbor_shs_entry_t *root,
+                                  const lxb_char_t *key, size_t key_len);
+
+LXB_API const lexbor_shs_entry_t *
+lexbor_shs_entry_get_upper_static(const lexbor_shs_entry_t *root,
+                                  const lxb_char_t *key, size_t key_len);
+
+/*
+ * Inline functions
+ */
+lxb_inline const lexbor_shs_hash_t *
+lexbor_shs_hash_get_static(const lexbor_shs_hash_t *table,
+                           const size_t table_size, const uint32_t key)
+{
+    const lexbor_shs_hash_t *entry;
+
+    entry = &table[ (key % table_size) + 1 ];
+
+    do {
+        if (entry->key == key) {
+            return entry;
+        }
+
+        entry = &table[entry->next];
+    }
+    while (entry != table);
+
+    return NULL;
+}
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_SHS_H */
+
+
+
+
+
diff --git a/contrib/url/lexbor/core/str.c b/contrib/url/lexbor/core/str.c
new file mode 100644
index 0000000000..0f04286bde
--- /dev/null
+++ b/contrib/url/lexbor/core/str.c
@@ -0,0 +1,642 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#include "lexbor/core/str.h"
+
+#define LEXBOR_STR_RES_ANSI_REPLACEMENT_CHARACTER
+#define LEXBOR_STR_RES_MAP_LOWERCASE
+#define LEXBOR_STR_RES_MAP_UPPERCASE
+#include "lexbor/core/str_res.h"
+
+
+lexbor_str_t *
+lexbor_str_create(void)
+{
+    return lexbor_calloc(1, sizeof(lexbor_str_t));
+}
+
+lxb_char_t *
+lexbor_str_init(lexbor_str_t *str, lexbor_mraw_t *mraw, size_t size)
+{
+    if (str == NULL) {
+        return NULL;
+    }
+
+    str->data = lexbor_mraw_alloc(mraw, (size + 1));
+    str->length = 0;
+
+    if (str->data != NULL) {
+        *str->data = '\0';
+    }
+
+    return str->data;
+}
+
+lxb_char_t *
+lexbor_str_init_append(lexbor_str_t *str, lexbor_mraw_t *mraw,
+                       const lxb_char_t *data, size_t length)
+{
+    lxb_char_t *p;
+
+    if (str == NULL) {
+        return NULL;
+    }
+
+    p = lexbor_mraw_alloc(mraw, (length + 1));
+    if (p == NULL) {
+        return NULL;
+    }
+
+    memcpy(p, data, length);
+
+    p[length] = '\0';
+
+    str->data = p;
+    str->length = length;
+
+    return p;
+}
+
+void
+lexbor_str_clean(lexbor_str_t *str)
+{
+    str->length = 0;
+}
+
+void
+lexbor_str_clean_all(lexbor_str_t *str)
+{
+    memset(str, 0, sizeof(lexbor_str_t));
+}
+
+lexbor_str_t *
+lexbor_str_destroy(lexbor_str_t *str, lexbor_mraw_t *mraw, bool destroy_obj)
+{
+    if (str == NULL) {
+        return NULL;
+    }
+
+    if (str->data != NULL) {
+        str->data = lexbor_mraw_free(mraw, str->data);
+    }
+
+    if (destroy_obj) {
+        return lexbor_free(str);
+    }
+
+    return str;
+}
+
+lxb_char_t *
+lexbor_str_realloc(lexbor_str_t *str, lexbor_mraw_t *mraw, size_t new_size)
+{
+    lxb_char_t *tmp = lexbor_mraw_realloc(mraw, str->data, new_size);
+    if (tmp == NULL) {
+        return NULL;
+    }
+
+    str->data = tmp;
+
+    return tmp;
+}
+
+lxb_char_t *
+lexbor_str_check_size(lexbor_str_t *str, lexbor_mraw_t *mraw, size_t plus_len)
+{
+    lxb_char_t *tmp;
+
+    if (str->length > (SIZE_MAX - plus_len)) {
+        return NULL;
+    }
+
+    if ((str->length + plus_len) <= lexbor_str_size(str)) {
+        return str->data;
+    }
+
+    tmp = lexbor_mraw_realloc(mraw, str->data, (str->length + plus_len));
+    if (tmp == NULL) {
+        return NULL;
+    }
+
+    str->data = tmp;
+
+    return tmp;
+}
+
+/* Append API */
+lxb_char_t *
+lexbor_str_append(lexbor_str_t *str, lexbor_mraw_t *mraw,
+                  const lxb_char_t *buff, size_t length)
+{
+    lxb_char_t *data_begin;
+
+    lexbor_str_check_size_arg_m(str, lexbor_str_size(str),
+                                mraw, (length + 1), NULL);
+
+    data_begin = &str->data[str->length];
+    memcpy(data_begin, buff, sizeof(lxb_char_t) * length);
+
+    str->length += length;
+    str->data[str->length] = '\0';
+
+    return data_begin;
+}
+
+lxb_char_t *
+lexbor_str_append_before(lexbor_str_t *str, lexbor_mraw_t *mraw,
+                         const lxb_char_t *buff, size_t length)
+{
+    lxb_char_t *data_begin;
+
+    lexbor_str_check_size_arg_m(str, lexbor_str_size(str),
+                                mraw, (length + 1), NULL);
+
+    data_begin = &str->data[str->length];
+
+    memmove(&str->data[length], str->data, sizeof(lxb_char_t) * str->length);
+    memcpy(str->data, buff, sizeof(lxb_char_t) * length);
+
+    str->length += length;
+    str->data[str->length] = '\0';
+
+    return data_begin;
+}
+
+lxb_char_t *
+lexbor_str_append_one(lexbor_str_t *str, lexbor_mraw_t *mraw,
+                      const lxb_char_t data)
+{
+    lexbor_str_check_size_arg_m(str, lexbor_str_size(str), mraw, 2, NULL);
+
+    str->data[str->length] = data;
+
+    str->length += 1;
+    str->data[str->length] = '\0';
+
+    return &str->data[(str->length - 1)];
+}
+
+lxb_char_t *
+lexbor_str_append_lowercase(lexbor_str_t *str, lexbor_mraw_t *mraw,
+                            const lxb_char_t *data, size_t length)
+{
+    size_t i;
+    lxb_char_t *data_begin;
+
+    lexbor_str_check_size_arg_m(str, lexbor_str_size(str),
+                                mraw, (length + 1), NULL);
+
+    data_begin = &str->data[str->length];
+
+    for (i = 0; i < length; i++) {
+        data_begin[i] = lexbor_str_res_map_lowercase[ data[i] ];
+    }
+
+    data_begin[i] = '\0';
+    str->length += length;
+
+    return data_begin;
+}
+
+lxb_char_t *
+lexbor_str_append_with_rep_null_chars(lexbor_str_t *str, lexbor_mraw_t *mraw,
+                                      const lxb_char_t *buff, size_t length)
+{
+    const lxb_char_t *pos, *res, *end;
+    size_t current_len = str->length;
+
+    lexbor_str_check_size_arg_m(str, lexbor_str_size(str),
+                                mraw, (length + 1), NULL);
+    end = buff + length;
+
+    while (buff != end) {
+        pos = memchr(buff, '\0', sizeof(lxb_char_t) * (end - buff));
+        if (pos == NULL) {
+            break;
+        }
+
+        res = lexbor_str_append(str, mraw, buff, (pos - buff));
+        if (res == NULL) {
+            return NULL;
+        }
+
+        res = lexbor_str_append(str, mraw,
+                         lexbor_str_res_ansi_replacement_character,
+                         sizeof(lexbor_str_res_ansi_replacement_character) - 1);
+        if (res == NULL) {
+            return NULL;
+        }
+
+        buff = pos + 1;
+    }
+
+    if (buff != end) {
+        res = lexbor_str_append(str, mraw, buff, (end - buff));
+        if (res == NULL) {
+            return NULL;
+        }
+    }
+
+    return &str->data[current_len];
+}
+
+lxb_char_t *
+lexbor_str_copy(lexbor_str_t *dest, const lexbor_str_t *target,
+                lexbor_mraw_t *mraw)
+{
+    if (target->data == NULL) {
+        return NULL;
+    }
+
+    if (dest->data == NULL) {
+        lexbor_str_init(dest, mraw, target->length);
+
+        if (dest->data == NULL) {
+            return NULL;
+        }
+    }
+
+    return lexbor_str_append(dest, mraw, target->data, target->length);
+}
+
+void
+lexbor_str_stay_only_whitespace(lexbor_str_t *target)
+{
+    size_t i, pos = 0;
+    lxb_char_t *data = target->data;
+
+    for (i = 0; i < target->length; i++) {
+        if (lexbor_utils_whitespace(data[i], ==, ||)) {
+            data[pos] = data[i];
+            pos++;
+        }
+    }
+
+    target->length = pos;
+}
+
+void
+lexbor_str_strip_collapse_whitespace(lexbor_str_t *target)
+{
+    size_t i, offset, ws_i;
+    lxb_char_t *data = target->data;
+
+    if (target->length == 0) {
+        return;
+    }
+
+    if (lexbor_utils_whitespace(*data, ==, ||)) {
+        *data = 0x20;
+    }
+
+    for (i = 0, offset = 0, ws_i = 0; i < target->length; i++)
+    {
+        if (lexbor_utils_whitespace(data[i], ==, ||)) {
+            if (data[ws_i] != 0x20) {
+                data[offset] = 0x20;
+
+                ws_i = offset;
+                offset++;
+            }
+        }
+        else {
+            if (data[ws_i] == 0x20) {
+                ws_i = offset;
+            }
+
+            data[offset] = data[i];
+            offset++;
+        }
+    }
+
+    if (offset != i) {
+        if (offset != 0) {
+            if (data[offset - 1] == 0x20) {
+                offset--;
+            }
+        }
+
+        data[offset] = 0x00;
+        target->length = offset;
+    }
+}
+
+size_t
+lexbor_str_crop_whitespace_from_begin(lexbor_str_t *target)
+{
+    size_t i;
+    lxb_char_t *data = target->data;
+
+    for (i = 0; i < target->length; i++) {
+        if (lexbor_utils_whitespace(data[i], !=, &&)) {
+            break;
+        }
+    }
+
+    if (i != 0 && i != target->length) {
+        memmove(target->data, &target->data[i], (target->length - i));
+    }
+
+    target->length -= i;
+    return i;
+}
+
+size_t
+lexbor_str_whitespace_from_begin(lexbor_str_t *target)
+{
+    size_t i;
+    lxb_char_t *data = target->data;
+
+    for (i = 0; i < target->length; i++) {
+        if (lexbor_utils_whitespace(data[i], !=, &&)) {
+            break;
+        }
+    }
+
+    return i;
+}
+
+size_t
+lexbor_str_whitespace_from_end(lexbor_str_t *target)
+{
+    size_t i = target->length;
+    lxb_char_t *data = target->data;
+
+    while (i) {
+        i--;
+
+        if (lexbor_utils_whitespace(data[i], !=, &&)) {
+            return target->length - (i + 1);
+        }
+    }
+
+    return 0;
+}
+
+/*
+ * Data utils
+ * TODO: All functions need optimization.
+ */
+const lxb_char_t *
+lexbor_str_data_ncasecmp_first(const lxb_char_t *first, const lxb_char_t *sec,
+                               size_t sec_size)
+{
+    size_t i;
+
+    for (i = 0; i < sec_size; i++) {
+        if (first[i] == '\0') {
+            return &first[i];
+        }
+
+        if (lexbor_str_res_map_lowercase[ first[i] ]
+            != lexbor_str_res_map_lowercase[ sec[i] ])
+        {
+            return NULL;
+        }
+    }
+
+    return &first[i];
+}
+
+bool
+lexbor_str_data_ncasecmp_end(const lxb_char_t *first, const lxb_char_t *sec,
+                             size_t size)
+{
+    while (size != 0) {
+        size--;
+
+        if (lexbor_str_res_map_lowercase[ first[size] ]
+            != lexbor_str_res_map_lowercase[ sec[size] ])
+        {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool
+lexbor_str_data_ncasecmp_contain(const lxb_char_t *where, size_t where_size,
+                                 const lxb_char_t *what, size_t what_size)
+{
+    for (size_t i = 0; what_size <= (where_size - i); i++) {
+        if(lexbor_str_data_ncasecmp(&where[i], what, what_size)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+bool
+lexbor_str_data_ncasecmp(const lxb_char_t *first, const lxb_char_t *sec,
+                         size_t size)
+{
+    for (size_t i = 0; i < size; i++) {
+        if (lexbor_str_res_map_lowercase[ first[i] ]
+            != lexbor_str_res_map_lowercase[ sec[i] ])
+        {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool
+lexbor_str_data_nlocmp_right(const lxb_char_t *first, const lxb_char_t *sec,
+                             size_t size)
+{
+    for (size_t i = 0; i < size; i++) {
+        if (first[i] != lexbor_str_res_map_lowercase[ sec[i] ]) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool
+lexbor_str_data_nupcmp_right(const lxb_char_t *first, const lxb_char_t *sec,
+                             size_t size)
+{
+    for (size_t i = 0; i < size; i++) {
+        if (first[i] != lexbor_str_res_map_uppercase[ sec[i] ]) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool
+lexbor_str_data_casecmp(const lxb_char_t *first, const lxb_char_t *sec)
+{
+    for (;;) {
+        if (lexbor_str_res_map_lowercase[*first]
+            != lexbor_str_res_map_lowercase[*sec])
+        {
+            return false;
+        }
+
+        if (*first == '\0') {
+            return true;
+        }
+
+        first++;
+        sec++;
+    }
+}
+
+bool
+lexbor_str_data_ncmp_end(const lxb_char_t *first, const lxb_char_t *sec,
+                         size_t size)
+{
+    while (size != 0) {
+        size--;
+
+        if (first[size] != sec[size]) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool
+lexbor_str_data_ncmp_contain(const lxb_char_t *where, size_t where_size,
+                             const lxb_char_t *what, size_t what_size)
+{
+    for (size_t i = 0; what_size <= (where_size - i); i++) {
+        if(memcmp(&where[i], what, sizeof(lxb_char_t) * what_size) == 0) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+bool
+lexbor_str_data_ncmp(const lxb_char_t *first, const lxb_char_t *sec,
+                     size_t size)
+{
+    return memcmp(first, sec, sizeof(lxb_char_t) * size) == 0;
+}
+
+bool
+lexbor_str_data_cmp(const lxb_char_t *first, const lxb_char_t *sec)
+{
+    for (;;) {
+        if (*first != *sec) {
+            return false;
+        }
+
+        if (*first == '\0') {
+            return true;
+        }
+
+        first++;
+        sec++;
+    }
+}
+
+bool
+lexbor_str_data_cmp_ws(const lxb_char_t *first, const lxb_char_t *sec)
+{
+    for (;;) {
+        if (*first != *sec) {
+            return false;
+        }
+
+        if (lexbor_utils_whitespace(*first, ==, ||) || *first == '\0') {
+            return true;
+        }
+
+        first++;
+        sec++;
+    }
+}
+
+void
+lexbor_str_data_to_lowercase(lxb_char_t *to, const lxb_char_t *from, size_t len)
+{
+    while (len) {
+        len--;
+
+        to[len] = lexbor_str_res_map_lowercase[ from[len] ];
+    }
+}
+
+void
+lexbor_str_data_to_uppercase(lxb_char_t *to, const lxb_char_t *from, size_t len)
+{
+    while (len) {
+        len--;
+
+        to[len] = lexbor_str_res_map_uppercase[ from[len] ];
+    }
+}
+
+const lxb_char_t *
+lexbor_str_data_find_lowercase(const lxb_char_t *data, size_t len)
+{
+    while (len) {
+        len--;
+
+        if (data[len] == lexbor_str_res_map_lowercase[ data[len] ]) {
+            return &data[len];
+        }
+    }
+
+    return NULL;
+}
+
+const lxb_char_t *
+lexbor_str_data_find_uppercase(const lxb_char_t *data, size_t len)
+{
+    while (len) {
+        len--;
+
+        if (data[len] == lexbor_str_res_map_uppercase[ data[len] ]) {
+            return &data[len];
+        }
+    }
+
+    return NULL;
+}
+
+/*
+ * No inline functions for ABI.
+ */
+lxb_char_t *
+lexbor_str_data_noi(lexbor_str_t *str)
+{
+    return lexbor_str_data(str);
+}
+
+size_t
+lexbor_str_length_noi(lexbor_str_t *str)
+{
+    return lexbor_str_length(str);
+}
+
+size_t
+lexbor_str_size_noi(lexbor_str_t *str)
+{
+    return lexbor_str_size(str);
+}
+
+void
+lexbor_str_data_set_noi(lexbor_str_t *str, lxb_char_t *data)
+{
+    lexbor_str_data_set(str, data);
+}
+
+lxb_char_t *
+lexbor_str_length_set_noi(lexbor_str_t *str, lexbor_mraw_t *mraw, size_t length)
+{
+    return lexbor_str_length_set(str, mraw, length);
+}
diff --git a/contrib/url/lexbor/core/str.h b/contrib/url/lexbor/core/str.h
new file mode 100644
index 0000000000..3bee145373
--- /dev/null
+++ b/contrib/url/lexbor/core/str.h
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2018-2023 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#ifndef LEXBOR_STR_H
+#define LEXBOR_STR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "lexbor/core/base.h"
+#include "lexbor/core/mraw.h"
+#include "lexbor/core/utils.h"
+
+
+#define lexbor_str_get(str, attr) str->attr
+#define lexbor_str_set(str, attr) lexbor_str_get(str, attr)
+#define lexbor_str_len(str) lexbor_str_get(str, length)
+#define lexbor_str(p) {.data = (lxb_char_t *) (p), sizeof(p) - 1}
+
+
+#define lexbor_str_check_size_arg_m(str, size, mraw, plus_len, return_fail)    \
+    do {                                                                       \
+        void *tmp;                                                             \
+                                                                               \
+        if (str->length > (SIZE_MAX - (plus_len)))                             \
+            return (return_fail);                                              \
+                                                                               \
+        if ((str->length + (plus_len)) > (size)) {                             \
+            tmp = lexbor_mraw_realloc(mraw, str->data,                         \
+                                      (str->length + plus_len));               \
+                                                                               \
+            if (tmp == NULL) {                                                 \
+                return (return_fail);                                          \
+            }                                                                  \
+                                                                               \
+            str->data = (lxb_char_t *) tmp;                                    \
+        }                                                                      \
+    }                                                                          \
+    while (0)
+
+
+typedef struct {
+    lxb_char_t *data;
+    size_t     length;
+}
+lexbor_str_t;
+
+
+LXB_API lexbor_str_t *
+lexbor_str_create(void);
+
+LXB_API lxb_char_t *
+lexbor_str_init(lexbor_str_t *str, lexbor_mraw_t *mraw, size_t size);
+
+LXB_API lxb_char_t *
+lexbor_str_init_append(lexbor_str_t *str, lexbor_mraw_t *mraw,
+                       const lxb_char_t *data, size_t length);
+
+LXB_API void
+lexbor_str_clean(lexbor_str_t *str);
+
+LXB_API void
+lexbor_str_clean_all(lexbor_str_t *str);
+
+LXB_API lexbor_str_t *
+lexbor_str_destroy(lexbor_str_t *str, lexbor_mraw_t *mraw, bool destroy_obj);
+
+
+LXB_API lxb_char_t *
+lexbor_str_realloc(lexbor_str_t *str, lexbor_mraw_t *mraw, size_t new_size);
+
+LXB_API lxb_char_t *
+lexbor_str_check_size(lexbor_str_t *str, lexbor_mraw_t *mraw, size_t plus_len);
+
+/* Append */
+LXB_API lxb_char_t *
+lexbor_str_append(lexbor_str_t *str, lexbor_mraw_t *mraw,
+                  const lxb_char_t *data, size_t length);
+
+LXB_API lxb_char_t *
+lexbor_str_append_before(lexbor_str_t *str, lexbor_mraw_t *mraw,
+                         const lxb_char_t *buff, size_t length);
+
+LXB_API lxb_char_t *
+lexbor_str_append_one(lexbor_str_t *str, lexbor_mraw_t *mraw,
+                      const lxb_char_t data);
+
+LXB_API lxb_char_t *
+lexbor_str_append_lowercase(lexbor_str_t *str, lexbor_mraw_t *mraw,
+                            const lxb_char_t *data, size_t length);
+
+LXB_API lxb_char_t *
+lexbor_str_append_with_rep_null_chars(lexbor_str_t *str, lexbor_mraw_t *mraw,
+                                      const lxb_char_t *buff, size_t length);
+
+/* Other functions */
+LXB_API lxb_char_t *
+lexbor_str_copy(lexbor_str_t *dest, const lexbor_str_t *target,
+                lexbor_mraw_t *mraw);
+
+LXB_API void
+lexbor_str_stay_only_whitespace(lexbor_str_t *target);
+
+LXB_API void
+lexbor_str_strip_collapse_whitespace(lexbor_str_t *target);
+
+LXB_API size_t
+lexbor_str_crop_whitespace_from_begin(lexbor_str_t *target);
+
+LXB_API size_t
+lexbor_str_whitespace_from_begin(lexbor_str_t *target);
+
+LXB_API size_t
+lexbor_str_whitespace_from_end(lexbor_str_t *target);
+
+
+/* Data utils */
+/*
+ * [in] first: must be null-terminated
+ * [in] sec: no matter what data
+ * [in] sec_size: size of the 'sec' buffer
+ *
+ * Function compare two lxb_char_t data until find '\0' in first arg.
+ * Successfully if the function returned a pointer starting with '\0',
+ * otherwise, if the data of the second buffer is insufficient function returned
+ * position in first buffer.
+ * If function returns NULL, the data are not equal.
+ */
+LXB_API const lxb_char_t *
+lexbor_str_data_ncasecmp_first(const lxb_char_t *first, const lxb_char_t *sec,
+                               size_t sec_size);
+LXB_API bool
+lexbor_str_data_ncasecmp_end(const lxb_char_t *first, const lxb_char_t *sec,
+                             size_t size);
+LXB_API bool
+lexbor_str_data_ncasecmp_contain(const lxb_char_t *where, size_t where_size,
+                                 const lxb_char_t *what, size_t what_size);
+LXB_API bool
+lexbor_str_data_ncasecmp(const lxb_char_t *first, const lxb_char_t *sec,
+                         size_t size);
+LXB_API bool
+lexbor_str_data_nlocmp_right(const lxb_char_t *first, const lxb_char_t *sec,
+                             size_t size);
+LXB_API bool
+lexbor_str_data_nupcmp_right(const lxb_char_t *first, const lxb_char_t *sec,
+                             size_t size);
+LXB_API bool
+lexbor_str_data_casecmp(const lxb_char_t *first, const lxb_char_t *sec);
+
+LXB_API bool
+lexbor_str_data_ncmp_end(const lxb_char_t *first, const lxb_char_t *sec,
+                         size_t size);
+LXB_API bool
+lexbor_str_data_ncmp_contain(const lxb_char_t *where, size_t where_size,
+                             const lxb_char_t *what, size_t what_size);
+LXB_API bool
+lexbor_str_data_ncmp(const lxb_char_t *first, const lxb_char_t *sec,
+                     size_t size);
+
+LXB_API bool
+lexbor_str_data_cmp(const lxb_char_t *first, const lxb_char_t *sec);
+
+LXB_API bool
+lexbor_str_data_cmp_ws(const lxb_char_t *first, const lxb_char_t *sec);
+
+LXB_API void
+lexbor_str_data_to_lowercase(lxb_char_t *to, const lxb_char_t *from, size_t len);
+
+LXB_API void
+lexbor_str_data_to_uppercase(lxb_char_t *to, const lxb_char_t *from, size_t len);
+
+LXB_API const lxb_char_t *
+lexbor_str_data_find_lowercase(const lxb_char_t *data, size_t len);
+
+LXB_API const lxb_char_t *
+lexbor_str_data_find_uppercase(const lxb_char_t *data, size_t len);
+
+
+/*
+ * Inline functions
+ */
+lxb_inline lxb_char_t *
+lexbor_str_data(lexbor_str_t *str)
+{
+    return str->data;
+}
+
+lxb_inline size_t
+lexbor_str_length(lexbor_str_t *str)
+{
+    return str->length;
+}
+
+lxb_inline size_t
+lexbor_str_size(lexbor_str_t *str)
+{
+    return lexbor_mraw_data_size(str->data);
+}
+
+lxb_inline void
+lexbor_str_data_set(lexbor_str_t *str, lxb_char_t *data)
+{
+    str->data = data;
+}
+
+lxb_inline lxb_char_t *
+lexbor_str_length_set(lexbor_str_t *str, lexbor_mraw_t *mraw, size_t length)
+{
+    if (length >= lexbor_str_size(str)) {
+        lxb_char_t *tmp;
+
+        tmp = lexbor_str_realloc(str, mraw, length + 1);
+        if (tmp == NULL) {
+            return NULL;
+        }
+    }
+
+    str->length = length;
+    str->data[length] = 0x00;
+
+    return str->data;
+}
+
+/*
+ * No inline functions for ABI.
+ */
+LXB_API lxb_char_t *
+lexbor_str_data_noi(lexbor_str_t *str);
+
+LXB_API size_t
+lexbor_str_length_noi(lexbor_str_t *str);
+
+LXB_API size_t
+lexbor_str_size_noi(lexbor_str_t *str);
+
+LXB_API void
+lexbor_str_data_set_noi(lexbor_str_t *str, lxb_char_t *data);
+
+LXB_API lxb_char_t *
+lexbor_str_length_set_noi(lexbor_str_t *str, lexbor_mraw_t *mraw,
+                          size_t length);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_STR_H */
diff --git a/contrib/url/lexbor/core/str_res.h b/contrib/url/lexbor/core/str_res.h
new file mode 100644
index 0000000000..cebbda8aa5
--- /dev/null
+++ b/contrib/url/lexbor/core/str_res.h
@@ -0,0 +1,420 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#ifndef LEXBOR_STR_RES_H
+#define LEXBOR_STR_RES_H
+
+#define LEXBOR_STR_RES_MAP_CHAR_OTHER      '\00'
+#define LEXBOR_STR_RES_MAP_CHAR_A_Z_a_z    '\01'
+#define LEXBOR_STR_RES_MAP_CHAR_WHITESPACE '\02'
+
+#define LEXBOR_STR_RES_SLIP 0xFF
+
+#endif /* LEXBOR_STR_RES_H */
+
+#ifdef LEXBOR_STR_RES_ANSI_REPLACEMENT_CHARACTER
+#ifndef LEXBOR_STR_RES_ANSI_REPLACEMENT_CHARACTER_ENABLED
+#define LEXBOR_STR_RES_ANSI_REPLACEMENT_CHARACTER_ENABLED
+static const lxb_char_t
+lexbor_str_res_ansi_replacement_character[] = "\xEF\xBF\xBD";
+#endif /* LEXBOR_STR_RES_ANSI_REPLACEMENT_CHARACTER_ENABLED */
+#endif /* LEXBOR_STR_RES_ANSI_REPLACEMENT_CHARACTER */
+
+#ifdef LEXBOR_STR_RES_MAP_NUM
+#ifndef LEXBOR_STR_RES_MAP_NUM_ENABLED
+#define LEXBOR_STR_RES_MAP_NUM_ENABLED
+static const lxb_char_t lexbor_str_res_map_num[] = {
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
+    0x06, 0x07, 0x08, 0x09, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff
+};
+#endif /* LEXBOR_STR_RES_MAP_NUM_ENABLED */
+#endif /* LEXBOR_STR_RES_MAP_NUM */
+
+
+#ifdef LEXBOR_STR_RES_MAP_HEX
+#ifndef LEXBOR_STR_RES_MAP_HEX_ENABLED
+#define LEXBOR_STR_RES_MAP_HEX_ENABLED
+static const lxb_char_t lexbor_str_res_map_hex[] = {
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
+    0x06, 0x07, 0x08, 0x09, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0a, 0x0b,
+    0x0c, 0x0d, 0x0e, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff
+};
+#endif /* LEXBOR_STR_RES_MAP_HEX_ENABLED */
+#endif /* LEXBOR_STR_RES_MAP_HEX */
+
+
+#ifdef LEXBOR_STR_RES_MAP_LOWERCASE
+#ifndef LEXBOR_STR_RES_MAP_LOWERCASE_ENABLED
+#define LEXBOR_STR_RES_MAP_LOWERCASE_ENABLED
+static const lxb_char_t lexbor_str_res_map_lowercase[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+    0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
+    0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a,
+    0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+    0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
+    0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
+    0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e,
+    0x3f, 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+    0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70,
+    0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
+    0x7a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62,
+    0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b,
+    0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74,
+    0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d,
+    0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86,
+    0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
+    0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1,
+    0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa,
+    0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3,
+    0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc,
+    0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5,
+    0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce,
+    0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+    0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0,
+    0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
+    0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2,
+    0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb,
+    0xfc, 0xfd, 0xfe, 0xff
+};
+#endif /* LEXBOR_STR_RES_MAP_LOWERCASE_ENABLED */
+#endif /* LEXBOR_STR_RES_MAP_LOWERCASE */
+
+
+#ifdef LEXBOR_STR_RES_MAP_UPPERCASE
+#ifndef LEXBOR_STR_RES_MAP_UPPERCASE_ENABLED
+#define LEXBOR_STR_RES_MAP_UPPERCASE_ENABLED
+static const lxb_char_t lexbor_str_res_map_uppercase[] = {
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+    0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
+    0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a,
+    0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
+    0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
+    0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35,
+    0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e,
+    0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+    0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50,
+    0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
+    0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x41, 0x42,
+    0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b,
+    0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54,
+    0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x7b, 0x7c, 0x7d,
+    0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86,
+    0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
+    0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1,
+    0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa,
+    0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3,
+    0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc,
+    0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5,
+    0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce,
+    0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+    0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0,
+    0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
+    0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2,
+    0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb,
+    0xfc, 0xfd, 0xfe, 0xff
+};
+#endif /* LEXBOR_STR_RES_MAP_UPPERCASE_ENABLED */
+#endif /* LEXBOR_STR_RES_MAP_UPPERCASE */
+
+
+#ifdef LEXBOR_STR_RES_REPLACEMENT_CHARACTER
+#ifndef LEXBOR_STR_RES_REPLACEMENT_CHARACTER_ENABLED
+#define LEXBOR_STR_RES_REPLACEMENT_CHARACTER_ENABLED
+static const size_t lexbor_str_res_replacement_character[] = {
+    65533, 1, 2, 3, 4, 5, 6, 7, 8,
+    9, 10, 11, 12, 13, 14, 15, 16, 17,
+    18, 19, 20, 21, 22, 23, 24, 25, 26,
+    27, 28, 29, 30, 31, 32, 33, 34, 35,
+    36, 37, 38, 39, 40, 41, 42, 43, 44,
+    45, 46, 47, 48, 49, 50, 51, 52, 53,
+    54, 55, 56, 57, 58, 59, 60, 61, 62,
+    63, 64, 65, 66, 67, 68, 69, 70, 71,
+    72, 73, 74, 75, 76, 77, 78, 79, 80,
+    81, 82, 83, 84, 85, 86, 87, 88, 89,
+    90, 91, 92, 93, 94, 95, 96, 97, 98,
+    99, 100, 101, 102, 103, 104, 105, 106, 107,
+    108, 109, 110, 111, 112, 113, 114, 115, 116,
+    117, 118, 119, 120, 121, 122, 123, 124, 125,
+    126, 127, 8364, 129, 8218, 402, 8222, 8230, 8224,
+    8225, 710, 8240, 352, 8249, 338, 141, 381, 143,
+    144, 8216, 8217, 8220, 8221, 8226, 8211, 8212, 732,
+    8482, 353, 8250, 339, 157, 382, 376
+};
+#endif /* LEXBOR_STR_RES_REPLACEMENT_CHARACTER_ENABLED */
+#endif /* LEXBOR_STR_RES_REPLACEMENT_CHARACTER */
+
+
+#ifdef LEXBOR_STR_RES_ALPHANUMERIC_CHARACTER
+#ifndef LEXBOR_STR_RES_ALPHANUMERIC_CHARACTER_ENABLED
+#define LEXBOR_STR_RES_ALPHANUMERIC_CHARACTER_ENABLED
+static const size_t lexbor_str_res_alphanumeric_character[] = {
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
+    0x06, 0x07, 0x08, 0x09, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0a,
+    0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0a, 0x0b, 0x0c, 0x0d,
+    0x0e, 0x0f, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0a,
+    0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0c, 0x0d,
+    0x0e, 0x0f, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0a,
+    0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0a, 0x0b, 0x0c, 0x0d,
+    0x0e, 0x0f, 0x0a, 0x0b, 0x0c, 0x0d, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff
+};
+#endif /* LEXBOR_STR_RES_ALPHANUMERIC_CHARACTER_ENABLED */
+#endif /* LEXBOR_STR_RES_ALPHANUMERIC_CHARACTER */
+
+
+#ifdef LEXBOR_STR_RES_ALPHA_CHARACTER
+#ifndef LEXBOR_STR_RES_ALPHA_CHARACTER_ENABLED
+#define LEXBOR_STR_RES_ALPHA_CHARACTER_ENABLED
+static const size_t lexbor_str_res_alpha_character[] = {
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0a,
+    0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0a, 0x0b, 0x0c, 0x0d,
+    0x0e, 0x0f, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0a,
+    0x0b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0c, 0x0d,
+    0x0e, 0x0f, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0a,
+    0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0a, 0x0b, 0x0c, 0x0d,
+    0x0e, 0x0f, 0x0a, 0x0b, 0x0c, 0x0d, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff
+};
+#endif /* LEXBOR_STR_RES_ALPHA_CHARACTER_ENABLED */
+#endif /* LEXBOR_STR_RES_ALPHA_CHARACTER */
+
+
+#ifdef LEXBOR_TOKENIZER_CHARS_MAP
+#ifndef LEXBOR_TOKENIZER_CHARS_MAP_ENABLED
+#define LEXBOR_TOKENIZER_CHARS_MAP_ENABLED
+static const unsigned char lexbor_tokenizer_chars_map[] = {
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0x02, 0x02, 0xff, 0x02, 0x02, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0x02, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+    0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x01,
+    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff
+};
+#endif /* LEXBOR_TOKENIZER_CHARS_MAP_ENABLED */
+#endif /* LEXBOR_TOKENIZER_CHARS_MAP */
+
+
+#ifdef LEXBOR_STR_RES_MAP_HEX_TO_CHAR
+#ifndef LEXBOR_STR_RES_MAP_HEX_TO_CHAR_ENABLED
+#define LEXBOR_STR_RES_MAP_HEX_TO_CHAR_ENABLED
+static const lxb_char_t lexbor_str_res_map_hex_to_char[] = {
+    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
+    0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x00
+};
+#endif /* LEXBOR_STR_RES_MAP_HEX_TO_CHAR_ENABLED */
+#endif /* LEXBOR_STR_RES_MAP_HEX_TO_CHAR */
+
+#ifdef LEXBOR_STR_RES_MAP_HEX_TO_CHAR_LOWERCASE
+#ifndef LEXBOR_STR_RES_MAP_HEX_TO_CHAR_LOWERCASE_ENABLED
+#define LEXBOR_STR_RES_MAP_HEX_TO_CHAR_LOWERCASE_ENABLED
+static const lxb_char_t lexbor_str_res_map_hex_to_char_lowercase[] = {
+    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
+    0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x00
+};
+#endif /* LEXBOR_STR_RES_MAP_HEX_TO_CHAR_LOWERCASE_ENABLED */
+#endif /* LEXBOR_STR_RES_MAP_HEX_TO_CHAR_LOWERCASE */
+
+
+#ifdef LEXBOR_STR_RES_CHAR_TO_TWO_HEX_VALUE
+#ifndef LEXBOR_STR_RES_CHAR_TO_TWO_HEX_VALUE_ENABLED
+#define LEXBOR_STR_RES_CHAR_TO_TWO_HEX_VALUE_ENABLED
+static const char * lexbor_str_res_char_to_two_hex_value[257] = {
+    "00", "01", "02", "03", "04", "05", "06", "07",
+    "08", "09", "0A", "0B", "0C", "0D", "0E", "0F",
+    "10", "11", "12", "13", "14", "15", "16", "17",
+    "18", "19", "1A", "1B", "1C", "1D", "1E", "1F",
+    "20", "21", "22", "23", "24", "25", "26", "27",
+    "28", "29", "2A", "2B", "2C", "2D", "2E", "2F",
+    "30", "31", "32", "33", "34", "35", "36", "37",
+    "38", "39", "3A", "3B", "3C", "3D", "3E", "3F",
+    "40", "41", "42", "43", "44", "45", "46", "47",
+    "48", "49", "4A", "4B", "4C", "4D", "4E", "4F",
+    "50", "51", "52", "53", "54", "55", "56", "57",
+    "58", "59", "5A", "5B", "5C", "5D", "5E", "5F",
+    "60", "61", "62", "63", "64", "65", "66", "67",
+    "68", "69", "6A", "6B", "6C", "6D", "6E", "6F",
+    "70", "71", "72", "73", "74", "75", "76", "77",
+    "78", "79", "7A", "7B", "7C", "7D", "7E", "7F",
+    "80", "81", "82", "83", "84", "85", "86", "87",
+    "88", "89", "8A", "8B", "8C", "8D", "8E", "8F",
+    "90", "91", "92", "93", "94", "95", "96", "97",
+    "98", "99", "9A", "9B", "9C", "9D", "9E", "9F",
+    "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7",
+    "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF",
+    "B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7",
+    "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF",
+    "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7",
+    "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF",
+    "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7",
+    "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF",
+    "E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7",
+    "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF",
+    "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7",
+    "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF",
+    NULL
+};
+#endif /* LEXBOR_STR_RES_CHAR_TO_TWO_HEX_VALUE_ENABLED */
+#endif /* LEXBOR_STR_RES_CHAR_TO_TWO_HEX_VALUE */
+
+#ifdef LEXBOR_STR_RES_CHAR_TO_TWO_HEX_VALUE_LOWERCASE
+#ifndef LEXBOR_STR_RES_CHAR_TO_TWO_HEX_VALUE_LOWERCASE_ENABLED
+#define LEXBOR_STR_RES_CHAR_TO_TWO_HEX_VALUE_LOWERCASE_ENABLED
+static const char * lexbor_str_res_char_to_two_hex_value_lowercase[257] = {
+    "00", "01", "02", "03", "04", "05", "06", "07",
+    "08", "09", "0a", "0b", "0c", "0d", "0e", "0f",
+    "10", "11", "12", "13", "14", "15", "16", "17",
+    "18", "19", "1a", "1b", "1c", "1d", "1e", "1f",
+    "20", "21", "22", "23", "24", "25", "26", "27",
+    "28", "29", "2a", "2b", "2c", "2d", "2e", "2f",
+    "30", "31", "32", "33", "34", "35", "36", "37",
+    "38", "39", "3a", "3b", "3c", "3d", "3e", "3f",
+    "40", "41", "42", "43", "44", "45", "46", "47",
+    "48", "49", "4a", "4b", "4c", "4d", "4e", "4f",
+    "50", "51", "52", "53", "54", "55", "56", "57",
+    "58", "59", "5a", "5b", "5c", "5d", "5e", "5f",
+    "60", "61", "62", "63", "64", "65", "66", "67",
+    "68", "69", "6a", "6b", "6c", "6d", "6e", "6f",
+    "70", "71", "72", "73", "74", "75", "76", "77",
+    "78", "79", "7a", "7b", "7c", "7d", "7e", "7f",
+    "80", "81", "82", "83", "84", "85", "86", "87",
+    "88", "89", "8a", "8b", "8c", "8d", "8e", "8f",
+    "90", "91", "92", "93", "94", "95", "96", "97",
+    "98", "99", "9a", "9b", "9c", "9d", "9e", "9f",
+    "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7",
+    "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af",
+    "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7",
+    "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf",
+    "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7",
+    "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf",
+    "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7",
+    "d8", "d9", "da", "db", "dc", "dd", "de", "df",
+    "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7",
+    "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef",
+    "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7",
+    "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff",
+    NULL
+};
+#endif /* LEXBOR_STR_RES_CHAR_TO_TWO_HEX_VALUE_LOWERCASE_ENABLED */
+#endif /* LEXBOR_STR_RES_CHAR_TO_TWO_HEX_VALUE_LOWERCASE */
diff --git a/contrib/url/lexbor/core/strtod.c b/contrib/url/lexbor/core/strtod.c
new file mode 100644
index 0000000000..6389fc3156
--- /dev/null
+++ b/contrib/url/lexbor/core/strtod.c
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) Alexander Borisov
+ *
+ * Based on nxt_strtod.c from NGINX NJS project
+ *
+ * An internal strtod() implementation based upon V8 src/strtod.cc
+ * without bignum support.
+ *
+ * Copyright 2012 the V8 project authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file.
+ */
+
+#include <stdint.h>
+#include <math.h>
+
+#include "lexbor/core/diyfp.h"
+#include "lexbor/core/strtod.h"
+
+
+/*
+ * Max double: 1.7976931348623157 x 10^308
+ * Min non-zero double: 4.9406564584124654 x 10^-324
+ * Any x >= 10^309 is interpreted as +infinity.
+ * Any x <= 10^-324 is interpreted as 0.
+ * Note that 2.5e-324 (despite being smaller than the min double)
+ * will be read as non-zero (equal to the min non-zero double).
+ */
+
+#define LEXBOR_DECIMAL_POWER_MAX 309
+#define LEXBOR_DECIMAL_POWER_MIN (-324)
+
+#define LEXBOR_UINT64_MAX lexbor_uint64_hl(0xFFFFFFFF, 0xFFFFFFFF)
+#define LEXBOR_UINT64_DECIMAL_DIGITS_MAX 19
+
+#define LEXBOR_DENOM_LOG   3
+#define LEXBOR_DENOM       (1 << LEXBOR_DENOM_LOG)
+
+
+static lexbor_diyfp_t
+lexbor_strtod_diyfp_read(const lxb_char_t *start, size_t length,
+                         int *remaining);
+
+static double
+lexbor_strtod_diyfp_strtod(const lxb_char_t *start, size_t length, int exp);
+
+
+/*
+ * Reads digits from the buffer and converts them to a uint64.
+ * Reads in as many digits as fit into a uint64.
+ * When the string starts with "1844674407370955161" no further digit is read.
+ * Since 2^64 = 18446744073709551616 it would still be possible read another
+ * digit if it was less or equal than 6, but this would complicate the code.
+ */
+lxb_inline uint64_t
+lexbor_strtod_read_uint64(const lxb_char_t *start, size_t length,
+                          size_t *ndigits)
+{
+    lxb_char_t d;
+    uint64_t value;
+    const lxb_char_t *p, *e;
+
+    value = 0;
+
+    p = start;
+    e = p + length;
+
+    while (p < e && value <= (UINT64_MAX / 10 - 1)) {
+        d = *p++ - '0';
+        value = 10 * value + d;
+    }
+
+    *ndigits = p - start;
+
+    return value;
+}
+
+/*
+ * Reads a nxt_diyfp_t from the buffer.
+ * The returned nxt_diyfp_t is not necessarily normalized.
+ * If remaining is zero then the returned nxt_diyfp_t is accurate.
+ * Otherwise it has been rounded and has error of at most 1/2 ulp.
+ */
+static lexbor_diyfp_t
+lexbor_strtod_diyfp_read(const lxb_char_t *start, size_t length, int *remaining)
+{
+    size_t read;
+    uint64_t significand;
+
+    significand = lexbor_strtod_read_uint64(start, length, &read);
+
+    /* Round the significand. */
+
+    if (length != read) {
+        if (start[read] >= '5') {
+            significand++;
+        }
+    }
+
+    *remaining = (int) (length - read);
+
+    return lexbor_diyfp(significand, 0);
+}
+
+/*
+ * Returns 10^exp as an exact nxt_diyfp_t.
+ * The given exp must be in the range [1; NXT_DECIMAL_EXPONENT_DIST[.
+ */
+lxb_inline lexbor_diyfp_t
+lexbor_strtod_adjust_pow10(int exp)
+{
+    switch (exp) {
+    case 1:
+        return lexbor_diyfp(lexbor_uint64_hl(0xa0000000, 00000000), -60);
+    case 2:
+        return lexbor_diyfp(lexbor_uint64_hl(0xc8000000, 00000000), -57);
+    case 3:
+        return lexbor_diyfp(lexbor_uint64_hl(0xfa000000, 00000000), -54);
+    case 4:
+        return lexbor_diyfp(lexbor_uint64_hl(0x9c400000, 00000000), -50);
+    case 5:
+        return lexbor_diyfp(lexbor_uint64_hl(0xc3500000, 00000000), -47);
+    case 6:
+        return lexbor_diyfp(lexbor_uint64_hl(0xf4240000, 00000000), -44);
+    case 7:
+        return lexbor_diyfp(lexbor_uint64_hl(0x98968000, 00000000), -40);
+    default:
+        return lexbor_diyfp(0, 0);
+    }
+}
+
+/*
+ * Returns the significand size for a given order of magnitude.
+ * If v = f*2^e with 2^p-1 <= f <= 2^p then p+e is v's order of magnitude.
+ * This function returns the number of significant binary digits v will have
+ * once its encoded into a double. In almost all cases this is equal to
+ * NXT_SIGNIFICAND_SIZE. The only exception are denormals. They start with
+ * leading zeroes and their effective significand-size is hence smaller.
+ */
+lxb_inline int
+lexbor_strtod_diyfp_sgnd_size(int order)
+{
+    if (order >= (LEXBOR_DBL_EXPONENT_DENORMAL + LEXBOR_SIGNIFICAND_SIZE)) {
+        return LEXBOR_SIGNIFICAND_SIZE;
+    }
+
+    if (order <= LEXBOR_DBL_EXPONENT_DENORMAL) {
+        return 0;
+    }
+
+    return order - LEXBOR_DBL_EXPONENT_DENORMAL;
+}
+
+/*
+ * Returns either the correct double or the double that is just below
+ * the correct double.
+ */
+static double
+lexbor_strtod_diyfp_strtod(const lxb_char_t *start, size_t length, int exp)
+{
+    int magnitude, prec_digits;
+    int remaining, dec_exp, adj_exp, orig_e, shift;
+    int64_t error;
+    uint64_t prec_bits, half_way;
+    lexbor_diyfp_t value, pow, adj_pow, rounded;
+
+    value = lexbor_strtod_diyfp_read(start, length, &remaining);
+
+    exp += remaining;
+
+    /*
+     * Since some digits may have been dropped the value is not accurate.
+     * If remaining is different than 0 than the error is at most .5 ulp
+     * (unit in the last place).
+     * Using a common denominator to avoid dealing with fractions.
+     */
+
+    error = (remaining == 0 ? 0 : LEXBOR_DENOM / 2);
+
+    orig_e = value.exp;
+    value = lexbor_diyfp_normalize(value);
+    error <<= orig_e - value.exp;
+
+    if (exp < LEXBOR_DECIMAL_EXPONENT_MIN) {
+        return 0.0;
+    }
+
+    pow = lexbor_cached_power_dec(exp, &dec_exp);
+
+    if (dec_exp != exp) {
+        adj_exp = exp - dec_exp;
+        adj_pow = lexbor_strtod_adjust_pow10(exp - dec_exp);
+        value = lexbor_diyfp_mul(value, adj_pow);
+
+        if (LEXBOR_UINT64_DECIMAL_DIGITS_MAX - (int) length < adj_exp) {
+            /*
+             * The adjustment power is exact. There is hence only
+             * an error of 0.5.
+             */
+            error += LEXBOR_DENOM / 2;
+        }
+    }
+
+    value = lexbor_diyfp_mul(value, pow);
+
+    /*
+     * The error introduced by a multiplication of a * b equals
+     *  error_a + error_b + error_a * error_b / 2^64 + 0.5
+     *  Substituting a with 'value' and b with 'pow':
+     *  error_b = 0.5  (all cached powers have an error of less than 0.5 ulp),
+     *  error_ab = 0 or 1 / NXT_DENOM > error_a * error_b / 2^64.
+     */
+
+    error += LEXBOR_DENOM + (error != 0 ? 1 : 0);
+
+    orig_e = value.exp;
+    value = lexbor_diyfp_normalize(value);
+    error <<= orig_e - value.exp;
+
+    /*
+     * Check whether the double's significand changes when the error is added
+     * or subtracted.
+     */
+
+    magnitude = LEXBOR_DIYFP_SIGNIFICAND_SIZE + value.exp;
+    prec_digits = LEXBOR_DIYFP_SIGNIFICAND_SIZE
+        - lexbor_strtod_diyfp_sgnd_size(magnitude);
+
+    if (prec_digits + LEXBOR_DENOM_LOG >= LEXBOR_DIYFP_SIGNIFICAND_SIZE) {
+        /*
+         * This can only happen for very small denormals. In this case the
+         * half-way multiplied by the denominator exceeds the range of uint64.
+         * Simply shift everything to the right.
+         */
+        shift = prec_digits + LEXBOR_DENOM_LOG
+            - LEXBOR_DIYFP_SIGNIFICAND_SIZE + 1;
+
+        value = lexbor_diyfp_shift_right(value, shift);
+
+        /*
+         * Add 1 for the lost precision of error, and NXT_DENOM
+         * for the lost precision of value.significand.
+         */
+        error = (error >> shift) + 1 + LEXBOR_DENOM;
+        prec_digits -= shift;
+    }
+
+    prec_bits = value.significand & (((uint64_t) 1 << prec_digits) - 1);
+    prec_bits *= LEXBOR_DENOM;
+
+    half_way = (uint64_t) 1 << (prec_digits - 1);
+    half_way *= LEXBOR_DENOM;
+
+    rounded = lexbor_diyfp_shift_right(value, prec_digits);
+
+    if (prec_bits >= half_way + error) {
+        rounded.significand++;
+    }
+
+    return lexbor_diyfp_2d(rounded);
+}
+
+double
+lexbor_strtod_internal(const lxb_char_t *start, size_t length, int exp)
+{
+    size_t left, right;
+    const lxb_char_t *p, *e, *b;
+
+    /* Trim leading zeroes. */
+
+    p = start;
+    e = p + length;
+
+    while (p < e) {
+        if (*p != '0') {
+            start = p;
+            break;
+        }
+
+        p++;
+    }
+
+    left = e - p;
+
+    /* Trim trailing zeroes. */
+
+    b = start;
+    p = b + left - 1;
+
+    while (p > b) {
+        if (*p != '0') {
+            break;
+        }
+
+        p--;
+    }
+
+    right = p - b + 1;
+
+    length = right;
+
+    if (length == 0) {
+        return 0.0;
+    }
+
+    exp += (int) (left - right);
+
+    if (exp + (int) length - 1 >= LEXBOR_DECIMAL_POWER_MAX) {
+        return INFINITY;
+    }
+
+    if (exp + (int) length <= LEXBOR_DECIMAL_POWER_MIN) {
+        return 0.0;
+    }
+
+    return lexbor_strtod_diyfp_strtod(start, length, exp);
+}
+
+#undef LEXBOR_DECIMAL_POWER_MAX
+#undef LEXBOR_DECIMAL_POWER_MIN
+
+#undef LEXBOR_UINT64_MAX
+#undef LEXBOR_UINT64_DECIMAL_DIGITS_MAX
+
+#undef LEXBOR_DENOM_LOG
+#undef LEXBOR_DENOM
diff --git a/contrib/url/lexbor/core/strtod.h b/contrib/url/lexbor/core/strtod.h
new file mode 100644
index 0000000000..7f15706bc9
--- /dev/null
+++ b/contrib/url/lexbor/core/strtod.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) Alexander Borisov
+ *
+ * Based on nxt_strtod.h from NGINX NJS project
+ *
+ * Copyright (C) Dmitry Volyntsev
+ * Copyright (C) Nginx, Inc.
+ */
+
+#ifndef LEXBOR_STRTOD_H
+#define LEXBOR_STRTOD_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "lexbor/core/base.h"
+
+
+LXB_API double
+lexbor_strtod_internal(const lxb_char_t *start, size_t length, int exp);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_STRTOD_H */
diff --git a/contrib/url/lexbor/core/swar.h b/contrib/url/lexbor/core/swar.h
new file mode 100644
index 0000000000..992bf2037b
--- /dev/null
+++ b/contrib/url/lexbor/core/swar.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 Alexander Borisov
+ *
+ * Author: Niels Dossche <nielsdos@php.net>
+ */
+
+#ifndef LEXBOR_SWAR_H
+#define LEXBOR_SWAR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#include "lexbor/core/base.h"
+
+
+/*
+ * Based on techniques from https://graphics.stanford.edu/~seander/bithacks.html
+ */
+#define LEXBOR_SWAR_ONES (~((size_t) 0) / 0xFF)
+#define LEXBOR_SWAR_REPEAT(x) (LEXBOR_SWAR_ONES * (x))
+#define LEXBOR_SWAR_HAS_ZERO(v) (((v) - LEXBOR_SWAR_ONES) & ~(v) & LEXBOR_SWAR_REPEAT(0x80))
+#define LEXBOR_SWAR_IS_LITTLE_ENDIAN (*(unsigned char *) &(uint16_t){1})
+
+
+/*
+ * When handling hot loops that search for a set of characters,
+ * this function can be used to quickly move the data pointer much
+ * closer to the first occurrence of such a character.
+ */
+lxb_inline const lxb_char_t *
+lexbor_swar_seek4(const lxb_char_t *data, const lxb_char_t *end,
+                  lxb_char_t c1, lxb_char_t c2, lxb_char_t c3, lxb_char_t c4)
+{
+    size_t bytes, matches, t1, t2, t3, t4;
+
+    if (LEXBOR_SWAR_IS_LITTLE_ENDIAN) {
+        while (data + sizeof(size_t) <= end) {
+            memcpy(&bytes, data, sizeof(size_t));
+
+            t1 = bytes ^ LEXBOR_SWAR_REPEAT(c1);
+            t2 = bytes ^ LEXBOR_SWAR_REPEAT(c2);
+            t3 = bytes ^ LEXBOR_SWAR_REPEAT(c3);
+            t4 = bytes ^ LEXBOR_SWAR_REPEAT(c4);
+            matches =   LEXBOR_SWAR_HAS_ZERO(t1) | LEXBOR_SWAR_HAS_ZERO(t2)
+                      | LEXBOR_SWAR_HAS_ZERO(t3) | LEXBOR_SWAR_HAS_ZERO(t4);
+
+            if (matches) {
+                data += ((((matches - 1) & LEXBOR_SWAR_ONES) * LEXBOR_SWAR_ONES)
+                        >> (sizeof(size_t) * 8 - 8)) - 1;
+                break;
+            } else {
+                data += sizeof(size_t);
+            }
+        }
+    }
+
+    return data;
+}
+
+lxb_inline const lxb_char_t *
+lexbor_swar_seek3(const lxb_char_t *data, const lxb_char_t *end,
+                  lxb_char_t c1, lxb_char_t c2, lxb_char_t c3)
+{
+    size_t bytes, matches, t1, t2, t3;
+
+    if (LEXBOR_SWAR_IS_LITTLE_ENDIAN) {
+        while (data + sizeof(size_t) <= end) {
+            memcpy(&bytes, data, sizeof(size_t));
+
+            t1 = bytes ^ LEXBOR_SWAR_REPEAT(c1);
+            t2 = bytes ^ LEXBOR_SWAR_REPEAT(c2);
+            t3 = bytes ^ LEXBOR_SWAR_REPEAT(c3);
+            matches =   LEXBOR_SWAR_HAS_ZERO(t1) | LEXBOR_SWAR_HAS_ZERO(t2)
+                       | LEXBOR_SWAR_HAS_ZERO(t3);
+
+            if (matches) {
+                data += ((((matches - 1) & LEXBOR_SWAR_ONES) * LEXBOR_SWAR_ONES)
+                         >> (sizeof(size_t) * 8 - 8)) - 1;
+                break;
+            } else {
+                data += sizeof(size_t);
+            }
+        }
+    }
+
+    return data;
+}
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_SWAR_H */
+
diff --git a/contrib/url/lexbor/core/types.h b/contrib/url/lexbor/core/types.h
new file mode 100644
index 0000000000..00f82ad1e5
--- /dev/null
+++ b/contrib/url/lexbor/core/types.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#ifndef LEXBOR_TYPES_H
+#define LEXBOR_TYPES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/* Inline */
+#ifdef _MSC_VER
+    #define lxb_inline static __forceinline
+#else
+    #define lxb_inline static inline
+#endif
+
+
+/* Simple types */
+typedef uint32_t      lxb_codepoint_t;
+typedef unsigned char lxb_char_t;
+typedef unsigned int  lxb_status_t;
+
+/* Callbacks */
+typedef lxb_status_t (*lexbor_callback_f)(const lxb_char_t *buffer,
+                                          size_t size, void *ctx);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_TYPES_H */
diff --git a/contrib/url/lexbor/core/utils.h b/contrib/url/lexbor/core/utils.h
new file mode 100644
index 0000000000..c02c854ce5
--- /dev/null
+++ b/contrib/url/lexbor/core/utils.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#ifndef LEXBOR_UTILS_H
+#define LEXBOR_UTILS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "lexbor/core/base.h"
+
+
+#define lexbor_utils_whitespace(onechar, action, logic)                        \
+    (onechar action ' '  logic                                                 \
+     onechar action '\t' logic                                                 \
+     onechar action '\n' logic                                                 \
+     onechar action '\f' logic                                                 \
+     onechar action '\r')
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_UTILS_H */
diff --git a/contrib/url/lexbor/url/base.h b/contrib/url/lexbor/url/base.h
new file mode 100644
index 0000000000..596fb92a0f
--- /dev/null
+++ b/contrib/url/lexbor/url/base.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2023-2024 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#ifndef LEXBOR_URL_BASE_H
+#define LEXBOR_URL_BASE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "lexbor/core/base.h"
+#include "lexbor/core/mraw.h"
+#include "lexbor/core/str.h"
+
+
+#define LXB_URL_VERSION_MAJOR 0
+#define LXB_URL_VERSION_MINOR 2
+#define LXB_URL_VERSION_PATCH 0
+
+#define LXB_URL_VERSION_STRING LEXBOR_STRINGIZE(LXB_URL_VERSION_MAJOR) "."    \
+                               LEXBOR_STRINGIZE(LXB_URL_VERSION_MINOR) "."    \
+                               LEXBOR_STRINGIZE(LXB_URL_VERSION_PATCH)
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_URL_BASE_H */
diff --git a/contrib/url/lexbor/url/url.c b/contrib/url/lexbor/url/url.c
new file mode 100644
index 0000000000..074964c3a4
--- /dev/null
+++ b/contrib/url/lexbor/url/url.c
@@ -0,0 +1,5049 @@
+/*
+ * Copyright (C) 2023-2024 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ */
+
+#include <math.h>
+#include <float.h>
+#include <unicode/uidna.h>
+#include <unicode/utypes.h>
+#include <unicode/parseerr.h>
+
+#include "lexbor/url/url.h"
+#include "lexbor/core/conv.h"
+#include "lexbor/core/utils.h"
+#include "lexbor/core/serialize.h"
+#include "lexbor/core/swar.h"
+
+#define LEXBOR_STR_RES_MAP_LOWERCASE
+#define LEXBOR_STR_RES_ALPHANUMERIC_CHARACTER
+#define LEXBOR_STR_RES_ALPHA_CHARACTER
+#define LEXBOR_STR_RES_CHAR_TO_TWO_HEX_VALUE
+#define LEXBOR_STR_RES_MAP_HEX
+#define LEXBOR_STR_RES_MAP_NUM
+#include "lexbor/core/str_res.h"
+
+
+#define LXB_URL_BUFFER_SIZE 4096
+#define LXB_URL_BUFFER_NUM_SIZE 128
+#define LXB_URL_DECODE_ERROR 0x1FFFFF
+
+
+typedef enum {
+    LXB_URL_MAP_UNDEF         = 0x00,
+    LXB_URL_MAP_C0            = 0x01,
+    LXB_URL_MAP_FRAGMENT      = 0x02,
+    LXB_URL_MAP_QUERY         = 0x04,
+    LXB_URL_MAP_SPECIAL_QUERY = 0x08,
+    LXB_URL_MAP_PATH          = 0x10,
+    LXB_URL_MAP_USERINFO      = 0x20,
+    LXB_URL_MAP_COMPONENT     = 0x40,
+    LXB_URL_MAP_ALL           = 0xff
+}
+lxb_url_map_type_t;
+
+typedef enum {
+    LXB_URL_HOST_OPT_UNDEF       = 0 << 0,
+    LXB_URL_HOST_OPT_NOT_SPECIAL = 1 << 0,
+    LXB_URL_HOST_OPT_DECODE      = 1 << 1,
+    LXB_URL_HOST_OPT_IDNA        = 1 << 2
+}
+lxb_url_host_opt_t;
+
+typedef struct {
+    lexbor_mraw_t *mraw;
+    lexbor_str_t  *str;
+}
+lxb_url_idna_ctx_t;
+
+
+static const uint8_t lxb_url_map[256] =
+{
+    LXB_URL_MAP_ALL, /* 0x00 */
+    LXB_URL_MAP_ALL, /* 0x01 */
+    LXB_URL_MAP_ALL, /* 0x02 */
+    LXB_URL_MAP_ALL, /* 0x03 */
+    LXB_URL_MAP_ALL, /* 0x04 */
+    LXB_URL_MAP_ALL, /* 0x05 */
+    LXB_URL_MAP_ALL, /* 0x06 */
+    LXB_URL_MAP_ALL, /* 0x07 */
+    LXB_URL_MAP_ALL, /* 0x08 */
+    LXB_URL_MAP_ALL, /* 0x09 */
+    LXB_URL_MAP_ALL, /* 0x0a */
+    LXB_URL_MAP_ALL, /* 0x0b */
+    LXB_URL_MAP_ALL, /* 0x0c */
+    LXB_URL_MAP_ALL, /* 0x0d */
+    LXB_URL_MAP_ALL, /* 0x0e */
+    LXB_URL_MAP_ALL, /* 0x0f */
+    LXB_URL_MAP_ALL, /* 0x10 */
+    LXB_URL_MAP_ALL, /* 0x11 */
+    LXB_URL_MAP_ALL, /* 0x12 */
+    LXB_URL_MAP_ALL, /* 0x13 */
+    LXB_URL_MAP_ALL, /* 0x14 */
+    LXB_URL_MAP_ALL, /* 0x15 */
+    LXB_URL_MAP_ALL, /* 0x16 */
+    LXB_URL_MAP_ALL, /* 0x17 */
+    LXB_URL_MAP_ALL, /* 0x18 */
+    LXB_URL_MAP_ALL, /* 0x19 */
+    LXB_URL_MAP_ALL, /* 0x1a */
+    LXB_URL_MAP_ALL, /* 0x1b */
+    LXB_URL_MAP_ALL, /* 0x1c */
+    LXB_URL_MAP_ALL, /* 0x1d */
+    LXB_URL_MAP_ALL, /* 0x1e */
+    LXB_URL_MAP_ALL, /* 0x1f */
+    LXB_URL_MAP_USERINFO|LXB_URL_MAP_FRAGMENT|LXB_URL_MAP_QUERY|LXB_URL_MAP_SPECIAL_QUERY|LXB_URL_MAP_PATH|LXB_URL_MAP_COMPONENT, /* 0x20 ( ) */
+    LXB_URL_MAP_UNDEF, /* 0x21 (!) */
+    LXB_URL_MAP_USERINFO|LXB_URL_MAP_FRAGMENT|LXB_URL_MAP_QUERY|LXB_URL_MAP_SPECIAL_QUERY|LXB_URL_MAP_PATH|LXB_URL_MAP_COMPONENT, /* 0x22 (") */
+    LXB_URL_MAP_USERINFO|LXB_URL_MAP_QUERY|LXB_URL_MAP_SPECIAL_QUERY|LXB_URL_MAP_PATH|LXB_URL_MAP_COMPONENT, /* 0x23 (#) */
+    LXB_URL_MAP_COMPONENT, /* 0x24 ($) */
+    LXB_URL_MAP_UNDEF, /* 0x25 (%) */
+    LXB_URL_MAP_COMPONENT, /* 0x26 (&) */
+    LXB_URL_MAP_SPECIAL_QUERY, /* 0x27 (') */
+    LXB_URL_MAP_UNDEF, /* 0x28 (() */
+    LXB_URL_MAP_UNDEF, /* 0x29 ()) */
+    LXB_URL_MAP_UNDEF, /* 0x2a (*) */
+    LXB_URL_MAP_COMPONENT, /* 0x2b (+) */
+    LXB_URL_MAP_COMPONENT, /* 0x2c (,) */
+    LXB_URL_MAP_UNDEF, /* 0x2d (-) */
+    LXB_URL_MAP_UNDEF, /* 0x2e (.) */
+    LXB_URL_MAP_USERINFO|LXB_URL_MAP_COMPONENT, /* 0x2f (/) */
+    LXB_URL_MAP_UNDEF, /* 0x30 (0) */
+    LXB_URL_MAP_UNDEF, /* 0x31 (1) */
+    LXB_URL_MAP_UNDEF, /* 0x32 (2) */
+    LXB_URL_MAP_UNDEF, /* 0x33 (3) */
+    LXB_URL_MAP_UNDEF, /* 0x34 (4) */
+    LXB_URL_MAP_UNDEF, /* 0x35 (5) */
+    LXB_URL_MAP_UNDEF, /* 0x36 (6) */
+    LXB_URL_MAP_UNDEF, /* 0x37 (7) */
+    LXB_URL_MAP_UNDEF, /* 0x38 (8) */
+    LXB_URL_MAP_UNDEF, /* 0x39 (9) */
+    LXB_URL_MAP_USERINFO|LXB_URL_MAP_COMPONENT, /* 0x3a (:) */
+    LXB_URL_MAP_USERINFO|LXB_URL_MAP_COMPONENT, /* 0x3b (;) */
+    LXB_URL_MAP_USERINFO|LXB_URL_MAP_FRAGMENT|LXB_URL_MAP_QUERY|LXB_URL_MAP_SPECIAL_QUERY|LXB_URL_MAP_PATH|LXB_URL_MAP_COMPONENT, /* 0x3c (<) */
+    LXB_URL_MAP_USERINFO|LXB_URL_MAP_COMPONENT, /* 0x3d (=) */
+    LXB_URL_MAP_USERINFO|LXB_URL_MAP_FRAGMENT|LXB_URL_MAP_QUERY|LXB_URL_MAP_SPECIAL_QUERY|LXB_URL_MAP_PATH|LXB_URL_MAP_COMPONENT, /* 0x3e (>) */
+    LXB_URL_MAP_PATH|LXB_URL_MAP_USERINFO|LXB_URL_MAP_COMPONENT, /* 0x3f (?) */
+    LXB_URL_MAP_USERINFO|LXB_URL_MAP_COMPONENT, /* 0x40 (@) */
+    LXB_URL_MAP_UNDEF, /* 0x41 (A) */
+    LXB_URL_MAP_UNDEF, /* 0x42 (B) */
+    LXB_URL_MAP_UNDEF, /* 0x43 (C) */
+    LXB_URL_MAP_UNDEF, /* 0x44 (D) */
+    LXB_URL_MAP_UNDEF, /* 0x45 (E) */
+    LXB_URL_MAP_UNDEF, /* 0x46 (F) */
+    LXB_URL_MAP_UNDEF, /* 0x47 (G) */
+    LXB_URL_MAP_UNDEF, /* 0x48 (H) */
+    LXB_URL_MAP_UNDEF, /* 0x49 (I) */
+    LXB_URL_MAP_UNDEF, /* 0x4a (J) */
+    LXB_URL_MAP_UNDEF, /* 0x4b (K) */
+    LXB_URL_MAP_UNDEF, /* 0x4c (L) */
+    LXB_URL_MAP_UNDEF, /* 0x4d (M) */
+    LXB_URL_MAP_UNDEF, /* 0x4e (N) */
+    LXB_URL_MAP_UNDEF, /* 0x4f (O) */
+    LXB_URL_MAP_UNDEF, /* 0x50 (P) */
+    LXB_URL_MAP_UNDEF, /* 0x51 (Q) */
+    LXB_URL_MAP_UNDEF, /* 0x52 (R) */
+    LXB_URL_MAP_UNDEF, /* 0x53 (S) */
+    LXB_URL_MAP_UNDEF, /* 0x54 (T) */
+    LXB_URL_MAP_UNDEF, /* 0x55 (U) */
+    LXB_URL_MAP_UNDEF, /* 0x56 (V) */
+    LXB_URL_MAP_UNDEF, /* 0x57 (W) */
+    LXB_URL_MAP_UNDEF, /* 0x58 (X) */
+    LXB_URL_MAP_UNDEF, /* 0x59 (Y) */
+    LXB_URL_MAP_UNDEF, /* 0x5a (Z) */
+    LXB_URL_MAP_USERINFO|LXB_URL_MAP_COMPONENT, /* 0x5b ([) */
+    LXB_URL_MAP_USERINFO|LXB_URL_MAP_COMPONENT, /* 0x5c (\) */
+    LXB_URL_MAP_USERINFO|LXB_URL_MAP_COMPONENT, /* 0x5d (]) */
+    LXB_URL_MAP_USERINFO|LXB_URL_MAP_COMPONENT, /* 0x5e (^) */
+    LXB_URL_MAP_UNDEF, /* 0x5f (_) */
+    LXB_URL_MAP_PATH|LXB_URL_MAP_FRAGMENT|LXB_URL_MAP_USERINFO|LXB_URL_MAP_COMPONENT, /* 0x60 (`) */
+    LXB_URL_MAP_UNDEF, /* 0x61 (a) */
+    LXB_URL_MAP_UNDEF, /* 0x62 (b) */
+    LXB_URL_MAP_UNDEF, /* 0x63 (c) */
+    LXB_URL_MAP_UNDEF, /* 0x64 (d) */
+    LXB_URL_MAP_UNDEF, /* 0x65 (e) */
+    LXB_URL_MAP_UNDEF, /* 0x66 (f) */
+    LXB_URL_MAP_UNDEF, /* 0x67 (g) */
+    LXB_URL_MAP_UNDEF, /* 0x68 (h) */
+    LXB_URL_MAP_UNDEF, /* 0x69 (i) */
+    LXB_URL_MAP_UNDEF, /* 0x6a (j) */
+    LXB_URL_MAP_UNDEF, /* 0x6b (k) */
+    LXB_URL_MAP_UNDEF, /* 0x6c (l) */
+    LXB_URL_MAP_UNDEF, /* 0x6d (m) */
+    LXB_URL_MAP_UNDEF, /* 0x6e (n) */
+    LXB_URL_MAP_UNDEF, /* 0x6f (o) */
+    LXB_URL_MAP_UNDEF, /* 0x70 (p) */
+    LXB_URL_MAP_UNDEF, /* 0x71 (q) */
+    LXB_URL_MAP_UNDEF, /* 0x72 (r) */
+    LXB_URL_MAP_UNDEF, /* 0x73 (s) */
+    LXB_URL_MAP_UNDEF, /* 0x74 (t) */
+    LXB_URL_MAP_UNDEF, /* 0x75 (u) */
+    LXB_URL_MAP_UNDEF, /* 0x76 (v) */
+    LXB_URL_MAP_UNDEF, /* 0x77 (w) */
+    LXB_URL_MAP_UNDEF, /* 0x78 (x) */
+    LXB_URL_MAP_UNDEF, /* 0x79 (y) */
+    LXB_URL_MAP_UNDEF, /* 0x7a (z) */
+    LXB_URL_MAP_PATH|LXB_URL_MAP_USERINFO|LXB_URL_MAP_COMPONENT, /* 0x7b ({) */
+    LXB_URL_MAP_USERINFO|LXB_URL_MAP_COMPONENT, /* 0x7c (|) */
+    LXB_URL_MAP_PATH|LXB_URL_MAP_USERINFO|LXB_URL_MAP_COMPONENT, /* 0x7d (}) */
+    LXB_URL_MAP_UNDEF, /* 0x7e (~) */
+    LXB_URL_MAP_ALL, /* 0x7f */
+    LXB_URL_MAP_ALL, /* 0x80 */
+    LXB_URL_MAP_ALL, /* 0x81 */
+    LXB_URL_MAP_ALL, /* 0x82 */
+    LXB_URL_MAP_ALL, /* 0x83 */
+    LXB_URL_MAP_ALL, /* 0x84 */
+    LXB_URL_MAP_ALL, /* 0x85 */
+    LXB_URL_MAP_ALL, /* 0x86 */
+    LXB_URL_MAP_ALL, /* 0x87 */
+    LXB_URL_MAP_ALL, /* 0x88 */
+    LXB_URL_MAP_ALL, /* 0x89 */
+    LXB_URL_MAP_ALL, /* 0x8a */
+    LXB_URL_MAP_ALL, /* 0x8b */
+    LXB_URL_MAP_ALL, /* 0x8c */
+    LXB_URL_MAP_ALL, /* 0x8d */
+    LXB_URL_MAP_ALL, /* 0x8e */
+    LXB_URL_MAP_ALL, /* 0x8f */
+    LXB_URL_MAP_ALL, /* 0x90 */
+    LXB_URL_MAP_ALL, /* 0x91 */
+    LXB_URL_MAP_ALL, /* 0x92 */
+    LXB_URL_MAP_ALL, /* 0x93 */
+    LXB_URL_MAP_ALL, /* 0x94 */
+    LXB_URL_MAP_ALL, /* 0x95 */
+    LXB_URL_MAP_ALL, /* 0x96 */
+    LXB_URL_MAP_ALL, /* 0x97 */
+    LXB_URL_MAP_ALL, /* 0x98 */
+    LXB_URL_MAP_ALL, /* 0x99 */
+    LXB_URL_MAP_ALL, /* 0x9a */
+    LXB_URL_MAP_ALL, /* 0x9b */
+    LXB_URL_MAP_ALL, /* 0x9c */
+    LXB_URL_MAP_ALL, /* 0x9d */
+    LXB_URL_MAP_ALL, /* 0x9e */
+    LXB_URL_MAP_ALL, /* 0x9f */
+    LXB_URL_MAP_ALL, /* 0xa0 */
+    LXB_URL_MAP_ALL, /* 0xa1 */
+    LXB_URL_MAP_ALL, /* 0xa2 */
+    LXB_URL_MAP_ALL, /* 0xa3 */
+    LXB_URL_MAP_ALL, /* 0xa4 */
+    LXB_URL_MAP_ALL, /* 0xa5 */
+    LXB_URL_MAP_ALL, /* 0xa6 */
+    LXB_URL_MAP_ALL, /* 0xa7 */
+    LXB_URL_MAP_ALL, /* 0xa8 */
+    LXB_URL_MAP_ALL, /* 0xa9 */
+    LXB_URL_MAP_ALL, /* 0xaa */
+    LXB_URL_MAP_ALL, /* 0xab */
+    LXB_URL_MAP_ALL, /* 0xac */
+    LXB_URL_MAP_ALL, /* 0xad */
+    LXB_URL_MAP_ALL, /* 0xae */
+    LXB_URL_MAP_ALL, /* 0xaf */
+    LXB_URL_MAP_ALL, /* 0xb0 */
+    LXB_URL_MAP_ALL, /* 0xb1 */
+    LXB_URL_MAP_ALL, /* 0xb2 */
+    LXB_URL_MAP_ALL, /* 0xb3 */
+    LXB_URL_MAP_ALL, /* 0xb4 */
+    LXB_URL_MAP_ALL, /* 0xb5 */
+    LXB_URL_MAP_ALL, /* 0xb6 */
+    LXB_URL_MAP_ALL, /* 0xb7 */
+    LXB_URL_MAP_ALL, /* 0xb8 */
+    LXB_URL_MAP_ALL, /* 0xb9 */
+    LXB_URL_MAP_ALL, /* 0xba */
+    LXB_URL_MAP_ALL, /* 0xbb */
+    LXB_URL_MAP_ALL, /* 0xbc */
+    LXB_URL_MAP_ALL, /* 0xbd */
+    LXB_URL_MAP_ALL, /* 0xbe */
+    LXB_URL_MAP_ALL, /* 0xbf */
+    LXB_URL_MAP_ALL, /* 0xc0 */
+    LXB_URL_MAP_ALL, /* 0xc1 */
+    LXB_URL_MAP_ALL, /* 0xc2 */
+    LXB_URL_MAP_ALL, /* 0xc3 */
+    LXB_URL_MAP_ALL, /* 0xc4 */
+    LXB_URL_MAP_ALL, /* 0xc5 */
+    LXB_URL_MAP_ALL, /* 0xc6 */
+    LXB_URL_MAP_ALL, /* 0xc7 */
+    LXB_URL_MAP_ALL, /* 0xc8 */
+    LXB_URL_MAP_ALL, /* 0xc9 */
+    LXB_URL_MAP_ALL, /* 0xca */
+    LXB_URL_MAP_ALL, /* 0xcb */
+    LXB_URL_MAP_ALL, /* 0xcc */
+    LXB_URL_MAP_ALL, /* 0xcd */
+    LXB_URL_MAP_ALL, /* 0xce */
+    LXB_URL_MAP_ALL, /* 0xcf */
+    LXB_URL_MAP_ALL, /* 0xd0 */
+    LXB_URL_MAP_ALL, /* 0xd1 */
+    LXB_URL_MAP_ALL, /* 0xd2 */
+    LXB_URL_MAP_ALL, /* 0xd3 */
+    LXB_URL_MAP_ALL, /* 0xd4 */
+    LXB_URL_MAP_ALL, /* 0xd5 */
+    LXB_URL_MAP_ALL, /* 0xd6 */
+    LXB_URL_MAP_ALL, /* 0xd7 */
+    LXB_URL_MAP_ALL, /* 0xd8 */
+    LXB_URL_MAP_ALL, /* 0xd9 */
+    LXB_URL_MAP_ALL, /* 0xda */
+    LXB_URL_MAP_ALL, /* 0xdb */
+    LXB_URL_MAP_ALL, /* 0xdc */
+    LXB_URL_MAP_ALL, /* 0xdd */
+    LXB_URL_MAP_ALL, /* 0xde */
+    LXB_URL_MAP_ALL, /* 0xdf */
+    LXB_URL_MAP_ALL, /* 0xe0 */
+    LXB_URL_MAP_ALL, /* 0xe1 */
+    LXB_URL_MAP_ALL, /* 0xe2 */
+    LXB_URL_MAP_ALL, /* 0xe3 */
+    LXB_URL_MAP_ALL, /* 0xe4 */
+    LXB_URL_MAP_ALL, /* 0xe5 */
+    LXB_URL_MAP_ALL, /* 0xe6 */
+    LXB_URL_MAP_ALL, /* 0xe7 */
+    LXB_URL_MAP_ALL, /* 0xe8 */
+    LXB_URL_MAP_ALL, /* 0xe9 */
+    LXB_URL_MAP_ALL, /* 0xea */
+    LXB_URL_MAP_ALL, /* 0xeb */
+    LXB_URL_MAP_ALL, /* 0xec */
+    LXB_URL_MAP_ALL, /* 0xed */
+    LXB_URL_MAP_ALL, /* 0xee */
+    LXB_URL_MAP_ALL, /* 0xef */
+    LXB_URL_MAP_ALL, /* 0xf0 */
+    LXB_URL_MAP_ALL, /* 0xf1 */
+    LXB_URL_MAP_ALL, /* 0xf2 */
+    LXB_URL_MAP_ALL, /* 0xf3 */
+    LXB_URL_MAP_ALL, /* 0xf4 */
+    LXB_URL_MAP_ALL, /* 0xf5 */
+    LXB_URL_MAP_ALL, /* 0xf6 */
+    LXB_URL_MAP_ALL, /* 0xf7 */
+    LXB_URL_MAP_ALL, /* 0xf8 */
+    LXB_URL_MAP_ALL, /* 0xf9 */
+    LXB_URL_MAP_ALL, /* 0xfa */
+    LXB_URL_MAP_ALL, /* 0xfb */
+    LXB_URL_MAP_ALL, /* 0xfc */
+    LXB_URL_MAP_ALL, /* 0xfd */
+    LXB_URL_MAP_ALL, /* 0xfe */
+    LXB_URL_MAP_ALL, /* 0xff */
+};
+
+/*
+ * U+0000 NULL, U+0009 TAB, U+000A LF, U+000D CR, U+0020 SPACE, U+0023 (#),
+ * U+002F (/), U+003A (:), U+003C (<), U+003E (>), U+003F (?), U+0040 (@),
+ * U+005B ([), U+005C (\), U+005D (]), U+005E (^), or U+007C (|).
+ * U+0000 NULL to U+001F, U+0025 (%), or U+007F DELETE.
+ */
+static const lxb_char_t lxb_url_map_forbidden_domain_cp[128] =
+{
+    0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+    0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13,
+    0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d,
+    0x1e, 0x1f, 0x20, 0xff, 0xff, 0x23, 0xff, 0x25, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x2f, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3a, 0xff,
+    0x3c, 0xff, 0x3e, 0x3f, 0x40, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0x5b, 0x5c, 0x5d, 0x5e, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0x7c, 0xff, 0xff, 0x7f
+};
+
+/*
+ * U+0000 NULL, U+0009 TAB, U+000A LF, U+000D CR, U+0020 SPACE, U+0023 (#),
+ * U+002F (/), U+003A (:), U+003C (<), U+003E (>), U+003F (?), U+0040 (@),
+ * U+005B ([), U+005C (\), U+005D (]), U+005E (^), or U+007C (|).
+ */
+static const lxb_char_t lxb_url_map_forbidden_host_cp[128] =
+{
+    0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x09,
+    0x0a, 0xff, 0xff, 0x0d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0x20, 0xff, 0xff, 0x23, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x2f, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3a, 0xff,
+    0x3c, 0xff, 0x3e, 0x3f, 0x40, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0x5b, 0x5c, 0x5d, 0x5e, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0x7c, 0xff, 0xff, 0xff
+};
+
+static const lxb_char_t lxb_url_map_num_8[] = {
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
+    0x06, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff
+};
+
+static const lxb_char_t lxb_url_codepoint_alphanumeric[0xA0] = {
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0x21, 0xff, 0xff, 0x24, 0xff, 0x26, 0x27,
+    0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+    0x38, 0x39, 0x3a, 0x3b, 0xff, 0x3d, 0xff, 0x3f,
+    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+    0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57,
+    0x58, 0x59, 0x5a, 0xff, 0xff, 0xff, 0xff, 0x5f,
+    0xff, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+    0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
+    0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
+    0x78, 0x79, 0x7a, 0xff, 0xff, 0xff, 0x7e, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
+
+static const lxb_char_t lxb_url_path_map[256] =
+{
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+    0x01, 0x00, 0x01, 0x01, 0x00, 0x04, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x02, 0x01, 0x02, 0x02, 0x00,
+    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x01, 0x02, 0x01, 0x00, 0x02,
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02
+};
+
+static const lxb_url_scheme_data_t
+lxb_url_scheme_res[LXB_URL_SCHEMEL_TYPE__LAST_ENTRY] =
+{
+    {.name = lexbor_str("#undef"),   0,   LXB_URL_SCHEMEL_TYPE__UNDEF  },
+    {.name = lexbor_str("#unknown"), 0,   LXB_URL_SCHEMEL_TYPE__UNKNOWN},
+    {.name = lexbor_str("http"),     80,  LXB_URL_SCHEMEL_TYPE_HTTP    },
+    {.name = lexbor_str("https"),    443, LXB_URL_SCHEMEL_TYPE_HTTPS   },
+    {.name = lexbor_str("ws"),       80,  LXB_URL_SCHEMEL_TYPE_WS      },
+    {.name = lexbor_str("wss"),      443, LXB_URL_SCHEMEL_TYPE_WSS     },
+    {.name = lexbor_str("ftp"),      21,  LXB_URL_SCHEMEL_TYPE_FTP     },
+    {.name = lexbor_str("file"),     0,   LXB_URL_SCHEMEL_TYPE_FILE    }
+};
+
+static const size_t
+lxb_url_scheme_res_length = sizeof(lxb_url_scheme_res) / sizeof(lxb_url_scheme_data_t);
+
+
+#define lxb_url_parse_return(data, buf, status)                               \
+    do {                                                                      \
+        if ((buf) != (data)) {                                                \
+            lexbor_free((lxb_char_t *) (buf));                                \
+        }                                                                     \
+        return (status);                                                      \
+    }                                                                         \
+    while (false)
+
+#define LXB_URL_SBUF_REALLOC(sbuf, sbuf_begin, sbuf_end, sbuffer, last)       \
+    do {                                                                      \
+        size_t new_len, offset, lst;                                          \
+        lxb_char_t *tmp;                                                      \
+                                                                              \
+        lst = (last) - (sbuf_begin);                                          \
+        offset = (sbuf) - (sbuf_begin);                                       \
+        new_len = offset << 1;                                                \
+                                                                              \
+        if ((sbuf_begin) == (sbuffer)) {                                      \
+            tmp = lexbor_malloc(new_len);                                     \
+            if (tmp == NULL) {                                                \
+                return NULL;                                                  \
+            }                                                                 \
+        }                                                                     \
+        else {                                                                \
+            tmp = lexbor_realloc((sbuf_begin), new_len);                      \
+            if (tmp == NULL) {                                                \
+                lexbor_free(sbuf_begin);                                      \
+                return NULL;                                                  \
+            }                                                                 \
+        }                                                                     \
+                                                                              \
+        (sbuf) = tmp + offset;                                                \
+        (last) = sbuf + lst;                                                  \
+        (sbuf_begin) = tmp;                                                   \
+        (sbuf_end) = tmp + new_len;                                           \
+    }                                                                         \
+    while (false)
+
+#define lxb_url_is_windows_letter(data)                                       \
+    (((data) >= 'a' && (data) <= 'z') || ((data) >= 'A' && (data) <= 'Z'))
+
+
+static lxb_status_t
+lxb_url_leading_trailing(lxb_url_parser_t *parser,
+                         const lxb_char_t **data, size_t *length);
+
+static const lxb_char_t *
+lxb_url_remove_tab_newline(lxb_url_parser_t *parser,
+                           const lxb_char_t *data, size_t *length);
+
+static const lxb_url_scheme_data_t *
+lxb_url_scheme_find(const lxb_char_t *data, size_t length);
+
+static lxb_status_t
+lxb_url_parse_basic_h(lxb_url_parser_t *parser, lxb_url_t *url,
+                      const lxb_url_t *base_url,
+                      const lxb_char_t *data, size_t length,
+                      lxb_url_state_t override_state);
+
+static const lxb_char_t *
+lxb_url_path_fast_path(lxb_url_parser_t *parser, lxb_url_t *url,
+                       const lxb_char_t *data, const lxb_char_t *end, bool bqs);
+
+const lxb_char_t *
+lxb_url_path_slow_path(lxb_url_parser_t *parser, lxb_url_t *url,
+                       const lxb_char_t *data, const lxb_char_t *end, bool bqs);
+
+static lxb_status_t
+lxb_url_path_try_dot(lxb_url_t *url, const lxb_char_t **begin,
+                     const lxb_char_t **last, const lxb_char_t **start,
+                     const lxb_char_t *end, bool bqs);
+
+static const lxb_char_t *
+lxb_url_path_dot_count(lxb_url_t *url, const lxb_char_t *p,
+                       const lxb_char_t *end, const lxb_char_t *sbuf_begin,
+                       lxb_char_t **sbuf, lxb_char_t **last, size_t *path_count,
+                       bool bqs);
+
+static void
+lxb_url_path_fix_windows_drive(lxb_url_t *url, lxb_char_t *sbuf,
+                               const lxb_char_t *last, size_t count);
+
+static lxb_status_t
+lxb_url_percent_encode_after_utf_8(const lxb_char_t *data,
+                                   const lxb_char_t *end, lexbor_str_t *str,
+                                   lexbor_mraw_t *mraw,
+                                   lxb_url_map_type_t enmap,
+                                   bool space_as_plus);
+
+static lxb_status_t
+lxb_url_host_parse(lxb_url_parser_t *parser, const lxb_char_t *data,
+                   const lxb_char_t *end, lxb_url_host_t *host,
+                   lexbor_mraw_t *mraw, lxb_url_host_opt_t opt);
+
+static lxb_status_t
+lxb_url_ipv4_parse(lxb_url_parser_t *parser, const lxb_char_t *data,
+                   const lxb_char_t *end, uint32_t *ipv6);
+
+static lxb_status_t
+lxb_url_ipv4_number_parse(const lxb_char_t *data,
+                          const lxb_char_t *end, uint64_t *num);
+
+static bool
+lxb_url_is_ipv4(lxb_url_parser_t *parser, const lxb_char_t *data,
+                const lxb_char_t *end);
+
+static lxb_status_t
+lxb_url_ipv6_parse(lxb_url_parser_t *parser, const lxb_char_t *data,
+                   const lxb_char_t *end, uint16_t *ipv6);
+
+static lxb_status_t
+lxb_url_ipv4_in_ipv6_parse(lxb_url_parser_t *parser, const lxb_char_t **data,
+                           const lxb_char_t *end, uint16_t **pieces);
+
+static lxb_status_t
+lxb_url_opaque_host_parse(lxb_url_parser_t *parser, const lxb_char_t *data,
+                          const lxb_char_t *end, lxb_url_host_t *host,
+                          lexbor_mraw_t *mraw);
+
+static lxb_status_t
+lxb_url_percent_decode(const lxb_char_t *data, const lxb_char_t *end,
+                       lexbor_str_t *str, lexbor_mraw_t *mraw,
+                       lxb_url_host_opt_t *opt);
+
+static const lxb_char_t *
+lxb_url_path_part_by_index(const lxb_url_t *url, size_t index,
+                           size_t *out_length);
+
+static lxb_status_t
+lxb_url_host_set_h(lxb_url_t *url, lxb_url_parser_t *parser,
+                   const lxb_char_t *host, size_t length,
+                   lxb_url_state_t override_state);
+
+static lxb_status_t
+lxb_url_callback_length(const lxb_char_t *data, size_t len, void *ctx);
+
+static lxb_codepoint_t
+lxb_url_decode_valid_utf_8_single(const lxb_char_t **data,
+                                  const lxb_char_t *end);
+static lxb_codepoint_t
+lxb_url_decode_valid_utf_8_single_reverse(const lxb_char_t **end,
+                                          const lxb_char_t *begin);
+
+
+lxb_url_parser_t *
+lxb_url_parser_create(void)
+{
+    return lexbor_calloc(1, sizeof(lxb_url_parser_t));
+}
+
+lxb_status_t
+lxb_url_parser_init(lxb_url_parser_t *parser, lexbor_mraw_t *mraw)
+{
+    bool itmy;
+    lxb_status_t status;
+
+    if (parser == NULL) {
+        return LXB_STATUS_ERROR_OBJECT_IS_NULL;
+    }
+
+    itmy = false;
+
+    if (mraw == NULL) {
+        mraw = lexbor_mraw_create();
+        status = lexbor_mraw_init(mraw, LXB_URL_BUFFER_SIZE);
+        if (status != LXB_STATUS_OK) {
+            goto failed;
+        }
+
+        itmy = true;
+    }
+
+    parser->mraw = mraw;
+    parser->log = NULL;
+
+    return LXB_STATUS_OK;
+
+failed:
+
+    if (itmy) {
+        (void) lexbor_mraw_destroy(mraw, true);
+    }
+
+    memset(parser, 0x00, sizeof(lxb_url_parser_t));
+
+    return status;
+}
+
+void
+lxb_url_parser_clean(lxb_url_parser_t *parser)
+{
+    parser->url = NULL;
+
+    if (parser->log != NULL) {
+        lexbor_plog_clean(parser->log);
+    }
+}
+
+lxb_url_parser_t *
+lxb_url_parser_destroy(lxb_url_parser_t *parser, bool destroy_self)
+{
+    if (parser == NULL) {
+        return NULL;
+    }
+
+    parser->log = lexbor_plog_destroy(parser->log, true);
+
+    if (destroy_self) {
+        return lexbor_free(parser);
+    }
+
+    return parser;
+}
+
+void
+lxb_url_parser_memory_destroy(lxb_url_parser_t *parser)
+{
+    parser->mraw = lexbor_mraw_destroy(parser->mraw, true);
+}
+
+static lxb_status_t
+lxb_url_log_append(lxb_url_parser_t *parser, const lxb_char_t *pos,
+                   lxb_url_error_type_t type)
+{
+    void *entry;
+    lxb_status_t status;
+
+    if (parser->log == NULL) {
+        parser->log = lexbor_plog_create();
+        status = lexbor_plog_init(parser->log, 5, sizeof(lexbor_plog_entry_t));
+        if (status != LXB_STATUS_OK) {
+            return status;
+        }
+    }
+
+    entry = lexbor_plog_push(parser->log, pos, NULL, type);
+    if (entry == NULL) {
+        return LXB_STATUS_ERROR_MEMORY_ALLOCATION;
+    }
+
+    return LXB_STATUS_OK;
+}
+
+static lxb_status_t
+lxb_url_str_init(lexbor_str_t *str, lexbor_mraw_t *mraw, size_t length)
+{
+    size_t size;
+    const lxb_char_t *p;
+
+    if (str->data == NULL) {
+        p = lexbor_str_init(str, mraw, length);
+        if (p == NULL) {
+            return LXB_STATUS_ERROR_MEMORY_ALLOCATION;
+        }
+    }
+    else {
+        size = str->length + length;
+
+        if (size > lexbor_str_size(str)) {
+            p = lexbor_str_realloc(str, mraw, size);
+            if (p == NULL) {
+                return LXB_STATUS_ERROR_MEMORY_ALLOCATION;
+            }
+        }
+    }
+
+    return LXB_STATUS_OK;
+}
+
+static lxb_status_t
+lxb_url_str_copy(const lexbor_str_t *src, lexbor_str_t *dst,
+                 lexbor_mraw_t *dst_mraw)
+{
+    if (src->data == NULL) {
+        *dst = *src;
+        return LXB_STATUS_OK;
+    }
+
+    if (dst->data != NULL) {
+        if (dst->length >= src->length) {
+            /* +1 == '\0' */
+            memcpy(dst->data, src->data, src->length + 1);
+
+            return LXB_STATUS_OK;
+        }
+
+        (void) lexbor_str_destroy(dst, dst_mraw, false);
+    }
+
+    (void) lexbor_str_init_append(dst, dst_mraw,
+                                  src->data, src->length);
+
+    return (dst->data != NULL) ? LXB_STATUS_OK
+    : LXB_STATUS_ERROR_MEMORY_ALLOCATION;
+}
+
+lxb_inline bool
+lxb_url_is_noncharacter(lxb_codepoint_t cp)
+{
+    if (cp >= 0xFDD0 && cp <= 0xFDEF) {
+        return true;
+    }
+
+    switch (cp) {
+        case 0xFFFE:
+        case 0xFFFF:
+        case 0x1FFFE:
+        case 0x1FFFF:
+        case 0x2FFFE:
+        case 0x2FFFF:
+        case 0x3FFFE:
+        case 0x3FFFF:
+        case 0x4FFFE:
+        case 0x4FFFF:
+        case 0x5FFFE:
+        case 0x5FFFF:
+        case 0x6FFFE:
+        case 0x6FFFF:
+        case 0x7FFFE:
+        case 0x7FFFF:
+        case 0x8FFFE:
+        case 0x8FFFF:
+        case 0x9FFFE:
+        case 0x9FFFF:
+        case 0xAFFFE:
+        case 0xAFFFF:
+        case 0xBFFFE:
+        case 0xBFFFF:
+        case 0xCFFFE:
+        case 0xCFFFF:
+        case 0xDFFFE:
+        case 0xDFFFF:
+        case 0xEFFFE:
+        case 0xEFFFF:
+        case 0xFFFFE:
+        case 0xFFFFF:
+        case 0x10FFFE:
+        case 0x10FFFF:
+            return true;
+
+        default:
+            break;
+    }
+
+    return false;
+}
+
+lxb_inline bool
+lxb_url_is_url_codepoint(lxb_codepoint_t cp)
+{
+    if (cp >= 0x00A0 && cp <= 0x1FFFFF) {
+        /* Leading and trailing surrogate. */
+        if ((cp >= 0xD800 && cp <= 0xDFFF)) {
+            return false;
+        }
+
+        return !(cp > 0x10FFFF || lxb_url_is_noncharacter(cp));
+    }
+
+    return lxb_url_codepoint_alphanumeric[(lxb_char_t) cp] != 0xFF;
+}
+
+lxb_inline bool
+lxb_url_is_special(const lxb_url_t *url)
+{
+    return url->scheme.type != LXB_URL_SCHEMEL_TYPE__UNKNOWN;
+}
+
+lxb_inline const lxb_url_scheme_data_t *
+lxb_url_scheme_by_type(lxb_url_scheme_type_t type)
+{
+    return &lxb_url_scheme_res[type];
+}
+
+lxb_inline bool
+lxb_url_scheme_is_special(const lxb_url_scheme_data_t *scheme)
+{
+    return scheme->type != LXB_URL_SCHEMEL_TYPE__UNKNOWN;
+}
+
+lxb_inline bool
+lxb_url_scheme_is_equal(const lxb_url_scheme_t *first,
+                        const lxb_url_scheme_t *second)
+{
+    if (first->type != second->type) {
+        return false;
+    }
+
+    if (first->type == LXB_URL_SCHEMEL_TYPE__UNKNOWN) {
+        if (first->name.length != second->name.length) {
+            return false;
+        }
+
+        return memcmp(first->name.data, second->name.data,
+                      first->name.length) == 0;
+    }
+
+    return true;
+}
+
+lxb_inline bool
+lxb_url_scheme_equal_port(lxb_url_scheme_type_t type, uint16_t port)
+{
+    return lxb_url_scheme_res[type].port == port;
+}
+
+static lxb_status_t
+lxb_url_scheme_copy(const lxb_url_scheme_t *src, lxb_url_scheme_t *dst,
+                    lexbor_mraw_t *dst_mraw)
+{
+    dst->type = src->type;
+
+    return lxb_url_str_copy(&src->name, &dst->name, dst_mraw);
+}
+
+static lxb_status_t
+lxb_url_scheme_copy_special(const lxb_url_scheme_data_t *src,
+                            lxb_url_scheme_t *dst, lexbor_mraw_t *dst_mraw)
+{
+    dst->type = src->type;
+
+    return lxb_url_str_copy(&src->name, &dst->name, dst_mraw);
+}
+
+static void
+lxb_url_path_set_null(lxb_url_t *url)
+{
+    if (url->path.str.data == NULL) {
+        return;
+    }
+
+    (void) lexbor_str_destroy(&url->path.str, url->mraw, false);
+
+    url->path.str.length = 0;
+    url->path.length = 0;
+    url->path.opaque = false;
+}
+
+static lxb_status_t
+lxb_url_path_copy(const lxb_url_t *src, lxb_url_t *dst)
+{
+    lexbor_str_t *to;
+    const lexbor_str_t *str;
+
+    if (dst->path.str.data != NULL) {
+        lxb_url_path_set_null(dst);
+    }
+
+    dst->path.opaque = src->path.opaque;
+
+    if (src->path.str.data == NULL) {
+        return LXB_STATUS_OK;
+    }
+
+    dst->path.length = src->path.length;
+
+    str = &src->path.str;
+    to = &dst->path.str;
+
+    to->data = lexbor_mraw_alloc(dst->mraw, (str->length + 1));
+    if (to->data == NULL) {
+        return LXB_STATUS_ERROR_MEMORY_ALLOCATION;
+    }
+
+    /* +1 == '\0' */
+    memcpy(to->data, str->data, str->length + 1);
+
+    to->length = str->length;
+
+    return LXB_STATUS_OK;
+}
+
+static void
+lxb_url_path_shorten(lxb_url_t *url)
+{
+    lexbor_str_t *str;
+    lxb_char_t *p, *begin;
+
+    str = &url->path.str;
+
+    if (url->scheme.type == LXB_URL_SCHEMEL_TYPE_FILE
+        && url->path.length == 1)
+    {
+        if (str->length >= 3
+            && lxb_url_is_windows_letter(str->data[1])
+            && str->data[2] == ':')
+        {
+            return;
+        }
+    }
+
+    if (url->path.str.data != NULL) {
+        url->path.length -= 1;
+
+        begin = str->data;
+        p = begin + str->length;
+
+        while (p > begin) {
+            p -= 1;
+
+            if (*p == '/') {
+                *p = '\0';
+                break;
+            }
+        }
+
+        str->length = p - begin;
+    }
+}
+
+static lxb_status_t
+lxb_url_path_append_wo_slash(lxb_url_t *url,
+                             const lxb_char_t *data, size_t length)
+{
+    lxb_char_t *p;
+
+    if (url->path.str.data == NULL) {
+        p = lexbor_str_init(&url->path.str, url->mraw, length);
+        if (p == NULL) {
+            return LXB_STATUS_ERROR_MEMORY_ALLOCATION;
+        }
+    }
+
+    p = lexbor_str_append(&url->path.str, url->mraw, data, length);
+
+    return (p != NULL) ? LXB_STATUS_OK : LXB_STATUS_ERROR_MEMORY_ALLOCATION;
+}
+
+static lxb_status_t
+lxb_url_path_append(lxb_url_t *url, const lxb_char_t *data, size_t length)
+{
+    size_t len;
+    lxb_char_t *p;
+    lexbor_str_t *str;
+
+    str = &url->path.str;
+
+    if (str->data == NULL) {
+        p = lexbor_str_init(str, url->mraw, length + 1);
+        if (p == NULL) {
+            return LXB_STATUS_ERROR_MEMORY_ALLOCATION;
+        }
+    }
+
+    len = str->length;
+    str->length += 1;
+
+    p = lexbor_str_append(&url->path.str, url->mraw, data, length);
+
+    str->data[len] = '/';
+
+    return (p != NULL) ? LXB_STATUS_OK : LXB_STATUS_ERROR_MEMORY_ALLOCATION;
+}
+
+static lxb_status_t
+lxb_url_path_list_push(lxb_url_t *url, lexbor_str_t *data)
+{
+    return lxb_url_path_append_wo_slash(url, data->data, data->length);
+}
+
+lxb_inline lxb_status_t
+lxb_url_query_copy(const lexbor_str_t *src, lexbor_str_t *dst,
+                   lexbor_mraw_t *dst_mraw)
+{
+    return lxb_url_str_copy(src, dst, dst_mraw);
+}
+
+lxb_inline lxb_status_t
+lxb_url_username_copy(const lexbor_str_t *src, lexbor_str_t *dst,
+                      lexbor_mraw_t *dst_mraw)
+{
+    return lxb_url_str_copy(src, dst, dst_mraw);
+}
+
+lxb_inline lxb_status_t
+lxb_url_password_copy(const lexbor_str_t *src, lexbor_str_t *dst,
+                      lexbor_mraw_t *dst_mraw)
+{
+    return lxb_url_str_copy(src, dst, dst_mraw);
+}
+
+static lxb_status_t
+lxb_url_host_copy(const lxb_url_host_t *src, lxb_url_host_t *dst,
+                  lexbor_mraw_t *dst_mraw)
+{
+    if (dst->type != LXB_URL_HOST_TYPE__UNDEF) {
+        if (src->type == LXB_URL_HOST_TYPE__UNDEF) {
+            if (dst->type <= LXB_URL_HOST_TYPE_OPAQUE) {
+                (void) lexbor_str_destroy(&dst->u.domain, dst_mraw, false);
+            }
+
+            dst->type = LXB_URL_HOST_TYPE__UNDEF;
+
+            return LXB_STATUS_OK;
+        }
+
+        if (dst->type <= LXB_URL_HOST_TYPE_OPAQUE) {
+            if (src->type <= LXB_URL_HOST_TYPE_OPAQUE) {
+                dst->type = src->type;
+
+                return lxb_url_str_copy(&src->u.domain,
+                                        &dst->u.domain, dst_mraw);
+            }
+
+            (void) lexbor_str_destroy(&dst->u.domain, dst_mraw, false);
+        }
+    }
+
+    if (src->type <= LXB_URL_HOST_TYPE_OPAQUE) {
+        dst->type = src->type;
+
+        if (src->type == LXB_URL_HOST_TYPE__UNDEF) {
+            return LXB_STATUS_OK;
+        }
+
+        return lxb_url_str_copy(&src->u.domain,
+                                &dst->u.domain, dst_mraw);
+    }
+
+    if (src->type == LXB_URL_HOST_TYPE_IPV6) {
+        memcpy(dst->u.ipv6, src->u.ipv6, sizeof(src->u.ipv6));
+    }
+    else {
+        dst->u.ipv4 = src->u.ipv4;
+    }
+
+    return LXB_STATUS_OK;
+}
+
+lxb_inline void
+lxb_url_host_destroy(lxb_url_host_t *host, lexbor_mraw_t *mraw)
+{
+    if (host->type != LXB_URL_HOST_TYPE__UNDEF) {
+        if (host->type <= LXB_URL_HOST_TYPE_OPAQUE) {
+            (void) lexbor_str_destroy(&host->u.domain, mraw, false);
+        }
+    }
+}
+
+static void
+lxb_url_host_set_empty(lxb_url_host_t *host, lexbor_mraw_t *mraw)
+{
+    lxb_url_host_destroy(host, mraw);
+
+    host->type = LXB_URL_HOST_TYPE_EMPTY;
+}
+
+static bool
+lxb_url_host_eq(lxb_url_host_t *host, const lxb_char_t *data, size_t length)
+{
+    lexbor_str_t *str;
+
+    if (host->type != LXB_URL_HOST_TYPE__UNDEF) {
+        if (host->type <= LXB_URL_HOST_TYPE_OPAQUE) {
+            str = &host->u.domain;
+
+            return str->length == length
+                   && memcmp(data, str->data, length) == 0;
+        }
+    }
+
+    return false;
+}
+
+lxb_inline void
+lxb_url_port_set(lxb_url_t *url, uint16_t port)
+{
+    url->port = port;
+    url->has_port = true;
+}
+
+static void
+lxb_url_fragment_set_null(lxb_url_t *url)
+{
+    if (url->fragment.data != NULL) {
+        (void) lexbor_str_destroy(&url->fragment, url->mraw, false);
+    }
+}
+
+lxb_inline bool
+lxb_url_includes_credentials(const lxb_url_t *url)
+{
+    return url->username.length != 0 || url->password.length != 0;
+}
+
+static bool
+lxb_url_start_windows_drive_letter(const lxb_char_t *data,
+                                   const lxb_char_t *end)
+{
+    size_t length = end - data;
+
+    if (length < 2) {
+        return false;
+    }
+
+    if (!lxb_url_is_windows_letter(data[0])
+        || (data[1] != ':' && data[1] != '|'))
+    {
+        return false;
+    }
+
+    if (length > 2 && !(   data[2] == '/' || data[2] == '\\'
+                        || data[2] == '?' || data[2] == '#'))
+    {
+        return false;
+    }
+
+    return true;
+}
+
+static bool
+lxb_url_windows_drive_letter(const lxb_char_t *data, const lxb_char_t *end)
+{
+    size_t length = end - data;
+
+    if (length < 2) {
+        return false;
+    }
+
+    return lxb_url_is_windows_letter(data[0])
+           && (data[1] == ':' || data[1] == '|');
+}
+
+static bool
+lxb_url_normalized_windows_drive_letter(const lxb_char_t *data,
+                                        const lxb_char_t *end)
+{
+    size_t length = end - data;
+
+    if (length < 2) {
+        return false;
+    }
+
+    return lxb_url_is_windows_letter(data[0]) && data[1] == ':';
+}
+
+static bool
+lxb_url_cannot_have_user_pass_port(lxb_url_t *url)
+{
+    return url->host.type == LXB_URL_HOST_TYPE_EMPTY
+    || url->host.type == LXB_URL_HOST_TYPE__UNDEF
+    || url->scheme.type == LXB_URL_SCHEMEL_TYPE_FILE;
+}
+
+lxb_url_t *
+lxb_url_parse(lxb_url_parser_t *parser, const lxb_url_t *base_url,
+              const lxb_char_t *data, size_t length)
+{
+    (void) lxb_url_parse_basic(parser, NULL, base_url, data, length,
+                               LXB_URL_STATE__UNDEF);
+    return parser->url;
+}
+
+lxb_status_t
+lxb_url_parse_basic(lxb_url_parser_t *parser, lxb_url_t *url,
+                    const lxb_url_t *base_url,
+                    const lxb_char_t *data, size_t length,
+                    lxb_url_state_t override_state)
+{
+    lxb_status_t status;
+
+    status = lxb_url_parse_basic_h(parser, url, base_url, data,
+                                   length, override_state);
+    if (status != LXB_STATUS_OK) {
+        if (parser->url != url) {
+            parser->url = lxb_url_destroy(parser->url);
+        }
+
+        return status;
+    }
+
+    return LXB_STATUS_OK;
+}
+
+static lxb_status_t
+lxb_url_parse_basic_h(lxb_url_parser_t *parser, lxb_url_t *url,
+                      const lxb_url_t *base_url,
+                      const lxb_char_t *data, size_t length,
+                      lxb_url_state_t override_state)
+{
+    bool at_sign, inside_bracket;
+    size_t len;
+    uint32_t port;
+    lxb_status_t status;
+    lexbor_str_t tmp_str;
+    lxb_url_state_t state;
+    const lxb_char_t *p, *begin, *end, *tmp, *pswd, *buf, *orig_data;
+    lxb_char_t c;
+    lxb_codepoint_t cp;
+    lxb_url_map_type_t map_type;
+    const lxb_url_scheme_data_t *schm;
+    lxb_url_host_opt_t opt;
+
+    static const lexbor_str_t mp_str = lexbor_str("");
+    static const lexbor_str_t lh_str = lexbor_str("localhost");
+
+    if (url == NULL) {
+        url = lexbor_mraw_calloc(parser->mraw, sizeof(lxb_url_t));
+        if (url == NULL) {
+            return LXB_STATUS_ERROR_MEMORY_ALLOCATION;
+        }
+
+        url->mraw = parser->mraw;
+
+        status = lxb_url_leading_trailing(parser, &data, &length);
+        if (status != LXB_STATUS_OK) {
+            return status;
+        }
+    }
+
+    parser->url = url;
+    orig_data = data;
+
+    buf = lxb_url_remove_tab_newline(parser, data, &length);
+    if (buf != data) {
+        if (buf == NULL) {
+            return LXB_STATUS_ERROR_MEMORY_ALLOCATION;
+        }
+
+        data = buf;
+    }
+
+    state = LXB_URL_STATE_SCHEME_START_STATE;
+
+    if (override_state != LXB_URL_STATE__UNDEF) {
+        state = override_state;
+    }
+
+    inside_bracket = false;
+
+    p = data;
+    end = data + length;
+
+    /* And go. */
+
+    schm = lxb_url_scheme_by_type(LXB_URL_SCHEMEL_TYPE__UNDEF);
+
+again:
+
+    switch (state) {
+    case LXB_URL_STATE_SCHEME_START_STATE:
+        if (p >= end || lexbor_str_res_alpha_character[*p] == 0xff) {
+            if (override_state == LXB_URL_STATE__UNDEF) {
+                state = LXB_URL_STATE_NO_SCHEME_STATE;
+                goto again;
+            }
+
+            lxb_url_parse_return(orig_data, buf, LXB_STATUS_ERROR_UNEXPECTED_DATA);
+        }
+
+        /* Fall through. */
+
+    case LXB_URL_STATE_SCHEME_STATE:
+        do {
+            p++;
+        }
+        while (p < end && (lexbor_str_res_alphanumeric_character[*p] != 0xff
+                           || *p == '+' || *p == '-' || *p == '.'));
+
+        if (p >= end || *p != ':') {
+            if (override_state == LXB_URL_STATE__UNDEF) {
+                p = data;
+
+                state = LXB_URL_STATE_NO_SCHEME_STATE;
+                goto again;
+            }
+            else if (p < end || override_state != LXB_URL_STATE_SCHEME_START_STATE) {
+                lxb_url_parse_return(orig_data, buf, LXB_STATUS_ERROR_UNEXPECTED_DATA);
+            }
+        }
+
+        schm = lxb_url_scheme_find(data, p - data);
+
+        if (override_state != LXB_URL_STATE__UNDEF) {
+            if (lxb_url_is_special(url) != lxb_url_scheme_is_special(schm)) {
+                lxb_url_parse_return(orig_data, buf, LXB_STATUS_OK);
+            }
+
+            if (url->has_port || lxb_url_includes_credentials(url)) {
+                if (schm->type == LXB_URL_SCHEMEL_TYPE_FILE) {
+                    lxb_url_parse_return(orig_data, buf, LXB_STATUS_OK);
+                }
+            }
+
+            if (url->scheme.type == LXB_URL_SCHEMEL_TYPE_FILE
+                && url->host.type == LXB_URL_HOST_TYPE_EMPTY)
+            {
+                lxb_url_parse_return(orig_data, buf, LXB_STATUS_OK);
+            }
+        }
+
+        url->scheme.type = schm->type;
+        url->scheme.name.length = 0;
+
+        status = lxb_url_str_init(&url->scheme.name, url->mraw, p - data);
+        if (status != LXB_STATUS_OK) {
+            lxb_url_parse_return(orig_data, buf, LXB_STATUS_ERROR_MEMORY_ALLOCATION);
+        }
+
+        (void) lexbor_str_append_lowercase(&url->scheme.name, url->mraw,
+                                           data, p - data);
+
+        p += 1;
+
+        if (override_state != LXB_URL_STATE__UNDEF) {
+            if (url->has_port && url->port == schm->port) {
+                url->port = 0;
+                url->has_port = false;
+            }
+
+            lxb_url_parse_return(orig_data, buf, LXB_STATUS_OK);
+        }
+
+        if (schm->type == LXB_URL_SCHEMEL_TYPE_FILE) {
+            if (end - p < 2 || p[0] != '/' || p[1] != '/') {
+                status = lxb_url_log_append(parser, p,
+                  LXB_URL_ERROR_TYPE_SPECIAL_SCHEME_MISSING_FOLLOWING_SOLIDUS);
+                if (status != LXB_STATUS_OK) {
+                    lxb_url_parse_return(orig_data, buf, status);
+                }
+            }
+
+            state = LXB_URL_STATE_FILE_STATE;
+            goto again;
+        }
+
+        if (lxb_url_scheme_is_special(schm)) {
+            if (base_url != NULL
+                && lxb_url_scheme_is_equal(&url->scheme, &base_url->scheme))
+            {
+                state = LXB_URL_STATE_SPECIAL_RELATIVE_OR_AUTHORITY_STATE;
+            }
+            else {
+                state = LXB_URL_STATE_SPECIAL_AUTHORITY_SLASHES_STATE;
+            }
+
+            goto again;
+        }
+
+        if (p < end && *p == '/') {
+            p += 1;
+            state = LXB_URL_STATE_PATH_OR_AUTHORITY_STATE;
+            goto again;
+        }
+
+        lxb_url_path_set_null(url);
+
+        state = LXB_URL_STATE_OPAQUE_PATH_STATE;
+        goto again;
+
+    case LXB_URL_STATE_NO_SCHEME_STATE:
+        if (base_url == NULL) {
+            goto failed_non_relative_url;
+        }
+
+        if (base_url->path.opaque) {
+            if (p >= end || *p != '#') {
+                goto failed_non_relative_url;
+            }
+
+            p += 1;
+
+            status = lxb_url_scheme_copy(&base_url->scheme,
+                                         &url->scheme, url->mraw);
+            if (status != LXB_STATUS_OK) {
+                lxb_url_parse_return(orig_data, buf, status);
+            }
+
+            status = lxb_url_path_copy(base_url, url);
+            if (status != LXB_STATUS_OK) {
+                lxb_url_parse_return(orig_data, buf, status);
+            }
+
+            status = lxb_url_query_copy(&base_url->query, &url->query,
+                                        url->mraw);
+            if (status != LXB_STATUS_OK) {
+                lxb_url_parse_return(orig_data, buf, status);
+            }
+
+            lxb_url_fragment_set_null(url);
+
+            state = LXB_URL_STATE_FRAGMENT_STATE;
+            goto again;
+        }
+
+        if (base_url->scheme.type != LXB_URL_SCHEMEL_TYPE_FILE) {
+            state = LXB_URL_STATE_RELATIVE_STATE;
+            goto again;
+        }
+
+        state = LXB_URL_STATE_FILE_STATE;
+        goto again;
+
+    case LXB_URL_STATE_SPECIAL_RELATIVE_OR_AUTHORITY_STATE:
+        if (end - p > 1 && p[0] == '/' && p[1] == '/') {
+            p += 2;
+            state = LXB_URL_STATE_SPECIAL_AUTHORITY_IGNORE_SLASHES_STATE;
+            goto again;
+        }
+
+        status = lxb_url_log_append(parser, p,
+                  LXB_URL_ERROR_TYPE_SPECIAL_SCHEME_MISSING_FOLLOWING_SOLIDUS);
+        if (status != LXB_STATUS_OK) {
+            lxb_url_parse_return(orig_data, buf, status);
+        }
+
+        state = LXB_URL_STATE_RELATIVE_STATE;
+        goto again;
+
+    case LXB_URL_STATE_PATH_OR_AUTHORITY_STATE:
+        if (p < end && *p == '/') {
+            p += 1;
+            state = LXB_URL_STATE_AUTHORITY_STATE;
+        }
+        else {
+            state = LXB_URL_STATE_PATH_STATE;
+        }
+
+        goto again;
+
+    case LXB_URL_STATE_RELATIVE_STATE:
+        status = lxb_url_scheme_copy(&base_url->scheme, &url->scheme, url->mraw);
+        if (status != LXB_STATUS_OK) {
+            lxb_url_parse_return(orig_data, buf, status);
+        }
+
+        if (end - p >= 1) {
+            if (*p == '/') {
+                p += 1;
+                state = LXB_URL_STATE_RELATIVE_SLASH_STATE;
+                goto again;
+            }
+
+            if (lxb_url_is_special(url) && *p == '\\') {
+                p += 1;
+
+                status = lxb_url_log_append(parser, p,
+                                   LXB_URL_ERROR_TYPE_INVALID_REVERSE_SOLIDUS);
+                if (status != LXB_STATUS_OK) {
+                    lxb_url_parse_return(orig_data, buf, status);
+                }
+
+                state = LXB_URL_STATE_RELATIVE_SLASH_STATE;
+                goto again;
+            }
+        }
+
+        status = lxb_url_username_copy(&base_url->username, &url->username,
+                                       url->mraw);
+        if (status != LXB_STATUS_OK) {
+            lxb_url_parse_return(orig_data, buf, status);
+        }
+
+        status = lxb_url_password_copy(&base_url->password, &url->password,
+                                       url->mraw);
+        if (status != LXB_STATUS_OK) {
+            lxb_url_parse_return(orig_data, buf, status);
+        }
+
+        status = lxb_url_host_copy(&base_url->host, &url->host, url->mraw);
+        if (status != LXB_STATUS_OK) {
+            lxb_url_parse_return(orig_data, buf, status);
+        }
+
+        if (base_url->has_port) {
+            lxb_url_port_set(url, base_url->port);
+        }
+
+        status = lxb_url_path_copy(base_url, url);
+        if (status != LXB_STATUS_OK) {
+            lxb_url_parse_return(orig_data, buf, status);
+        }
+
+        status = lxb_url_query_copy(&base_url->query, &url->query, url->mraw);
+        if (status != LXB_STATUS_OK) {
+            lxb_url_parse_return(orig_data, buf, status);
+        }
+
+        if (end - p == 0) {
+            lxb_url_parse_return(orig_data, buf, LXB_STATUS_OK);
+        }
+
+        if (*p == '?') {
+            p += 1;
+
+            (void) lexbor_str_destroy(&url->query, url->mraw, false);
+
+            state = LXB_URL_STATE_QUERY_STATE;
+            goto again;
+        }
+
+        if (*p == '#') {
+            p += 1;
+
+            (void) lexbor_str_destroy(&url->fragment, url->mraw, false);
+
+            state = LXB_URL_STATE_FRAGMENT_STATE;
+            goto again;
+        }
+
+        (void) lexbor_str_destroy(&url->query, url->mraw, false);
+
+        lxb_url_path_shorten(url);
+
+        state = LXB_URL_STATE_PATH_STATE;
+        goto again;
+
+    case LXB_URL_STATE_RELATIVE_SLASH_STATE:
+        c = (end - p >= 1) ? *p : '\0';
+
+        if (lxb_url_is_special(url) && (c == '/' || c == '\\')) {
+            if (c == '\\') {
+                status = lxb_url_log_append(parser, p,
+                                   LXB_URL_ERROR_TYPE_INVALID_REVERSE_SOLIDUS);
+                if (status != LXB_STATUS_OK) {
+                    lxb_url_parse_return(orig_data, buf, status);
+                }
+            }
+
+            p += 1;
+            state = LXB_URL_STATE_SPECIAL_AUTHORITY_IGNORE_SLASHES_STATE;
+            goto again;
+        }
+
+        if (c == '/') {
+            p += 1;
+            state = LXB_URL_STATE_AUTHORITY_STATE;
+            goto again;
+        }
+
+        status = lxb_url_username_copy(&base_url->username, &url->username,
+                                       url->mraw);
+        if (status != LXB_STATUS_OK) {
+            lxb_url_parse_return(orig_data, buf, status);
+        }
+
+        status = lxb_url_password_copy(&base_url->password, &url->password,
+                                       url->mraw);
+        if (status != LXB_STATUS_OK) {
+            lxb_url_parse_return(orig_data, buf, status);
+        }
+
+        status = lxb_url_host_copy(&base_url->host, &url->host, url->mraw);
+        if (status != LXB_STATUS_OK) {
+            lxb_url_parse_return(orig_data, buf, status);
+        }
+
+        if (base_url->has_port) {
+            lxb_url_port_set(url, base_url->port);
+        }
+
+        state = LXB_URL_STATE_PATH_STATE;
+
+        goto again;
+
+    case LXB_URL_STATE_SPECIAL_AUTHORITY_SLASHES_STATE:
+        state = LXB_URL_STATE_SPECIAL_AUTHORITY_IGNORE_SLASHES_STATE;
+
+        if (end - p > 1 && p[0] == '/' && p[1] == '/') {
+            p += 2;
+        }
+        else {
+            status = lxb_url_log_append(parser, p,
+                  LXB_URL_ERROR_TYPE_SPECIAL_SCHEME_MISSING_FOLLOWING_SOLIDUS);
+            if (status != LXB_STATUS_OK) {
+                lxb_url_parse_return(orig_data, buf, status);
+            }
+        }
+
+        goto again;
+
+    case LXB_URL_STATE_SPECIAL_AUTHORITY_IGNORE_SLASHES_STATE:
+        if (p >= end || (*p != '/' && *p != '\\')) {
+            state = LXB_URL_STATE_AUTHORITY_STATE;
+            goto again;
+        }
+
+        status = lxb_url_log_append(parser, p,
+                  LXB_URL_ERROR_TYPE_SPECIAL_SCHEME_MISSING_FOLLOWING_SOLIDUS);
+        if (status != LXB_STATUS_OK) {
+            lxb_url_parse_return(orig_data, buf, status);
+        }
+
+        p += 1;
+        goto again;
+
+    case LXB_URL_STATE_AUTHORITY_STATE:
+        begin = p;
+        pswd = NULL;
+        at_sign = false;
+
+        while (p < end) {
+            c = *p;
+
+            switch (c) {
+                case '@':
+                    status = lxb_url_log_append(parser, p,
+                                                LXB_URL_ERROR_TYPE_INVALID_CREDENTIALS);
+                    if (status != LXB_STATUS_OK) {
+                        lxb_url_parse_return(orig_data, buf, status);
+                    }
+
+                    if (p == begin) {
+                        at_sign = true;
+                        break;
+                    }
+
+                    if (pswd == NULL || !at_sign) {
+                        tmp = (pswd != NULL) ? pswd - 1 : p;
+
+                        if (tmp > begin) {
+                            status = lxb_url_percent_encode_after_utf_8(begin, tmp,
+                                                        &url->username, url->mraw,
+                                                        LXB_URL_MAP_USERINFO, false);
+                            if (status != LXB_STATUS_OK) {
+                                lxb_url_parse_return(orig_data, buf, status);
+                            }
+                        }
+                    }
+
+                    if (pswd != NULL && p > pswd) {
+                        status = lxb_url_percent_encode_after_utf_8(pswd, p,
+                                                    &url->password, url->mraw,
+                                                    LXB_URL_MAP_USERINFO, false);
+                        if (status != LXB_STATUS_OK) {
+                            lxb_url_parse_return(orig_data, buf, status);
+                        }
+
+                        pswd = p;
+                    }
+
+                    at_sign = true;
+                    begin = p;
+
+                    break;
+
+                case ':':
+                    if (pswd == NULL) {
+                        pswd = p + 1;
+                    }
+
+                    break;
+
+                case '/':
+                case '\\':
+                case '?':
+                case '#':
+                    if (c == '\\') {
+                        if (!lxb_url_is_special(url)) {
+                            p += 1;
+                            continue;
+                        }
+                    }
+
+                    goto authority_done;
+
+                default:
+                    break;
+            }
+
+            p += 1;
+        }
+
+    authority_done:
+
+        if (at_sign) {
+            if (begin == p || begin == p - 1) {
+                status = lxb_url_log_append(parser, p,
+                                            LXB_URL_ERROR_TYPE_INVALID_CREDENTIALS);
+                if (status != LXB_STATUS_OK) {
+                    lxb_url_parse_return(orig_data, buf, status);
+                }
+
+                lxb_url_parse_return(orig_data, buf, LXB_STATUS_ERROR_UNEXPECTED_DATA);
+            }
+
+            /* Skip '@'. */
+            begin += 1;
+        }
+
+        p = begin;
+
+        state = LXB_URL_STATE_HOST_STATE;
+        goto again;
+
+    case LXB_URL_STATE_HOST_STATE:
+    case LXB_URL_STATE_HOSTNAME_STATE:
+        begin = p;
+
+        if (override_state != LXB_URL_STATE__UNDEF
+            && url->scheme.type == LXB_URL_SCHEMEL_TYPE_FILE)
+        {
+            p -= 1;
+            state = LXB_URL_STATE_FILE_HOST_STATE;
+            goto again;
+        }
+
+        opt = !lxb_url_is_special(url);
+
+        for (; p < end; p++) {
+            c = *p;
+
+            switch (c) {
+                case '/':
+                case '?':
+                case '#':
+                    goto host_done;
+
+                case '\\':
+                    if (!lxb_url_is_special(url)) {
+                        break;
+                    }
+
+                    goto host_done;
+
+                case ':':
+                    if (inside_bracket) {
+                        break;
+                    }
+
+                    if (p == begin) {
+                        goto failed_host;
+                    }
+
+                    if (override_state == LXB_URL_STATE_HOSTNAME_STATE) {
+                        lxb_url_parse_return(orig_data, buf, LXB_STATUS_OK);
+                    }
+
+                    status = lxb_url_host_parse(parser, begin, p, &url->host,
+                                                url->mraw, opt);
+                    if (status != LXB_STATUS_OK) {
+                        lxb_url_parse_return(orig_data, buf, status);
+                    }
+
+                    p += 1;
+
+                    state = LXB_URL_STATE_PORT_STATE;
+                    goto again;
+
+                case '[':
+                    inside_bracket = true;
+                    break;
+
+                case ']':
+                    inside_bracket = false;
+                    break;
+
+                case '%':
+                    opt |= LXB_URL_HOST_OPT_DECODE;
+                    break;
+
+                case 'X':
+                case 'x':
+                    if (p + 4 <= end && (p[1] == 'n' || p[1] == 'N')
+                        && p[2] == '-' && p[3] == '-')
+                    {
+                        opt |= LXB_URL_HOST_OPT_IDNA;
+                        p += 3;
+                    }
+
+                    break;
+
+                default:
+                    if (c >= 0x80) {
+                        opt |= LXB_URL_HOST_OPT_IDNA;
+                    }
+
+                    break;
+            }
+        }
+
+        host_done:
+
+        if (begin == p && lxb_url_is_special(url)) {
+            goto failed_host;
+        }
+
+        if (override_state != LXB_URL_STATE__UNDEF && begin == p
+            && (lxb_url_includes_credentials(url) || url->has_port))
+        {
+            lxb_url_parse_return(orig_data, buf, LXB_STATUS_OK);
+        }
+
+        status = lxb_url_host_parse(parser, begin, p, &url->host,
+                                    url->mraw, opt);
+        if (status != LXB_STATUS_OK) {
+            lxb_url_parse_return(orig_data, buf, status);
+        }
+
+        if (override_state != LXB_URL_STATE__UNDEF) {
+            lxb_url_parse_return(orig_data, buf, LXB_STATUS_OK);
+        }
+
+        state = LXB_URL_STATE_PATH_START_STATE;
+        goto again;
+
+    case LXB_URL_STATE_PORT_STATE:
+        begin = p;
+
+        while (true) {
+            c = (p < end) ? *p : '\0';
+
+            if (c >= '0' && c <= '9') {
+                p += 1;
+                continue;
+            }
+
+            if (p >= end || c == '/' || c == '?' || c == '#'
+                || (lxb_url_is_special(url) && c == '\\')
+                || override_state != LXB_URL_STATE__UNDEF)
+            {
+                if (begin == p) {
+                    if (override_state != LXB_URL_STATE__UNDEF) {
+                        lxb_url_parse_return(orig_data, buf, LXB_STATUS_OK);
+                    }
+
+                    state = LXB_URL_STATE_PATH_START_STATE;
+                    goto again;
+                }
+
+                port = 0;
+
+                while (begin < p) {
+                    port = lexbor_str_res_map_num[*begin++] + port * 10;
+
+                    if (port > 65535) {
+                        status = lxb_url_log_append(parser, p,
+                                          LXB_URL_ERROR_TYPE_PORT_OUT_OF_RANGE);
+                        if (status != LXB_STATUS_OK) {
+                            lxb_url_parse_return(orig_data, buf, status);
+                        }
+
+                        lxb_url_parse_return(orig_data, buf,
+                                             LXB_STATUS_ERROR_UNEXPECTED_DATA);
+                    }
+                }
+
+                if (!lxb_url_is_special(url)
+                    || !lxb_url_scheme_equal_port(url->scheme.type, port))
+                {
+                    url->port = port;
+                    url->has_port = true;
+                }
+                else {
+                    url->port = 0;
+                    url->has_port = false;
+                }
+
+                if (override_state != LXB_URL_STATE__UNDEF) {
+                    lxb_url_parse_return(orig_data, buf, LXB_STATUS_OK);
+                }
+
+                state = LXB_URL_STATE_PATH_START_STATE;
+                goto again;
+            }
+
+            status = lxb_url_log_append(parser, p,
+                                        LXB_URL_ERROR_TYPE_PORT_INVALID);
+            if (status != LXB_STATUS_OK) {
+                lxb_url_parse_return(orig_data, buf, status);
+            }
+
+            lxb_url_parse_return(orig_data, buf, LXB_STATUS_ERROR_UNEXPECTED_DATA);
+        }
+
+        break;
+
+    case LXB_URL_STATE_FILE_STATE:
+        schm = lxb_url_scheme_by_type(LXB_URL_SCHEMEL_TYPE_FILE);
+
+        status = lxb_url_scheme_copy_special(schm, &url->scheme, url->mraw);
+        if (status != LXB_STATUS_OK) {
+            lxb_url_parse_return(orig_data, buf, status);
+        }
+
+        lxb_url_host_set_empty(&url->host, url->mraw);
+
+        c = (p < end) ? *p : '\0';
+
+        if (c == '/' || c == '\\') {
+            if (c == '\\') {
+                status = lxb_url_log_append(parser, p,
+                                   LXB_URL_ERROR_TYPE_INVALID_REVERSE_SOLIDUS);
+                if (status != LXB_STATUS_OK) {
+                    lxb_url_parse_return(orig_data, buf, status);
+                }
+            }
+
+            p += 1;
+
+            state = LXB_URL_STATE_FILE_SLASH_STATE;
+            goto again;
+        }
+
+        if (base_url != NULL
+            && base_url->scheme.type == LXB_URL_SCHEMEL_TYPE_FILE)
+        {
+            status = lxb_url_host_copy(&base_url->host, &url->host, url->mraw);
+            if (status != LXB_STATUS_OK) {
+                lxb_url_parse_return(orig_data, buf, status);
+            }
+
+            status = lxb_url_path_copy(base_url, url);
+            if (status != LXB_STATUS_OK) {
+                lxb_url_parse_return(orig_data, buf, status);
+            }
+
+            status = lxb_url_query_copy(&base_url->query, &url->query, url->mraw);
+            if (status != LXB_STATUS_OK) {
+                lxb_url_parse_return(orig_data, buf, status);
+            }
+
+            if (c == '?') {
+                p += 1;
+
+                (void) lexbor_str_destroy(&url->query, url->mraw, false);
+
+                state = LXB_URL_STATE_QUERY_STATE;
+                goto again;
+            }
+
+            if (c == '#') {
+                p += 1;
+
+                (void) lexbor_str_destroy(&url->fragment, url->mraw, false);
+
+                state = LXB_URL_STATE_FRAGMENT_STATE;
+                goto again;
+            }
+
+            if (p >= end) {
+                lxb_url_parse_return(orig_data, buf, LXB_STATUS_OK);
+            }
+
+            (void) lexbor_str_destroy(&url->query, url->mraw, false);
+
+            if (!lxb_url_start_windows_drive_letter(p, end)) {
+                lxb_url_path_shorten(url);
+            }
+            else {
+                status = lxb_url_log_append(parser, p,
+                          LXB_URL_ERROR_TYPE_FILE_INVALID_WINDOWS_DRIVE_LETTER);
+                if (status != LXB_STATUS_OK) {
+                    lxb_url_parse_return(orig_data, buf, status);
+                }
+
+                lxb_url_path_set_null(url);
+                url->path.opaque = true;
+            }
+        }
+
+        state = LXB_URL_STATE_PATH_STATE;
+        goto again;
+
+    case LXB_URL_STATE_FILE_SLASH_STATE:
+        c = (p < end) ? *p : '\0';
+
+        if (c == '/' || c == '\\') {
+            if (c == '\\') {
+                status = lxb_url_log_append(parser, p,
+                                   LXB_URL_ERROR_TYPE_INVALID_REVERSE_SOLIDUS);
+                if (status != LXB_STATUS_OK) {
+                    lxb_url_parse_return(orig_data, buf, status);
+                }
+            }
+
+            p += 1;
+
+            state = LXB_URL_STATE_FILE_HOST_STATE;
+            goto again;
+        }
+
+        if (base_url != NULL
+            && base_url->scheme.type == LXB_URL_SCHEMEL_TYPE_FILE)
+        {
+            status = lxb_url_host_copy(&base_url->host, &url->host, url->mraw);
+            if (status != LXB_STATUS_OK) {
+                lxb_url_parse_return(orig_data, buf, status);
+            }
+
+            if (!lxb_url_start_windows_drive_letter(p, end)
+                && !base_url->path.opaque && base_url->path.length >= 1)
+            {
+                tmp = lxb_url_path_part_by_index(base_url,
+                                       base_url->path.str.data[0] == '/', &len);
+
+                if (tmp != NULL
+                    && lxb_url_normalized_windows_drive_letter(tmp, tmp + len))
+                {
+                    len = (tmp + len) - base_url->path.str.data;
+
+                    status = lxb_url_path_append_wo_slash(url,
+                                                  base_url->path.str.data, len);
+                    if (status != LXB_STATUS_OK) {
+                        lxb_url_parse_return(orig_data, buf, status);
+                    }
+                }
+            }
+        }
+
+        state = LXB_URL_STATE_PATH_STATE;
+        goto again;
+
+    case LXB_URL_STATE_FILE_HOST_STATE:
+        begin = p;
+        opt = !lxb_url_is_special(url);
+
+        while (true) {
+            if (p < end) {
+                switch (*p) {
+                    case '/':
+                    case '\\':
+                    case '?':
+                    case '#':
+                        break;
+
+                    case '%':
+                        p += 1;
+                        opt |= LXB_URL_HOST_OPT_DECODE;
+                        continue;
+
+                    case 'X':
+                    case 'x':
+                        if (p + 4 <= end && (p[1] == 'n' || p[1] == 'N')
+                            && p[2] == '-' && p[3] == '-')
+                        {
+                            opt |= LXB_URL_HOST_OPT_IDNA;
+                            p += 3;
+                        }
+
+                        p += 1;
+                        continue;
+
+                    default:
+                        if (*p >= 0x80) {
+                            opt |= LXB_URL_HOST_OPT_IDNA;
+                        }
+
+                        p += 1;
+                        continue;
+                }
+            }
+
+            if (override_state == LXB_URL_STATE__UNDEF && p - begin == 2
+                && lxb_url_windows_drive_letter(begin, p))
+            {
+                status = lxb_url_log_append(parser, begin,
+                    LXB_URL_ERROR_TYPE_FILE_INVALID_WINDOWS_DRIVE_LETTER_HOST);
+                if (status != LXB_STATUS_OK) {
+                    lxb_url_parse_return(orig_data, buf, status);
+                }
+
+                p = begin;
+
+                state = LXB_URL_STATE_PATH_STATE;
+                goto again;
+            }
+
+            if (p == begin) {
+                lxb_url_host_set_empty(&url->host, url->mraw);
+
+                if (override_state != LXB_URL_STATE__UNDEF) {
+                    lxb_url_parse_return(orig_data, buf, LXB_STATUS_OK);
+                }
+
+                state = LXB_URL_STATE_PATH_START_STATE;
+                goto again;
+            }
+
+            status = lxb_url_host_parse(parser, begin, p, &url->host,
+                                        url->mraw, opt);
+            if (status != LXB_STATUS_OK) {
+                lxb_url_parse_return(orig_data, buf, status);
+            }
+
+            if (lxb_url_host_eq(&url->host, lh_str.data, lh_str.length)) {
+                lxb_url_host_set_empty(&url->host, url->mraw);
+            }
+
+            if (override_state != LXB_URL_STATE__UNDEF) {
+                lxb_url_parse_return(orig_data, buf, LXB_STATUS_OK);
+            }
+
+            state = LXB_URL_STATE_PATH_START_STATE;
+            goto again;
+        }
+
+        break;
+
+    case LXB_URL_STATE_PATH_START_STATE:
+        c = (p < end) ? *p : '\0';
+
+        if (lxb_url_is_special(url)) {
+            if (c == '\\') {
+                status = lxb_url_log_append(parser, p,
+                                   LXB_URL_ERROR_TYPE_INVALID_REVERSE_SOLIDUS);
+                if (status != LXB_STATUS_OK) {
+                    lxb_url_parse_return(orig_data, buf, status);
+                }
+
+                p += 1;
+            }
+            else if (c == '/') {
+                p += 1;
+            }
+
+            state = LXB_URL_STATE_PATH_STATE;
+            goto again;
+        }
+
+        if (override_state == LXB_URL_STATE__UNDEF) {
+            if (c == '?') {
+                p += 1;
+                state = LXB_URL_STATE_QUERY_STATE;
+                goto again;
+            }
+
+            if (c == '#') {
+                p += 1;
+                state = LXB_URL_STATE_FRAGMENT_STATE;
+                goto again;
+            }
+        }
+
+        if (p < end) {
+            if (c == '/') {
+                p += 1;
+            }
+
+            state = LXB_URL_STATE_PATH_STATE;
+            goto again;
+        }
+
+        if (override_state != LXB_URL_STATE__UNDEF
+            && url->host.type == LXB_URL_HOST_TYPE__UNDEF)
+        {
+            status = lxb_url_path_append(url, mp_str.data, mp_str.length);
+            lxb_url_parse_return(orig_data, buf, status);
+        }
+
+        lxb_url_parse_return(orig_data, buf, LXB_STATUS_OK);
+
+    case LXB_URL_STATE_PATH_STATE:
+        p = lxb_url_path_fast_path(parser, url, p, end,
+                                   override_state == LXB_URL_STATE__UNDEF);
+        if (p == NULL) {
+            lxb_url_parse_return(orig_data, buf, LXB_STATUS_ERROR_MEMORY_ALLOCATION);
+        }
+
+        if (p >= end) {
+            lxb_url_parse_return(orig_data, buf, LXB_STATUS_OK);
+        }
+
+        switch (*p) {
+            case '?':
+                p += 1;
+                state = LXB_URL_STATE_QUERY_STATE;
+                break;
+            case '#':
+                p += 1;
+                state = LXB_URL_STATE_FRAGMENT_STATE;
+                break;
+        }
+
+        goto again;
+
+    case LXB_URL_STATE_OPAQUE_PATH_STATE:
+        begin = p;
+        url->path.opaque = true;
+
+        while (true) {
+            if (p >= end) {
+                tmp_str.data = NULL;
+
+                status = lxb_url_percent_encode_after_utf_8(begin, p,
+                                                            &tmp_str, url->mraw,
+                                                            LXB_URL_MAP_C0, false);
+                if (status != LXB_STATUS_OK) {
+                    lxb_url_parse_return(orig_data, buf, status);
+                }
+
+                status = lxb_url_path_list_push(url, &tmp_str);
+
+                lxb_url_parse_return(orig_data, buf, status);
+            }
+
+            c = *p;
+
+            if (c == '#' || c == '?') {
+                tmp_str.data = NULL;
+
+                status = lxb_url_percent_encode_after_utf_8(begin, p,
+                                                            &tmp_str, url->mraw,
+                                                            LXB_URL_MAP_C0, false);
+                if (status != LXB_STATUS_OK) {
+                    lxb_url_parse_return(orig_data, buf, status);
+                }
+
+                status = lxb_url_path_list_push(url, &tmp_str);
+                if (status != LXB_STATUS_OK) {
+                    lxb_url_parse_return(orig_data, buf, status);
+                }
+
+                p += 1;
+
+                if (c == '#') {
+                    state = LXB_URL_STATE_FRAGMENT_STATE;
+                }
+                else {
+                    state = LXB_URL_STATE_QUERY_STATE;
+                }
+
+                goto again;
+            }
+
+            tmp = p;
+            cp = lxb_url_decode_valid_utf_8_single(&p, end);
+
+            if ((!lxb_url_is_url_codepoint(cp) && cp != '%')
+                || (cp == '%' && (end - p < 2
+                                  || lexbor_str_res_map_hex[p[0]] == 0xff
+                                  || lexbor_str_res_map_hex[p[1]] == 0xff)))
+            {
+                status = lxb_url_log_append(parser, tmp,
+                                            LXB_URL_ERROR_TYPE_INVALID_URL_UNIT);
+                if (status != LXB_STATUS_OK) {
+                    lxb_url_parse_return(orig_data, buf, status);
+                }
+            }
+        }
+
+        break;
+
+    case LXB_URL_STATE_QUERY_STATE:
+        begin = p;
+
+        while (true) {
+            c = (p < end) ? *p : '\0';
+
+            if (p >= end || (override_state == LXB_URL_STATE__UNDEF && *p == '#')) {
+                if (lxb_url_is_special(url)) {
+                    map_type = LXB_URL_MAP_SPECIAL_QUERY;
+                }
+                else {
+                    map_type = LXB_URL_MAP_QUERY;
+                }
+
+                status = lxb_url_percent_encode_after_utf_8(begin, p,
+                                                            &url->query,
+                                                            url->mraw,
+                                                            map_type, false);
+                if (status != LXB_STATUS_OK) {
+                    lxb_url_parse_return(orig_data, buf, status);
+                }
+
+                if (p < end) {
+                    p += 1;
+                    state = LXB_URL_STATE_FRAGMENT_STATE;
+                    goto again;
+                }
+
+                lxb_url_parse_return(orig_data, buf, LXB_STATUS_OK);
+            }
+
+            tmp = p;
+            cp = lxb_url_decode_valid_utf_8_single(&p, end);
+
+            if ((!lxb_url_is_url_codepoint(cp) && cp != '%')
+                || (cp == '%' && (end - p < 2
+                                  || lexbor_str_res_map_hex[p[0]] == 0xff
+                                  || lexbor_str_res_map_hex[p[1]] == 0xff)))
+            {
+                status = lxb_url_log_append(parser, tmp,
+                                            LXB_URL_ERROR_TYPE_INVALID_URL_UNIT);
+                if (status != LXB_STATUS_OK) {
+                    lxb_url_parse_return(orig_data, buf, status);
+                }
+            }
+        }
+
+        break;
+
+    case LXB_URL_STATE_FRAGMENT_STATE:
+        begin = p;
+
+        while (p < end) {
+            tmp = p;
+            cp = lxb_url_decode_valid_utf_8_single(&p, end);
+
+            if ((!lxb_url_is_url_codepoint(cp) && cp != '%')
+                || (cp == '%' && (end - p < 2
+                                  || lexbor_str_res_map_hex[p[0]] == 0xff
+                                  || lexbor_str_res_map_hex[p[1]] == 0xff)))
+            {
+                status = lxb_url_log_append(parser, tmp,
+                                            LXB_URL_ERROR_TYPE_INVALID_URL_UNIT);
+                if (status != LXB_STATUS_OK) {
+                    lxb_url_parse_return(orig_data, buf, status);
+                }
+            }
+        }
+
+        status = lxb_url_percent_encode_after_utf_8(begin, p, &url->fragment,
+                                                    url->mraw,
+                                                    LXB_URL_MAP_FRAGMENT, false);
+        lxb_url_parse_return(orig_data, buf, status);
+
+    default:
+        lxb_url_parse_return(orig_data, buf, LXB_STATUS_ERROR);
+    }
+
+    lxb_url_parse_return(orig_data, buf, LXB_STATUS_OK);
+
+failed_non_relative_url:
+
+    status = lxb_url_log_append(parser, p,
+                           LXB_URL_ERROR_TYPE_MISSING_SCHEME_NON_RELATIVE_URL);
+    if (status != LXB_STATUS_OK) {
+        lxb_url_parse_return(orig_data, buf, status);
+    }
+
+    lxb_url_parse_return(orig_data, buf, LXB_STATUS_ERROR_UNEXPECTED_DATA);
+
+failed_host:
+
+    status = lxb_url_log_append(parser, p,
+                                LXB_URL_ERROR_TYPE_HOST_MISSING);
+    if (status != LXB_STATUS_OK) {
+        lxb_url_parse_return(orig_data, buf, status);
+    }
+
+    lxb_url_parse_return(orig_data, buf, LXB_STATUS_ERROR_UNEXPECTED_DATA);
+}
+
+static const lxb_char_t *
+lxb_url_path_fast_path(lxb_url_parser_t *parser, lxb_url_t *url,
+                       const lxb_char_t *p, const lxb_char_t *end, bool bqs)
+{
+    size_t count;
+    lxb_char_t x, c;
+    lxb_status_t status;
+    const lxb_char_t *begin, *last;
+
+    begin = p;
+    last = p;
+    count = url->path.length;
+
+    for (; p < end; p++) {
+        c = *p;
+        x = lxb_url_path_map[c];
+
+        if (x != 0x00) {
+            if (c == '/') {
+                count += 1;
+                last = p + 1;
+            }
+            else if (c == '%') {
+                if (end - p < 3
+                    || lexbor_str_res_map_hex[p[1]] == 0xff
+                    || lexbor_str_res_map_hex[p[2]] == 0xff)
+                {
+                    status = lxb_url_log_append(parser, p,
+                                                LXB_URL_ERROR_TYPE_INVALID_URL_UNIT);
+                    if (status != LXB_STATUS_OK) {
+                        return NULL;
+                    }
+
+                    p = (end - p < 3) ? end - 1 : p + 2;
+                }
+                else if (p[1] == '2' && (p[2] == 'e' || p[2] == 'E')
+                         && (p == begin
+                             || p[-1] == '/'
+                             || (p[-1] == '\\' && lxb_url_is_special(url))))
+                {
+                    url->path.length = count;
+
+                    status = lxb_url_path_try_dot(url, &begin, &last,
+                                                  &p, end, bqs);
+                    if (status != LXB_STATUS_OK) {
+                        return NULL;
+                    }
+
+                    count = url->path.length;
+                }
+                else {
+                    p += 2;
+                }
+            }
+            else if ((c == '?' || c == '#') && bqs) {
+                break;
+            }
+            else if (c == '\\' && lxb_url_is_special(url)) {
+                count += 1;
+
+                status = lxb_url_log_append(parser, p,
+                                            LXB_URL_ERROR_TYPE_INVALID_REVERSE_SOLIDUS);
+                if (status != LXB_STATUS_OK) {
+                    return NULL;
+                }
+
+                status = lxb_url_path_append(url, begin, p - begin);
+                if (status != LXB_STATUS_OK) {
+                    return NULL;
+                }
+
+                begin = p + 1;
+                last = p + 1;
+            }
+            else if (c == '.') {
+                if (p == begin
+                    || p[-1] == '/'
+                    || (p[-1] == '\\' && lxb_url_is_special(url)))
+                {
+                    url->path.length = count;
+
+                    status = lxb_url_path_try_dot(url, &begin, &last,
+                                                  &p, end, bqs);
+                    if (status != LXB_STATUS_OK) {
+                        return NULL;
+                    }
+
+                    count = url->path.length;
+                }
+            }
+            else {
+                url->path.length = count;
+
+                if (last - 1 > begin) {
+                    status = lxb_url_path_append(url, begin,
+                                                 (last - 1) - begin);
+                    if (status != LXB_STATUS_OK) {
+                        return NULL;
+                    }
+                }
+
+                return lxb_url_path_slow_path(parser, url, last, end, bqs);
+            }
+        }
+    }
+
+    status = lxb_url_path_append(url, begin, p - begin);
+    if (status != LXB_STATUS_OK) {
+        return NULL;
+    }
+
+    if (count == 0 || p != begin) {
+        count += 1;
+    }
+
+    url->path.length = count;
+
+    return p;
+}
+
+/*
+ * The lxb_url_path_slow_path() function should not be static.  Otherwise, the
+ * compiler will inline it, which will lead to cache problems and slower code
+ * execution.
+ */
+const lxb_char_t *
+lxb_url_path_slow_path(lxb_url_parser_t *parser, lxb_url_t *url,
+                       const lxb_char_t *data, const lxb_char_t *end, bool bqs)
+{
+    size_t len, count;
+    lxb_char_t c, *last, *sbuf, *sbuf_begin;
+    lexbor_str_t *str;
+    lxb_status_t status;
+    lxb_codepoint_t cp;
+    const lxb_char_t *p, *tmp, *sbuf_end;
+    lxb_char_t sbuffer[1024];
+
+    p = data;
+    sbuf = sbuffer;
+    sbuf_begin = sbuffer;
+    sbuf_end = sbuffer + sizeof(sbuffer);
+
+    c = '\0';
+
+    if (url->path.str.length != 0) {
+        str = &url->path.str;
+
+        /* "+ 2" == "/\0" */
+        if (sbuf + (str->length + 2) > sbuf_end) {
+            len = str->length + sizeof(sbuffer);
+
+            sbuf_begin = lexbor_malloc(len);
+            if (sbuf_begin == NULL) {
+                return NULL;
+            }
+
+            sbuf = sbuf_begin;
+            sbuf_end = sbuf + len;
+        }
+
+        memcpy(sbuf, str->data, str->length);
+        sbuf += str->length;
+
+        str->length = 0;
+    }
+
+    *sbuf++ = '/';
+
+    last = sbuf;
+    count = url->path.length;
+
+    while (p < end) {
+        c = *p;
+
+        if (c >= 0x80) {
+            tmp = p;
+            cp = lxb_url_decode_valid_utf_8_single(&p, end);
+
+            if (!lxb_url_is_url_codepoint(cp)) {
+                status = lxb_url_log_append(parser, tmp,
+                                            LXB_URL_ERROR_TYPE_INVALID_URL_UNIT);
+                if (status != LXB_STATUS_OK) {
+                    goto failed;
+                }
+            }
+
+            len = (p - tmp) * 3;
+
+            if (sbuf + len + 1 >= sbuf_end) {
+                LXB_URL_SBUF_REALLOC(sbuf, sbuf_begin, sbuf_end, sbuffer, last);
+            }
+
+            while (tmp < p) {
+                c = *tmp++;
+
+                *sbuf++ = '%';
+                *sbuf++ = lexbor_str_res_char_to_two_hex_value[c][0];
+                *sbuf++ = lexbor_str_res_char_to_two_hex_value[c][1];
+            }
+
+            continue;
+        }
+
+        if (c == '/') {
+            *sbuf++ = '/';
+            if (sbuf >= sbuf_end) {
+                LXB_URL_SBUF_REALLOC(sbuf, sbuf_begin, sbuf_end, sbuffer, last);
+            }
+
+            lxb_url_path_fix_windows_drive(url, last, sbuf, count);
+
+            count += 1;
+            last = sbuf;
+
+            if (p + 1 >= end) {
+                count += 1;
+            }
+        }
+        else if (c == '\\' && lxb_url_is_special(url)) {
+            status = lxb_url_log_append(parser, p,
+                                        LXB_URL_ERROR_TYPE_INVALID_REVERSE_SOLIDUS);
+            if (status != LXB_STATUS_OK) {
+                goto failed;
+            }
+
+            *sbuf++ = '/';
+            if (sbuf >= sbuf_end) {
+                LXB_URL_SBUF_REALLOC(sbuf, sbuf_begin, sbuf_end, sbuffer, last);
+            }
+
+            lxb_url_path_fix_windows_drive(url, last, sbuf, count);
+
+            count += 1;
+            last = sbuf;
+
+            if (p + 1 >= end) {
+                count += 1;
+            }
+        }
+        else if ((c == '?' || c == '#') && bqs) {
+            lxb_url_path_fix_windows_drive(url, last, sbuf, count);
+
+            count += 1;
+            last = sbuf;
+            break;
+        }
+        else if (lxb_url_map[c] & LXB_URL_MAP_PATH) {
+            if (sbuf + 4 >= sbuf_end) {
+                LXB_URL_SBUF_REALLOC(sbuf, sbuf_begin, sbuf_end, sbuffer, last);
+            }
+
+            *sbuf++ = '%';
+            *sbuf++ = lexbor_str_res_char_to_two_hex_value[c][0];
+            *sbuf++ = lexbor_str_res_char_to_two_hex_value[c][1];
+        }
+        else if (c == '.') {
+            if (last == sbuf) {
+                tmp = lxb_url_path_dot_count(url, p, end, sbuf_begin,
+                                             &sbuf, &last, &count, bqs);
+
+                if (tmp != p) {
+                    p = tmp + 1;
+                    continue;
+                }
+            }
+
+            *sbuf++ = '.';
+            if (sbuf >= sbuf_end) {
+                LXB_URL_SBUF_REALLOC(sbuf, sbuf_begin,
+                                     sbuf_end, sbuffer, last);
+            }
+        }
+        else if (c == '%') {
+            if (end - p < 3
+                || lexbor_str_res_map_hex[p[1]] == 0xff
+                || lexbor_str_res_map_hex[p[2]] == 0xff)
+            {
+                status = lxb_url_log_append(parser, p,
+                                            LXB_URL_ERROR_TYPE_INVALID_URL_UNIT);
+                if (status != LXB_STATUS_OK) {
+                    goto failed;
+                }
+            }
+            else if (p[1] == '2' && (p[2] == 'e' || p[2] == 'E')
+                     && last == sbuf)
+            {
+                tmp = lxb_url_path_dot_count(url, p, end, sbuf_begin,
+                                             &sbuf, &last, &count, bqs);
+
+                if (tmp != p) {
+                    p = tmp + 1;
+                    continue;
+                }
+            }
+
+            *sbuf++ = '%';
+            if (sbuf >= sbuf_end) {
+                LXB_URL_SBUF_REALLOC(sbuf, sbuf_begin,
+                                     sbuf_end, sbuffer, last);
+            }
+        }
+        else {
+            if (lxb_url_codepoint_alphanumeric[c] == 0xFF) {
+                status = lxb_url_log_append(parser, p,
+                                            LXB_URL_ERROR_TYPE_INVALID_URL_UNIT);
+                if (status != LXB_STATUS_OK) {
+                    goto failed;
+                }
+            }
+
+            *sbuf++ = c;
+            if (sbuf >= sbuf_end) {
+                LXB_URL_SBUF_REALLOC(sbuf, sbuf_begin, sbuf_end, sbuffer, last);
+            }
+        }
+
+        p += 1;
+    }
+
+    if (count == 0 || last < sbuf) {
+        lxb_url_path_fix_windows_drive(url, last, sbuf, count);
+        count += 1;
+    }
+
+    url->path.length = count;
+
+    status = lxb_url_path_append_wo_slash(url, sbuf_begin, sbuf - sbuf_begin);
+    if (status != LXB_STATUS_OK) {
+        goto failed;
+    }
+
+    if (sbuf_begin != sbuffer) {
+        lexbor_free(sbuf_begin);
+    }
+
+    return p;
+
+failed:
+
+    if (sbuf_begin != sbuffer) {
+        lexbor_free(sbuf_begin);
+    }
+
+    return NULL;
+}
+
+static lxb_status_t
+lxb_url_path_try_dot(lxb_url_t *url, const lxb_char_t **begin,
+                     const lxb_char_t **last, const lxb_char_t **start,
+                     const lxb_char_t *end, bool bqs)
+{
+    unsigned count;
+    lxb_char_t c;
+    lexbor_str_t *str;
+    lxb_status_t status;
+    const lxb_char_t *p;
+
+    p = *start;
+    count = 0;
+
+    for (; p < end; p++) {
+        c = *p;
+
+        if (c == '/'
+            || (c == '\\' && lxb_url_is_special(url))
+            || ((c == '?' || c == '#') && bqs))
+        {
+            break;
+        }
+        else if (c == '.') {
+            count += 1;
+        }
+        else if (c == '%') {
+            if (p + 3 <= end && p[1] == '2' && (p[2] == 'e' || p[2] == 'E')) {
+                count += 1;
+            }
+            else {
+                return LXB_STATUS_OK;
+            }
+
+            p += 2;
+        }
+        else {
+            return LXB_STATUS_OK;
+        }
+    }
+
+    if (count == 0 || count > 2) {
+        return LXB_STATUS_OK;
+    }
+
+    if (*start > *begin) {
+        status = lxb_url_path_append(url, *begin, (*start - *begin) - 1);
+        if (status != LXB_STATUS_OK) {
+            return status;
+        }
+    }
+
+    if (p < end) {
+        *start = p;
+        *begin = p + 1;
+        *last = *begin;
+    }
+    else {
+        *start = end - 1;
+        *begin = end;
+        *last = end;
+    }
+
+    if (count == 2) {
+        lxb_url_path_shorten(url);
+    }
+    else if (count == 1) {
+        str = &url->path.str;
+
+        if (str->length > 0 && str->data[str->length - 1] == '/') {
+            str->length -= 1;
+            str->data[str->length] = '\0';
+        }
+    }
+
+    return LXB_STATUS_OK;
+}
+
+static const lxb_char_t *
+lxb_url_path_dot_count(lxb_url_t *url, const lxb_char_t *p,
+                       const lxb_char_t *end, const lxb_char_t *sbuf_begin,
+                       lxb_char_t **sbuf, lxb_char_t **last, size_t *path_count,
+                       bool bqs)
+{
+    unsigned count;
+    lxb_char_t c, *last_p;
+    const lxb_char_t *begin;
+
+    count = 0;
+    begin = p;
+
+    for (; p < end; p++) {
+        c = *p;
+
+        if (c == '/'
+            || (c == '\\' && lxb_url_is_special(url))
+            || ((c == '?' || c == '#') && bqs))
+        {
+            break;
+        }
+        else if (c == '.') {
+            count += 1;
+        }
+        else if (c == '%') {
+            if (p + 3 <= end && p[1] == '2' && (p[2] == 'e' || p[2] == 'E')) {
+                count += 1;
+            }
+            else {
+                return begin;
+            }
+
+            p += 2;
+        }
+        else {
+            return begin;
+        }
+    }
+
+    if (count == 0 || count > 2) {
+        return begin;
+    }
+
+    if (url->scheme.type == LXB_URL_SCHEMEL_TYPE_FILE
+        && *path_count == 1
+        && lxb_url_normalized_windows_drive_letter(sbuf_begin + 1, *last - 1))
+    {
+        return p;
+    }
+
+    if (count == 2) {
+        if (*path_count > 0) {
+            *path_count -= 1;
+            last_p = *last - 1;
+
+            while (last_p > sbuf_begin) {
+                last_p -= 1;
+
+                if (*last_p == '/') {
+                    *sbuf = last_p + 1;
+                    break;
+                }
+            }
+
+            *last = *sbuf;
+        }
+    }
+
+    return p;
+}
+
+static void
+lxb_url_path_fix_windows_drive(lxb_url_t *url, lxb_char_t *sbuf,
+                               const lxb_char_t *last, size_t count)
+{
+    if (url->scheme.type == LXB_URL_SCHEMEL_TYPE_FILE
+        && count == 0
+        && ((last - sbuf == 3 && (last[-1] == '/')) || last - sbuf == 2)
+        && lxb_url_windows_drive_letter(sbuf, last))
+    {
+        sbuf[1] = ':';
+    }
+}
+
+static lxb_status_t
+lxb_url_leading_trailing(lxb_url_parser_t *parser,
+                         const lxb_char_t **data, size_t *length)
+{
+    lxb_char_t c;
+    lxb_status_t status;
+    lxb_codepoint_t cp;
+    const lxb_char_t *p, *end, *tmp;
+
+    p = *data;
+    end = p + *length;
+
+    while (p < end) {
+        c = *p;
+
+        if (c > 0x1F && c < 0x80 && c != 0x20) {
+            break;
+        }
+        else if (c >= 0x80) {
+            tmp = p;
+            cp = lxb_url_decode_valid_utf_8_single(&p, end);
+            if (cp > 0x1F && cp != LXB_URL_DECODE_ERROR) {
+                p = tmp;
+                break;
+            }
+
+            continue;
+        }
+
+        p += 1;
+    }
+
+    if (p != *data) {
+        status = lxb_url_log_append(parser, *data,
+                                    LXB_URL_ERROR_TYPE_INVALID_URL_UNIT);
+        if (status != LXB_STATUS_OK) {
+            return status;
+        }
+    }
+
+    while (end > p) {
+        tmp = end;
+        cp = lxb_url_decode_valid_utf_8_single_reverse(&end, p);
+
+        if (cp > 0x1F && cp != 0x20 && cp != LXB_URL_DECODE_ERROR) {
+            end = tmp;
+            break;
+        }
+    }
+
+    if (end != *data + *length) {
+        status = lxb_url_log_append(parser, end,
+                                    LXB_URL_ERROR_TYPE_INVALID_URL_UNIT);
+        if (status != LXB_STATUS_OK) {
+            return status;
+        }
+    }
+
+    *data = p;
+    *length = end - p;
+
+    return LXB_STATUS_OK;
+}
+
+static const lxb_char_t *
+lxb_url_remove_tab_newline(lxb_url_parser_t *parser,
+                           const lxb_char_t *data, size_t *length)
+{
+    size_t len;
+    lxb_char_t c, *buf, *p_buf;
+    lxb_status_t status;
+    const lxb_char_t *p, *end;
+
+    p = data;
+    end = data + *length;
+
+    /* Fast path. */
+
+    p = lexbor_swar_seek3(p, end, '\n', '\r', '\t');
+
+    while (p < end) {
+        c = *p;
+
+        if (c == '\n' || c == '\r' || c == '\t') {
+            /* Slow path. */
+            goto oh_my;
+        }
+
+        p += 1;
+    }
+
+    return data;
+
+oh_my:
+
+    status = lxb_url_log_append(parser, p,
+                                LXB_URL_ERROR_TYPE_INVALID_URL_UNIT);
+    if (status != LXB_STATUS_OK) {
+        return NULL;
+    }
+
+    buf = lexbor_malloc(*length);
+    if (buf == NULL) {
+        return NULL;
+    }
+
+    p_buf = buf;
+    len = p - data;
+    p_buf += len;
+
+    memcpy(buf, data, len);
+
+    p += 1;
+
+    while (p < end) {
+        c = *p;
+
+        if (c == '\n' || c == '\r' || c == '\t') {
+            p += 1;
+            continue;
+        }
+
+        *p_buf++ = c;
+        p += 1;
+    }
+
+    *length = p_buf - buf;
+
+    return buf;
+}
+
+
+static const lxb_url_scheme_data_t *
+lxb_url_scheme_find(const lxb_char_t *data, size_t length)
+{
+    const lxb_url_scheme_data_t *schm;
+
+    for (size_t i = LXB_URL_SCHEMEL_TYPE__UNKNOWN + 1;
+         i < lxb_url_scheme_res_length; i++)
+    {
+        schm = &lxb_url_scheme_res[i];
+
+        if (schm->name.length == length) {
+            if (lexbor_str_data_ncasecmp(schm->name.data, data, length)) {
+                return schm;
+            }
+        }
+    }
+
+    return &lxb_url_scheme_res[LXB_URL_SCHEMEL_TYPE__UNKNOWN];
+}
+
+static lxb_status_t
+lxb_url_percent_encode_after_utf_8(const lxb_char_t *data,
+                                   const lxb_char_t *end, lexbor_str_t *str,
+                                   lexbor_mraw_t *mraw,
+                                   lxb_url_map_type_t enmap,
+                                   bool space_as_plus)
+{
+    size_t length;
+    lxb_status_t status;
+    const lxb_char_t *p;
+    lxb_char_t c, *pd;
+
+    p = data;
+    length = end - p;
+
+    /* Only valid for UTF-8. */
+
+    while (p < end) {
+        if (lxb_url_map[*p++] & enmap) {
+            length += 2;
+        }
+    }
+
+    status = lxb_url_str_init(str, mraw, length + 1);
+    if (status != LXB_STATUS_OK) {
+        return status;
+    }
+
+    p = data;
+    pd = &str->data[str->length];
+
+    while (p < end) {
+        c = *p;
+
+        if (space_as_plus && c == ' ') {
+            *pd++ = '+';
+        }
+        else if (lxb_url_map[c] & enmap) {
+            *pd++ = '%';
+            *pd++ = lexbor_str_res_char_to_two_hex_value[c][0];
+            *pd++ = lexbor_str_res_char_to_two_hex_value[c][1];
+        }
+        else {
+            *pd++ = c;
+        }
+
+        p += 1;
+    }
+
+    *pd = '\0';
+    str->length += pd - &str->data[str->length];
+
+    return LXB_STATUS_OK;
+}
+
+static lxb_status_t
+lxb_url_host_parse(lxb_url_parser_t *parser, const lxb_char_t *data,
+                   const lxb_char_t *end, lxb_url_host_t *host,
+                   lexbor_mraw_t *mraw, lxb_url_host_opt_t opt)
+{
+    char *dest, *tmp;
+    UIDNA *idna;
+    UIDNAInfo pInfo;
+    UErrorCode errcode;
+    size_t size;
+    uint32_t ipv4, length;
+    lxb_char_t c;
+    lxb_status_t status;
+    lexbor_str_t *domain;
+    const lxb_char_t *p;
+
+    if (data < end && *data == '[') {
+        if (end[-1] != ']') {
+            status = lxb_url_log_append(parser, &end[-1],
+                                        LXB_URL_ERROR_TYPE_IPV6_UNCLOSED);
+            if (status != LXB_STATUS_OK) {
+                return status;
+            }
+
+            return LXB_STATUS_ERROR_UNEXPECTED_DATA;
+        }
+
+        data += 1;
+        end -= 1;
+
+        host->type = LXB_URL_HOST_TYPE_IPV6;
+
+        return lxb_url_ipv6_parse(parser, data, end, host->u.ipv6);
+    }
+
+    if (opt & LXB_URL_HOST_OPT_NOT_SPECIAL) {
+        return lxb_url_opaque_host_parse(parser, data, end, host, mraw);
+    }
+
+    domain = &host->u.domain;
+
+    if (opt & LXB_URL_HOST_OPT_DECODE) {
+        status = lxb_url_percent_decode(data, end, domain, mraw, &opt);
+        if (status != LXB_STATUS_OK) {
+            return status;
+        }
+    }
+    else {
+        status = lxb_url_str_init(domain, mraw, (end - data) + 1);
+        if (status != LXB_STATUS_OK) {
+            return status;
+        }
+
+        if (opt & LXB_URL_HOST_OPT_IDNA) {
+            domain->length = end - data;
+
+            memcpy(domain->data, data, domain->length);
+            domain->data[domain->length] = '\0';
+        }
+        else {
+            (void) lexbor_str_append_lowercase(domain, mraw, data, end - data);
+        }
+    }
+
+    if (opt & LXB_URL_HOST_OPT_IDNA) {
+        errcode = U_ZERO_ERROR;
+
+        idna = uidna_openUTS46(UIDNA_CHECK_BIDI|UIDNA_CHECK_CONTEXTJ, &errcode);
+        if (U_FAILURE(errcode)) {
+            return LXB_STATUS_ERROR;
+        }
+
+        size = 4096;
+        dest = lexbor_malloc(size);
+        if (dest == NULL) {
+            goto failed_idna;
+        }
+
+    again:
+
+        pInfo = (UIDNAInfo) UIDNA_INFO_INITIALIZER;
+        errcode = U_ZERO_ERROR;
+
+        length = uidna_nameToASCII_UTF8(idna, (const char *) domain->data,
+                                        domain->length, dest, size, &pInfo,
+                                        &errcode);
+        if (U_FAILURE(errcode)) {
+            if (errcode == U_BUFFER_OVERFLOW_ERROR) {
+                size *= 4;
+
+                tmp = lexbor_realloc(dest, size);
+                if (tmp == NULL) {
+                    goto failed_idna;
+                }
+
+                dest = tmp;
+                goto again;
+            }
+
+            return LXB_STATUS_ERROR;
+        }
+
+        lexbor_str_clean(domain);
+
+        p = lexbor_str_append(domain, mraw, (const lxb_char_t *) dest, length);
+        if (p == NULL) {
+            goto failed_idna;
+        }
+
+        lexbor_free(dest);
+        uidna_close(idna);
+    }
+
+    if (domain->length == 0) {
+        return LXB_STATUS_ERROR_UNEXPECTED_DATA;
+    }
+
+    p = domain->data;
+    end = p + domain->length;
+
+    while (p < end) {
+        c = *p++;
+
+        if (c < 128 && lxb_url_map_forbidden_domain_cp[c] != 0xff) {
+            status = lxb_url_log_append(parser, p - 1,
+                                 LXB_URL_ERROR_TYPE_DOMAIN_INVALID_CODE_POINT);
+            if (status != LXB_STATUS_OK) {
+                return status;
+            }
+
+            return LXB_STATUS_ERROR_UNEXPECTED_DATA;
+        }
+    }
+
+    if (lxb_url_is_ipv4(parser, domain->data, end)) {
+        status = lxb_url_ipv4_parse(parser, domain->data, end, &ipv4);
+
+        (void) lexbor_str_destroy(domain, mraw, false);
+
+        if (status != LXB_STATUS_OK) {
+            return status;
+        }
+
+        host->u.ipv4 = ipv4;
+        host->type = LXB_URL_HOST_TYPE_IPV4;
+
+        return status;
+    }
+
+    host->type = LXB_URL_HOST_TYPE_DOMAIN;
+
+    return LXB_STATUS_OK;
+
+failed_idna:
+
+    if (dest) {
+        lexbor_free(dest);
+    }
+
+    uidna_close(idna);
+
+    return LXB_STATUS_ERROR_MEMORY_ALLOCATION;
+}
+
+lxb_inline lxb_status_t
+lxb_url_ipv4_append(lxb_url_parser_t *parser, const lxb_char_t *data,
+                    const lxb_char_t *end, uint64_t *ipv,
+                    int *out_of, unsigned i)
+{
+    lxb_status_t status;
+    lxb_url_error_type_t type;
+
+    status = lxb_url_ipv4_number_parse(data, end, &ipv[i]);
+
+    if (status != LXB_STATUS_OK) {
+        if (status == LXB_STATUS_ERROR) {
+            type = LXB_URL_ERROR_TYPE_IPV4_NON_NUMERIC_PART;
+            goto failed;
+        }
+
+        status = lxb_url_log_append(parser, data,
+                                    LXB_URL_ERROR_TYPE_IPV4_NON_DECIMAL_PART);
+        if (status != LXB_STATUS_OK) {
+            return status;
+        }
+    }
+
+    if (ipv[i] > 255) {
+        status = lxb_url_log_append(parser, data,
+                                    LXB_URL_ERROR_TYPE_IPV4_OUT_OF_RANGE_PART);
+        if (status != LXB_STATUS_OK) {
+            return status;
+        }
+
+        if (*out_of != -1) {
+            *out_of = (int) i;
+        }
+    }
+
+    return LXB_STATUS_OK;
+
+failed:
+
+    status = lxb_url_log_append(parser, data, type);
+    if (status != LXB_STATUS_OK) {
+        return status;
+    }
+
+    return LXB_STATUS_ERROR_UNEXPECTED_DATA;
+}
+
+static lxb_status_t
+lxb_url_ipv4_parse(lxb_url_parser_t *parser, const lxb_char_t *data,
+                   const lxb_char_t *end, uint32_t *ipv4)
+{
+    int out_of;
+    uint32_t ip;
+    uint64_t parts[5];
+    unsigned i;
+    lxb_status_t status;
+    const lxb_char_t *p, *begin;
+    lxb_url_error_type_t type;
+
+    static const uint64_t st[] = {0, 256, 65536, 16777216, 4294967296, 0};
+
+    if (data >= end) {
+        return LXB_STATUS_ERROR;
+    }
+
+    i = 0;
+    p = data;
+    begin = data;
+    out_of = -1;
+
+    /* Let's make the compiler happy. */
+
+    parts[0] = 0;
+
+    while (p < end) {
+        if (*p == '.') {
+            if (i == 4) {
+                type = LXB_URL_ERROR_TYPE_IPV4_TOO_MANY_PARTS;
+                goto failed;
+            }
+
+            status = lxb_url_ipv4_append(parser, begin, p, parts, &out_of, ++i);
+            if (status != LXB_STATUS_OK) {
+                return status;
+            }
+
+            begin = p + 1;
+        }
+
+        p += 1;
+    }
+
+    if (begin < p) {
+        if (i == 4) {
+            type = LXB_URL_ERROR_TYPE_IPV4_TOO_MANY_PARTS;
+            goto failed;
+        }
+
+        status = lxb_url_ipv4_append(parser, begin, p, parts, &out_of, ++i);
+        if (status != LXB_STATUS_OK) {
+            return status;
+        }
+    }
+    else if (p[-1] == '.') {
+        status = lxb_url_log_append(parser, begin,
+                                    LXB_URL_ERROR_TYPE_IPV4_EMPTY_PART);
+        if (status != LXB_STATUS_OK) {
+            return status;
+        }
+    }
+
+    if (out_of != -1 && out_of != (int) i) {
+        return LXB_STATUS_ERROR_OVERFLOW;
+    }
+
+    if (parts[i] >= st[5 - i]) {
+        return LXB_STATUS_ERROR_OVERFLOW;
+    }
+
+    ip = (uint32_t) parts[i--];
+
+    for (unsigned j = 1; j <= i; j++)  {
+        if (parts[j] > 255) {
+            return LXB_STATUS_ERROR_OVERFLOW;
+        }
+
+        ip += parts[j] * st[3 - (j - 1)];
+    }
+
+    *ipv4 = ip;
+
+    return LXB_STATUS_OK;
+
+failed:
+
+    status = lxb_url_log_append(parser, begin, type);
+    if (status != LXB_STATUS_OK) {
+        return status;
+    }
+
+    return LXB_STATUS_ERROR_UNEXPECTED_DATA;
+}
+
+static lxb_status_t
+lxb_url_ipv4_number_parse(const lxb_char_t *data,
+                          const lxb_char_t *end, uint64_t *num)
+{
+    uint64_t n;
+    unsigned r;
+    lxb_char_t c;
+    lxb_status_t status;
+    const lxb_char_t *str_map;
+
+    if (data >= end) {
+        goto failed;
+    }
+
+    r = 10;
+    str_map = lexbor_str_res_map_num;
+    status = LXB_STATUS_OK;
+
+    if (data + 1 < end) {
+        if (data[0] == '0') {
+            if ((data[1] == 'x' || data[1] == 'X')) {
+                data += 2;
+                r = 16;
+                str_map = lexbor_str_res_map_hex;
+            }
+            else {
+                data += 1;
+                r = 8;
+                str_map = lxb_url_map_num_8;
+            }
+
+            if (data >= end) {
+                *num = 0;
+                return LXB_STATUS_WARNING;
+            }
+
+            status = LXB_STATUS_WARNING;
+        }
+    }
+
+    n = 0;
+
+    while (data < end) {
+        c = *data++;
+
+        if (str_map[c] == 0xff) {
+            goto failed;
+        }
+
+        n = str_map[c] + n * r;
+
+        if (n > UINT32_MAX) {
+            break;
+        }
+    }
+
+    *num = n;
+
+    return status;
+
+failed:
+
+    *num = 0;
+
+    return LXB_STATUS_ERROR;
+}
+
+static bool
+lxb_url_is_ipv4(lxb_url_parser_t *parser, const lxb_char_t *data,
+                const lxb_char_t *end)
+{
+    bool isit, first;
+    uint64_t num;
+    lxb_char_t c;
+    lxb_status_t status;
+    const lxb_char_t *p = end;
+
+    if (data >= end) {
+        return false;
+    }
+
+    isit = true;
+    first = true;
+
+    do {
+        p -= 1;
+        c = *p;
+
+        if (lexbor_str_res_map_hex[c] == 0xff) {
+            if (c == '.') {
+                if (p == end - 1) {
+                    if (first) {
+                        end = p;
+                        first = false;
+                        continue;
+                    }
+
+                    return false;
+                }
+
+                p += 1;
+                break;
+            }
+            else if (c != 'x' && c != 'X') {
+                return false;
+            }
+        }
+
+        if (c < '0' || c > '9') {
+            isit = false;
+        }
+    }
+    while (p > data);
+
+    if (p == end) {
+        return false;
+    }
+
+    if (isit) {
+        return true;
+    }
+
+    status = lxb_url_ipv4_number_parse(p, end, &num);
+
+    return status != LXB_STATUS_ERROR;
+}
+
+static lxb_status_t
+lxb_url_ipv6_parse(lxb_url_parser_t *parser, const lxb_char_t *data,
+                   const lxb_char_t *end, uint16_t *ipv6)
+{
+    size_t i, idx;
+    uint16_t *piece, *compress, num, swap;
+    lxb_char_t c;
+    lxb_status_t status;
+    const lxb_char_t *p;
+    lxb_url_error_type_t err_type;
+
+    piece = ipv6;
+    compress = NULL;
+    p = data;
+
+    if (p >= end) {
+        goto done;
+    }
+
+    if (*p == ':') {
+        if (p + 1 >= end || p[1] != ':') {
+            p = (p + 1 >= end) ? p : &p[1];
+
+            err_type = LXB_URL_ERROR_TYPE_IPV6_INVALID_COMPRESSION;
+            goto failed;
+        }
+
+        p += 2;
+
+        piece += 1;
+        compress = piece;
+    }
+
+    while (p < end) {
+        if (piece == &ipv6[8]) {
+            err_type = LXB_URL_ERROR_TYPE_IPV6_TOO_MANY_PIECES;
+            goto failed;
+        }
+
+        if (*p == ':') {
+            if (compress != NULL) {
+                err_type = LXB_URL_ERROR_TYPE_IPV6_MULTIPLE_COMPRESSION;
+                goto failed;
+            }
+
+            p += 1;
+
+            piece += 1;
+            compress = piece;
+
+            continue;
+        }
+
+        num = 0;
+        i = 0;
+
+        while (i < 4 && p < end) {
+            c = lexbor_str_res_map_hex[*p];
+            if (c == 0xff) {
+                break;
+            }
+
+            num = num << 4 | c;
+
+            p += 1;
+            i += 1;
+        }
+
+        if (p >= end) {
+            *piece++ = num;
+            break;
+        }
+
+        if (*p == '.') {
+            if (i == 0) {
+                err_type = LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_INVALID_CODE_POINT;
+                goto failed;
+            }
+
+            p -= i;
+
+            if (piece > &ipv6[6]) {
+                err_type = LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_TOO_MANY_PIECES;
+                goto failed;
+            }
+
+            status = lxb_url_ipv4_in_ipv6_parse(parser, &p, end, &piece);
+            if (status != LXB_STATUS_OK) {
+                return status;
+            }
+
+            break;
+        }
+
+        if (*p == ':') {
+            p += 1;
+
+            if (p >= end) {
+                err_type = LXB_URL_ERROR_TYPE_IPV6_INVALID_CODE_POINT;
+                goto failed;
+            }
+        }
+        else if (p < end) {
+            err_type = LXB_URL_ERROR_TYPE_IPV6_INVALID_CODE_POINT;
+            goto failed;
+        }
+
+        *piece++ = num;
+    }
+
+done:
+
+    if (compress != NULL) {
+        num = piece - compress;
+        i = 7;
+
+        while (i != 0 && num > 0) {
+            idx = (compress - ipv6) + num - 1;
+            swap = ipv6[idx];
+
+            ipv6[idx] = ipv6[i];
+            ipv6[i] = swap;
+
+            i -= 1;
+            num -= 1;
+        }
+    }
+    else if (piece - ipv6 != 8) {
+        err_type = LXB_URL_ERROR_TYPE_IPV6_TOO_FEW_PIECES;
+        goto failed;
+    }
+
+    return LXB_STATUS_OK;
+
+failed:
+
+    status = lxb_url_log_append(parser, p, err_type);
+    if (status != LXB_STATUS_OK) {
+        return status;
+    }
+
+    return LXB_STATUS_ERROR_UNEXPECTED_DATA;
+}
+
+static lxb_status_t
+lxb_url_ipv4_in_ipv6_parse(lxb_url_parser_t *parser, const lxb_char_t **data,
+                           const lxb_char_t *end, uint16_t **pieces)
+{
+    int16_t ipv4;
+    uint16_t *piece;
+    lxb_char_t c;
+    lxb_status_t status;
+    unsigned int seen;
+    const lxb_char_t *p;
+    lxb_url_error_type_t err_type;
+
+    piece = *pieces;
+    seen = 0;
+    p = *data;
+
+    while (p < end) {
+        ipv4 = -1;
+
+        if (seen > 0) {
+            if (*p == '.' && seen < 4) {
+                p += 1;
+
+                if (p >= end) {
+                    break;
+                }
+            }
+            else {
+                err_type = LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_INVALID_CODE_POINT;
+                goto failed;
+            }
+        }
+
+        do {
+            c = *p;
+
+            if (c < '0' || c > '9') {
+                if (ipv4 == -1) {
+                    err_type = LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_INVALID_CODE_POINT;
+                    goto failed;
+                }
+
+                break;
+            }
+
+            if (ipv4 == -1) {
+                ipv4 = lexbor_str_res_map_num[c];
+            }
+            else if (ipv4 == 0) {
+                err_type = LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_INVALID_CODE_POINT;
+                goto failed;
+            }
+            else {
+                ipv4 = ipv4 * 10 + lexbor_str_res_map_num[c];
+            }
+
+            if (ipv4 > 255) {
+                err_type = LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_OUT_OF_RANGE_PART;
+                goto failed;
+            }
+
+            p += 1;
+        }
+        while (p < end);
+
+        *piece = *piece * 0x100 + ipv4;
+
+        seen += 1;
+
+        if (seen == 2 || seen == 4) {
+            piece += 1;
+        }
+    }
+
+    if (seen != 4) {
+        err_type = LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_TOO_FEW_PARTS;
+        goto failed;
+    }
+
+    *pieces = piece;
+    *data = p;
+
+    return LXB_STATUS_OK;
+
+failed:
+
+    status = lxb_url_log_append(parser, p, err_type);
+    if (status != LXB_STATUS_OK) {
+        return status;
+    }
+
+    return LXB_STATUS_ERROR_UNEXPECTED_DATA;
+}
+
+static lxb_status_t
+lxb_url_opaque_host_parse(lxb_url_parser_t *parser, const lxb_char_t *data,
+                          const lxb_char_t *end, lxb_url_host_t *host,
+                          lexbor_mraw_t *mraw)
+{
+    lxb_char_t c;
+    lxb_status_t status;
+    const lxb_char_t *p;
+
+    p = data;
+
+    while (p < end) {
+        c = *p++;
+
+        if (c < 128 && lxb_url_map_forbidden_host_cp[c] != 0xff) {
+            status = lxb_url_log_append(parser, p - 1,
+                                   LXB_URL_ERROR_TYPE_HOST_INVALID_CODE_POINT);
+            if (status != LXB_STATUS_OK) {
+                return status;
+            }
+
+            return LXB_STATUS_ERROR_UNEXPECTED_DATA;
+        }
+    }
+
+    host->type = LXB_URL_HOST_TYPE_OPAQUE;
+
+    return lxb_url_percent_encode_after_utf_8(data, end, &host->u.opaque, mraw,
+                                              LXB_URL_MAP_C0, false);
+}
+
+static lxb_status_t
+lxb_url_percent_decode(const lxb_char_t *data, const lxb_char_t *end,
+                       lexbor_str_t *str, lexbor_mraw_t *mraw,
+                       lxb_url_host_opt_t *opt)
+{
+    lxb_char_t c, *dp;
+    lxb_status_t status;
+    const lxb_char_t *p;
+
+    status = lxb_url_str_init(str, mraw, (end - data) + 1);
+    if (status != LXB_STATUS_OK) {
+        return status;
+    }
+
+    p = data;
+    dp = str->data;
+
+    while (p < end) {
+        c = *p++;
+
+        if (c != '%') {
+            *dp++ = c;
+            continue;
+        }
+
+        if (p + 2 <= end && lexbor_str_res_map_hex[p[0]] != 0xff
+            && lexbor_str_res_map_hex[p[1]] != 0xff)
+        {
+            c = lexbor_str_res_map_hex[p[0]] << 4 | lexbor_str_res_map_hex[p[1]];
+            p += 2;
+
+            if (c >= 0x80) {
+                *opt |= LXB_URL_HOST_OPT_IDNA;
+            }
+        }
+
+        *dp++ = c;
+    }
+
+    *dp = '\0';
+    str->length = dp - str->data;
+
+    return LXB_STATUS_OK;
+}
+
+void
+lxb_url_erase(lxb_url_t *url)
+{
+    if (url == NULL) {
+        return;
+    }
+
+    if (url->scheme.name.data != NULL) {
+        lexbor_str_destroy(&url->scheme.name, url->mraw, false);
+    }
+
+    switch (url->host.type) {
+        case LXB_URL_HOST_TYPE_DOMAIN:
+        case LXB_URL_HOST_TYPE_OPAQUE:
+            lexbor_str_destroy(&url->host.u.domain, url->mraw, false);
+            break;
+
+        default:
+            break;
+    }
+
+    if (url->username.data != NULL) {
+        lexbor_str_destroy(&url->username, url->mraw, false);
+    }
+
+    if (url->password.data != NULL) {
+        lexbor_str_destroy(&url->password, url->mraw, false);
+    }
+
+    if (url->path.str.data != NULL) {
+        lexbor_str_destroy(&url->path.str, url->mraw, false);
+    }
+
+    if (url->query.data != NULL) {
+        lexbor_str_destroy(&url->query, url->mraw, false);
+    }
+
+    if (url->fragment.data != NULL) {
+        lexbor_str_destroy(&url->fragment, url->mraw, false);
+    }
+}
+
+lxb_url_t *
+lxb_url_destroy(lxb_url_t *url)
+{
+    if (url == NULL) {
+        return NULL;
+    }
+
+    lxb_url_erase(url);
+
+    return lexbor_mraw_free(url->mraw, url);
+}
+
+void
+lxb_url_memory_destroy(lxb_url_t *url)
+{
+    (void) lexbor_mraw_destroy(url->mraw, true);
+}
+
+static const lxb_char_t *
+lxb_url_path_part_by_index(const lxb_url_t *url, size_t index,
+                           size_t *out_length)
+{
+    size_t i, length, begin;
+    const lxb_char_t *data;
+
+    data = url->path.str.data;
+    length = url->path.str.length;
+
+    i = 0;
+    begin = 0;
+
+    while (i < length) {
+        if (data[i] == '/') {
+            if (index == 0) {
+                *out_length = i - begin;
+                return &data[begin];
+            }
+
+            index -= 1;
+            begin = i + 1;
+        }
+
+        i += 1;
+    }
+
+    *out_length = 0;
+
+    return NULL;
+}
+
+lxb_status_t
+lxb_url_api_href_set(lxb_url_t *url, lxb_url_parser_t *parser,
+                     const lxb_char_t *href, size_t length)
+{
+    lxb_status_t status;
+    lexbor_mraw_t *origin_mraw;
+    lxb_url_parser_t self_parser;
+    const lxb_char_t tmp[1] = "";
+
+    if (href == NULL) {
+        href = tmp;
+        length = 0;
+    }
+
+    if (parser == NULL) {
+        parser = &self_parser;
+
+        parser->log = NULL;
+    }
+
+    origin_mraw = parser->mraw;
+    parser->mraw = url->mraw;
+
+    status = lxb_url_parse_basic_h(parser, NULL, NULL, href, length,
+                                   LXB_URL_STATE__UNDEF);
+
+    parser->mraw = origin_mraw;
+
+    if (status != LXB_STATUS_OK) {
+        parser->url = lxb_url_destroy(parser->url);
+    }
+    else {
+        lxb_url_erase(url);
+
+        *url = *parser->url;
+    }
+
+    if (parser == &self_parser) {
+        lxb_url_parser_destroy(parser, false);
+    }
+
+    return status;
+}
+
+lxb_status_t
+lxb_url_api_protocol_set(lxb_url_t *url, lxb_url_parser_t *parser,
+                         const lxb_char_t *protocol, size_t length)
+{
+    lxb_status_t status;
+    lxb_url_parser_t self_parser;
+    const lxb_char_t tmp[1] = "";
+
+    if (protocol == NULL) {
+        protocol = tmp;
+        length = 0;
+    }
+
+    if (parser == NULL) {
+        parser = &self_parser;
+
+        parser->log = NULL;
+    }
+
+    status = lxb_url_parse_basic_h(parser, url, NULL, protocol, length,
+                                   LXB_URL_STATE_SCHEME_START_STATE);
+
+    if (parser == &self_parser) {
+        lxb_url_parser_destroy(parser, false);
+    }
+
+    return status;
+}
+
+lxb_status_t
+lxb_url_api_username_set(lxb_url_t *url,
+                         const lxb_char_t *username, size_t length)
+{
+    if (lxb_url_cannot_have_user_pass_port(url)) {
+        return LXB_STATUS_OK;
+    }
+
+    url->username.length = 0;
+
+    if (username == NULL || length == 0) {
+        lexbor_str_destroy(&url->username, url->mraw, false);
+        return LXB_STATUS_OK;
+    }
+
+    return lxb_url_percent_encode_after_utf_8(username, username + length,
+                                              &url->username, url->mraw,
+                                              LXB_URL_MAP_USERINFO, false);
+}
+
+lxb_status_t
+lxb_url_api_password_set(lxb_url_t *url,
+                         const lxb_char_t *password, size_t length)
+{
+    if (lxb_url_cannot_have_user_pass_port(url)) {
+        return LXB_STATUS_OK;
+    }
+
+    url->password.length = 0;
+
+    if (password == NULL || length == 0) {
+        lexbor_str_destroy(&url->password, url->mraw, false);
+        return LXB_STATUS_OK;
+    }
+
+    return lxb_url_percent_encode_after_utf_8(password, password + length,
+                                              &url->password, url->mraw,
+                                              LXB_URL_MAP_USERINFO, false);
+}
+
+lxb_status_t
+lxb_url_api_host_set(lxb_url_t *url, lxb_url_parser_t *parser,
+                     const lxb_char_t *host, size_t length)
+{
+    return lxb_url_host_set_h(url, parser, host, length,
+                              LXB_URL_STATE_HOST_STATE);
+}
+
+lxb_status_t
+lxb_url_api_hostname_set(lxb_url_t *url, lxb_url_parser_t *parser,
+                         const lxb_char_t *hostname, size_t length)
+{
+    return lxb_url_host_set_h(url, parser, hostname, length,
+                              LXB_URL_STATE_HOSTNAME_STATE);
+}
+
+static lxb_status_t
+lxb_url_host_set_h(lxb_url_t *url, lxb_url_parser_t *parser,
+                   const lxb_char_t *host, size_t length,
+                   lxb_url_state_t override_state)
+{
+    lxb_status_t status;
+    lxb_url_host_t old;
+    lxb_url_parser_t self_parser;
+    const lxb_char_t tmp[1] = "";
+
+    if (url->host.type == LXB_URL_HOST_TYPE_OPAQUE) {
+        return LXB_STATUS_OK;
+    }
+
+    if (host == NULL) {
+        host = tmp;
+        length = 0;
+    }
+
+    if (parser == NULL) {
+        parser = &self_parser;
+
+        parser->log = NULL;
+    }
+
+    old = url->host;
+
+    memset(&url->host, 0x00, sizeof(lxb_url_host_t));
+
+    status = lxb_url_parse_basic_h(parser, url, NULL, host, length,
+                                   override_state);
+
+    if (parser == &self_parser) {
+        lxb_url_parser_destroy(parser, false);
+    }
+
+    if (status != LXB_STATUS_OK) {
+        lxb_url_host_destroy(&url->host, url->mraw);
+        url->host = old;
+    }
+    else {
+        if (override_state == LXB_URL_STATE_HOSTNAME_STATE
+            && url->host.type == LXB_URL_HOST_TYPE__UNDEF)
+        {
+            url->host = old;
+        }
+        else {
+            lxb_url_host_destroy(&old, url->mraw);
+        }
+    }
+
+    return status;
+}
+
+lxb_status_t
+lxb_url_api_port_set(lxb_url_t *url, lxb_url_parser_t *parser,
+                     const lxb_char_t *port, size_t length)
+{
+    lxb_status_t status;
+    lxb_url_parser_t self_parser;
+
+    if (lxb_url_cannot_have_user_pass_port(url)) {
+        return LXB_STATUS_OK;
+    }
+
+    if (port == NULL || length == 0) {
+        url->port = 0;
+        url->has_port = false;
+
+        return LXB_STATUS_OK;
+    }
+
+    if (parser == NULL) {
+        parser = &self_parser;
+
+        parser->log = NULL;
+    }
+
+    status = lxb_url_parse_basic_h(parser, url, NULL, port, length,
+                                   LXB_URL_STATE_PORT_STATE);
+
+    if (parser == &self_parser) {
+        lxb_url_parser_destroy(parser, false);
+    }
+
+    return status;
+}
+
+lxb_status_t
+lxb_url_api_pathname_set(lxb_url_t *url, lxb_url_parser_t *parser,
+                         const lxb_char_t *pathname, size_t length)
+{
+    lxb_status_t status;
+    lxb_url_parser_t self_parser;
+    const lxb_char_t tmp[1] = "";
+
+    if (url->path.opaque) {
+        return LXB_STATUS_OK;
+    }
+
+    if (pathname == NULL) {
+        pathname = tmp;
+        length = 0;
+    }
+
+    if (parser == NULL) {
+        parser = &self_parser;
+
+        parser->log = NULL;
+    }
+
+    url->path.length = 0;
+    url->path.str.length = 0;
+
+    status = lxb_url_parse_basic_h(parser, url, NULL, pathname, length,
+                                   LXB_URL_STATE_PATH_START_STATE);
+
+    if (parser == &self_parser) {
+        lxb_url_parser_destroy(parser, false);
+    }
+
+    return status;
+}
+
+lxb_status_t
+lxb_url_api_search_set(lxb_url_t *url, lxb_url_parser_t *parser,
+                       const lxb_char_t *search, size_t length)
+{
+    lxb_status_t status;
+    lxb_url_parser_t self_parser;
+
+    lexbor_str_destroy(&url->query, url->mraw, false);
+
+    url->query.length = 0;
+
+    if (search == NULL || length == 0) {
+        lexbor_str_destroy(&url->query, url->mraw, false);
+        return LXB_STATUS_OK;
+    }
+
+    if (*search == '?') {
+        search += 1;
+        length -= 1;
+    }
+
+    if (parser == NULL) {
+        parser = &self_parser;
+
+        parser->log = NULL;
+    }
+
+    status = lxb_url_parse_basic_h(parser, url, NULL, search, length,
+                                   LXB_URL_STATE_QUERY_STATE);
+
+    if (parser == &self_parser) {
+        lxb_url_parser_destroy(parser, false);
+    }
+
+    return status;
+}
+
+lxb_status_t
+lxb_url_api_hash_set(lxb_url_t *url, lxb_url_parser_t *parser,
+                     const lxb_char_t *hash, size_t length)
+{
+    lxb_status_t status;
+    lxb_url_parser_t self_parser;
+
+    lexbor_str_destroy(&url->fragment, url->mraw, false);
+
+    url->fragment.length = 0;
+
+    if (hash == NULL || length == 0) {
+        lexbor_str_destroy(&url->fragment, url->mraw, false);
+        return LXB_STATUS_OK;
+    }
+
+    if (*hash == '#') {
+        hash += 1;
+        length -= 1;
+    }
+
+    if (parser == NULL) {
+        parser = &self_parser;
+
+        parser->log = NULL;
+    }
+
+    status = lxb_url_parse_basic_h(parser, url, NULL, hash, length,
+                                   LXB_URL_STATE_FRAGMENT_STATE);
+
+    if (parser == &self_parser) {
+        lxb_url_parser_destroy(parser, false);
+    }
+
+    return status;
+}
+
+size_t
+lxb_url_length(const lxb_url_t *url, bool exclude_fragment)
+{
+    size_t length;
+    const lexbor_str_t *str;
+
+    static const lexbor_str_t colon_str = lexbor_str(":");
+    static const lexbor_str_t dsol_str = lexbor_str("//");
+    static const lexbor_str_t at_str = lexbor_str("@");
+    static const lexbor_str_t dt_str = lexbor_str("/.");
+    static const lexbor_str_t qm_str = lexbor_str("?");
+    static const lexbor_str_t hs_str = lexbor_str("#");
+
+    /* Scheme. */
+
+    length = lxb_url_scheme_length(url);
+    length += colon_str.length;
+
+    /* Host. */
+
+    if (url->host.type != LXB_URL_HOST_TYPE__UNDEF) {
+        length += dsol_str.length;
+
+        if (lxb_url_includes_credentials(url)) {
+            length += url->username.length;
+
+            if (url->password.length != 0) {
+                length += colon_str.length;
+                length += url->password.length;
+            }
+
+            length += at_str.length;
+        }
+
+        length += lxb_url_host_length(&url->host);
+
+        if (url->has_port) {
+            length += colon_str.length;
+            length += lxb_url_port_length(url);
+        }
+    }
+    else if (!url->path.opaque && url->path.str.length > 1) {
+        str = &url->path.str;
+
+        if (str->data[0] == '/' && str->data[1] == '/') {
+            length += dt_str.length;
+        }
+    }
+
+    length += lxb_url_path_length(&url->path);
+
+    if (url->query.data != NULL) {
+        length += qm_str.length;
+        length += lxb_url_query_length(url);
+    }
+
+    if (!exclude_fragment && url->fragment.data != NULL) {
+        length += hs_str.length;
+        length += lxb_url_fragment_length(url);
+    }
+
+    return length;
+}
+
+size_t
+lxb_url_scheme_length(const lxb_url_t *url)
+{
+    return url->scheme.name.length;
+}
+
+size_t
+lxb_url_username_length(const lxb_url_t *url)
+{
+    return url->username.length;
+}
+
+size_t
+lxb_url_password_length(const lxb_url_t *url)
+{
+    return url->password.length;
+}
+
+size_t
+lxb_url_host_length(const lxb_url_host_t *host)
+{
+    switch (host->type) {
+        case LXB_URL_HOST_TYPE_DOMAIN:
+        case LXB_URL_HOST_TYPE_OPAQUE:
+            return host->u.domain.length;
+
+        case LXB_URL_HOST_TYPE_IPV4:
+            return sizeof("000.000.000.000") - 1;
+
+        case LXB_URL_HOST_TYPE_IPV6:
+            return sizeof("[FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]") - 1;
+
+        default:
+            break;
+    }
+
+    return 0;
+}
+
+static lxb_status_t
+lxb_url_idna_to_unicode(const lexbor_str_t *str,
+                        lexbor_serialize_cb_f cb, void *ctx)
+{
+    lxb_status_t status;
+    char *dest, *tmp;
+    UIDNA *idna;
+    UIDNAInfo pInfo;
+    UErrorCode errcode;
+    size_t size;
+    uint32_t length;
+
+    errcode = U_ZERO_ERROR;
+
+    idna = uidna_openUTS46(UIDNA_CHECK_BIDI|UIDNA_CHECK_CONTEXTJ, &errcode);
+    if (U_FAILURE(errcode)) {
+        return LXB_STATUS_ERROR;
+    }
+
+    size = 4096;
+    dest = lexbor_malloc(size);
+    if (dest == NULL) {
+        goto failed_idna;
+    }
+
+again:
+
+    pInfo = (UIDNAInfo) UIDNA_INFO_INITIALIZER;
+    errcode = U_ZERO_ERROR;
+
+    length = uidna_nameToUnicodeUTF8(idna, (const char *) str->data, str->length,
+                                     dest, size, &pInfo, &errcode);
+    if (U_FAILURE(errcode)) {
+        if (errcode == U_BUFFER_OVERFLOW_ERROR) {
+            size *= 4;
+
+            tmp = lexbor_realloc(dest, size);
+            if (tmp == NULL) {
+                goto failed_idna;
+            }
+
+            dest = tmp;
+            goto again;
+        }
+
+        return LXB_STATUS_ERROR;
+    }
+
+    status = cb((const lxb_char_t *) dest, length, ctx);
+
+    lexbor_free(dest);
+    uidna_close(idna);
+
+    return status;
+
+failed_idna:
+
+    if (dest != NULL)
+        lexbor_free(dest);
+
+    uidna_close(idna);
+
+    return LXB_STATUS_OK;
+}
+
+size_t
+lxb_url_host_unicode_length(const lxb_url_host_t *host)
+{
+    size_t length;
+    lxb_status_t status;
+
+    if (host->type != LXB_URL_HOST_TYPE_DOMAIN
+        && host->type != LXB_URL_HOST_TYPE_OPAQUE)
+    {
+        return lxb_url_host_length(host);
+    }
+
+    length = 0;
+    status = lxb_url_idna_to_unicode(&host->u.domain,
+                                     lxb_url_callback_length, &length);
+
+    return (status == LXB_STATUS_OK) ? length : 0;
+}
+
+static lxb_status_t
+lxb_url_callback_length(const lxb_char_t *data, size_t len, void *ctx)
+{
+	size_t *length = ctx;
+
+    *length += len;
+
+	return LXB_STATUS_OK;
+}
+
+size_t
+lxb_url_port_length(const lxb_url_t *url)
+{
+    return (url->has_port) ? floor(log10(url->port)) + 1 : 0;
+}
+
+size_t
+lxb_url_path_length(const lxb_url_path_t *path)
+{
+    return path->str.length;
+}
+
+size_t
+lxb_url_query_length(const lxb_url_t *url)
+{
+    return url->query.length;
+}
+
+size_t
+lxb_url_fragment_length(const lxb_url_t *url)
+{
+    return url->fragment.length;
+}
+
+lxb_status_t
+lxb_url_serialize(const lxb_url_t *url, lexbor_serialize_cb_f cb, void *ctx,
+                  bool exclude_fragment)
+{
+    lxb_status_t status;
+    const lexbor_str_t *str;
+    lxb_char_t *p;
+    lxb_char_t buf[LXB_URL_BUFFER_NUM_SIZE];
+
+    static const lexbor_str_t colon_str = lexbor_str(":");
+    static const lexbor_str_t dsol_str = lexbor_str("//");
+    static const lexbor_str_t at_str = lexbor_str("@");
+    static const lexbor_str_t dt_str = lexbor_str("/.");
+    static const lexbor_str_t qm_str = lexbor_str("?");
+    static const lexbor_str_t hs_str = lexbor_str("#");
+
+    /* Scheme. */
+
+    str = &url->scheme.name;
+
+    lexbor_serialize_write(cb, str->data, str->length, ctx, status);
+    lexbor_serialize_write(cb, colon_str.data, colon_str.length, ctx, status);
+
+    /* Host. */
+
+    if (url->host.type != LXB_URL_HOST_TYPE__UNDEF) {
+        lexbor_serialize_write(cb, dsol_str.data, dsol_str.length, ctx, status);
+
+        if (lxb_url_includes_credentials(url)) {
+            lexbor_serialize_write(cb, url->username.data, url->username.length,
+                                   ctx, status);
+
+            if (url->password.length != 0) {
+                lexbor_serialize_write(cb, colon_str.data, colon_str.length,
+                                       ctx, status);
+                lexbor_serialize_write(cb, url->password.data,
+                                       url->password.length, ctx, status);
+            }
+
+            lexbor_serialize_write(cb, at_str.data, at_str.length, ctx, status);
+        }
+
+        status = lxb_url_serialize_host(&url->host, cb, ctx);
+        if (status != LXB_STATUS_OK) {
+            return status;
+        }
+
+        if (url->has_port) {
+            lexbor_serialize_write(cb, colon_str.data, colon_str.length,
+                                   ctx, status);
+
+            p = buf + lexbor_conv_int64_to_data((int64_t) url->port,
+                                                buf, LXB_URL_BUFFER_NUM_SIZE);
+
+            lexbor_serialize_write(cb, buf, p - buf, ctx, status);
+        }
+    }
+    else if (!url->path.opaque && url->path.str.length > 1) {
+        str = &url->path.str;
+
+        if (str->data[0] == '/' && str->data[1] == '/') {
+            lexbor_serialize_write(cb, dt_str.data, dt_str.length, ctx, status);
+        }
+    }
+
+    status = lxb_url_serialize_path(&url->path, cb, ctx);
+    if (status != LXB_STATUS_OK) {
+        return status;
+    }
+
+    if (url->query.data != NULL) {
+        lexbor_serialize_write(cb, qm_str.data, qm_str.length,
+                               ctx, status);
+        lexbor_serialize_write(cb, url->query.data, url->query.length,
+                               ctx, status);
+    }
+
+    if (!exclude_fragment && url->fragment.data != NULL) {
+        lexbor_serialize_write(cb, hs_str.data, hs_str.length,
+                               ctx, status);
+        lexbor_serialize_write(cb, url->fragment.data, url->fragment.length,
+                               ctx, status);
+    }
+
+    return LXB_STATUS_OK;
+}
+
+lxb_status_t
+lxb_url_serialize_scheme(const lxb_url_t *url,
+                         lexbor_serialize_cb_f cb, void *ctx)
+{
+    const lexbor_str_t *str = &url->scheme.name;
+
+    return cb(str->data, str->length, ctx);
+}
+
+lxb_status_t
+lxb_url_serialize_username(const lxb_url_t *url,
+                           lexbor_serialize_cb_f cb, void *ctx)
+{
+    if (lxb_url_includes_credentials(url)) {
+        return cb(url->username.data, url->username.length, ctx);
+    }
+
+    return LXB_STATUS_OK;
+}
+
+lxb_status_t
+lxb_url_serialize_password(const lxb_url_t *url,
+                           lexbor_serialize_cb_f cb, void *ctx)
+{
+    if (lxb_url_includes_credentials(url)) {
+        return cb(url->password.data, url->password.length, ctx);
+    }
+
+    return LXB_STATUS_OK;
+}
+
+lxb_status_t
+lxb_url_serialize_host(const lxb_url_host_t *host,
+                       lexbor_serialize_cb_f cb, void *ctx)
+{
+    lxb_status_t status;
+
+    static const lexbor_str_t ob_str = lexbor_str("[");
+    static const lexbor_str_t cb_str = lexbor_str("]");
+
+    switch (host->type) {
+        case LXB_URL_HOST_TYPE_DOMAIN:
+        case LXB_URL_HOST_TYPE_OPAQUE:
+            return cb(host->u.domain.data, host->u.domain.length, ctx);
+
+        case LXB_URL_HOST_TYPE_IPV4:
+            return lxb_url_serialize_host_ipv4(host->u.ipv4, cb, ctx);
+
+        case LXB_URL_HOST_TYPE_IPV6:
+            lexbor_serialize_write(cb, ob_str.data, ob_str.length,
+                                   ctx, status);
+
+            status = lxb_url_serialize_host_ipv6(host->u.ipv6, cb, ctx);
+            if (status != LXB_STATUS_OK) {
+                return status;
+            }
+
+            return cb(cb_str.data, cb_str.length, ctx);
+
+        default:
+            break;
+    }
+
+    return LXB_STATUS_OK;
+}
+
+lxb_status_t
+lxb_url_serialize_host_unicode(const lxb_url_host_t *host,
+                               lexbor_serialize_cb_f cb, void *ctx)
+{
+    if (host->type != LXB_URL_HOST_TYPE_DOMAIN
+        && host->type != LXB_URL_HOST_TYPE_OPAQUE)
+    {
+        return lxb_url_serialize_host(host, cb, ctx);
+    }
+
+    return lxb_url_idna_to_unicode(&host->u.domain, cb, ctx);
+}
+
+lxb_status_t
+lxb_url_serialize_host_ipv4(uint32_t ipv4,
+                            lexbor_serialize_cb_f cb, void *ctx)
+{
+    lxb_char_t *p, *end;
+    lxb_char_t buf[LXB_URL_BUFFER_NUM_SIZE];
+
+    p = buf;
+    end = p + LXB_URL_BUFFER_NUM_SIZE;
+
+    p += lexbor_conv_int64_to_data((uint8_t) (ipv4 >> 24), p, end - p);
+    *p++ = '.';
+    p += lexbor_conv_int64_to_data((uint8_t) (ipv4 >> 16), p, end - p);
+    *p++ = '.';
+    p += lexbor_conv_int64_to_data((uint8_t) (ipv4 >> 8), p, end - p);
+    *p++ = '.';
+    p += lexbor_conv_int64_to_data((uint8_t) (ipv4), p, end - p);
+
+
+    /* By specification. */
+    /*
+    uint32_t n;
+
+    for (size_t i = 0; i < 4; i++) {
+        n = ipv4 % 256;
+
+        p += lexbor_conv_int64_to_data((int64_t) n, p, end - p);
+
+        if (i != 3) {
+            *p++ = '.';
+        }
+
+        ipv4 = (uint32_t) floor((double) ipv4 / 256.0f);
+    }
+     */
+
+    *p = '\0';
+
+    return cb(buf, p - buf, ctx);
+}
+
+lxb_status_t
+lxb_url_serialize_host_ipv6(const uint16_t *ipv6,
+                            lexbor_serialize_cb_f cb, void *ctx)
+{
+    bool ignore;
+    size_t i, count, tmp_count;
+    const uint16_t *compress, *tmp_compress;
+    lxb_char_t *p, *end;
+    lxb_char_t buf[LXB_URL_BUFFER_NUM_SIZE];
+
+    p = buf;
+    end = p + LXB_URL_BUFFER_NUM_SIZE;
+
+    count = 0;
+    tmp_count = (size_t) (ipv6[0] == 0);
+    compress = NULL;
+    tmp_compress = ipv6;
+
+    for (i = 1; i < 8; i++) {
+        if (ipv6[i] == 0) {
+            if (ipv6[i - 1] == 0) {
+                tmp_count += 1;
+            }
+            else {
+                tmp_count = 1;
+                tmp_compress = &ipv6[i];
+            }
+        }
+        else if (tmp_count > count) {
+            compress = tmp_compress;
+            count = tmp_count;
+        }
+    }
+
+    if (tmp_count > count) {
+        compress = tmp_compress;
+        count = tmp_count;
+    }
+
+    if (compress == &ipv6[1] && ipv6[0] == 0) {
+        compress = ipv6;
+    }
+
+    if (count < 2) {
+        compress = NULL;
+    }
+
+    ignore = false;
+
+    for (i = 0; i < 8; i++) {
+        if (ignore) {
+            if (ipv6[i] == 0) {
+                continue;
+            }
+
+            ignore = false;
+        }
+
+        if (compress == &ipv6[i]) {
+            *p++ = ':';
+
+            if (i == 0) {
+                *p++ = ':';
+            }
+
+            i += count - 1;
+            ignore = true;
+
+            continue;
+        }
+
+        p += lexbor_conv_dec_to_hex(ipv6[i], p, end - p);
+
+        if (i != 7) {
+            *p++ = ':';
+        }
+    }
+
+    *p = '\0';
+
+    return cb(buf, p - buf, ctx);
+}
+
+lxb_status_t
+lxb_url_serialize_port(const lxb_url_t *url,
+                       lexbor_serialize_cb_f cb, void *ctx)
+{
+    lxb_char_t *p;
+    lxb_char_t buf[LXB_URL_BUFFER_NUM_SIZE];
+
+    if (url->has_port) {
+        p = buf + lexbor_conv_int64_to_data((int64_t) url->port,
+                                            buf, LXB_URL_BUFFER_NUM_SIZE);
+        return cb(buf, p - buf, ctx);
+    }
+
+    return LXB_STATUS_OK;
+}
+
+lxb_status_t
+lxb_url_serialize_path(const lxb_url_path_t *path,
+                       lexbor_serialize_cb_f cb, void *ctx)
+{
+    return cb(path->str.data, path->str.length, ctx);
+}
+
+lxb_status_t
+lxb_url_serialize_query(const lxb_url_t *url,
+                        lexbor_serialize_cb_f cb, void *ctx)
+{
+    if (url->query.data != NULL) {
+        return cb(url->query.data, url->query.length, ctx);
+    }
+
+    return LXB_STATUS_OK;
+}
+
+lxb_status_t
+lxb_url_serialize_fragment(const lxb_url_t *url,
+                           lexbor_serialize_cb_f cb, void *ctx)
+{
+    if (url->query.data != NULL) {
+        return cb(url->fragment.data, url->fragment.length, ctx);
+    }
+
+    return LXB_STATUS_OK;
+}
+
+lxb_url_t *
+lxb_url_clone(lexbor_mraw_t *mraw, lxb_url_t *url)
+{
+    lxb_status_t status;
+    lxb_url_t *new_url;
+
+    new_url = lexbor_mraw_calloc(mraw, sizeof(lxb_url_t));
+    if (new_url == NULL) {
+        return NULL;
+    }
+
+    new_url->mraw = mraw;
+
+    status = lxb_url_scheme_copy(&url->scheme, &new_url->scheme, mraw);
+    if (status != LXB_STATUS_OK) {
+        goto failed;
+    }
+
+    status = lxb_url_username_copy(&url->username, &new_url->username, mraw);
+    if (status != LXB_STATUS_OK) {
+        goto failed;
+    }
+
+    status = lxb_url_password_copy(&url->password, &new_url->password, mraw);
+    if (status != LXB_STATUS_OK) {
+        goto failed;
+    }
+
+    status = lxb_url_host_copy(&url->host, &new_url->host, mraw);
+    if (status != LXB_STATUS_OK) {
+        goto failed;
+    }
+
+    new_url->port = url->port;
+    new_url->has_port = url->has_port;
+
+    status = lxb_url_path_copy(url, new_url);
+    if (status != LXB_STATUS_OK) {
+        goto failed;
+    }
+
+    status = lxb_url_query_copy(&url->query, &new_url->query, mraw);
+    if (status != LXB_STATUS_OK) {
+        goto failed;
+    }
+
+    status = lxb_url_str_copy(&url->fragment, &new_url->fragment, mraw);
+    if (status != LXB_STATUS_OK) {
+        goto failed;
+    }
+
+    return new_url;
+
+failed:
+
+    lxb_url_destroy(new_url);
+
+    return NULL;
+}
+
+/*
+ * Code from lexbor/encoding/decode.c
+ */
+static lxb_codepoint_t
+lxb_url_decode_valid_utf_8_single(const lxb_char_t **data, const lxb_char_t *end)
+{
+    lxb_codepoint_t cp;
+    const lxb_char_t *p = *data;
+
+    if (*p < 0x80){
+        /* 0xxxxxxx */
+
+        if (end - p < 1) {
+            *data = end;
+            return LXB_URL_DECODE_ERROR;
+        }
+
+        cp = (lxb_codepoint_t) *p;
+
+        (*data) += 1;
+    }
+    else if ((*p & 0xe0) == 0xc0) {
+        /* 110xxxxx 10xxxxxx */
+
+        if (end - p < 2) {
+            *data = end;
+            return LXB_URL_DECODE_ERROR;
+        }
+
+        cp  = (p[0] ^ (0xC0 & p[0])) << 6;
+        cp |= (p[1] ^ (0x80 & p[1]));
+
+        (*data) += 2;
+    }
+    else if ((*p & 0xf0) == 0xe0) {
+        /* 1110xxxx 10xxxxxx 10xxxxxx */
+
+        if (end - p < 3) {
+            *data = end;
+            return LXB_URL_DECODE_ERROR;
+        }
+
+        cp  = (p[0] ^ (0xE0 & p[0])) << 12;
+        cp |= (p[1] ^ (0x80 & p[1])) << 6;
+        cp |= (p[2] ^ (0x80 & p[2]));
+
+        (*data) += 3;
+    }
+    else if ((*p & 0xf8) == 0xf0) {
+        /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
+
+        if (end - p < 4) {
+            *data = end;
+            return LXB_URL_DECODE_ERROR;
+        }
+
+        cp  = (p[0] ^ (0xF0 & p[0])) << 18;
+        cp |= (p[1] ^ (0x80 & p[1])) << 12;
+        cp |= (p[2] ^ (0x80 & p[2])) << 6;
+        cp |= (p[3] ^ (0x80 & p[3]));
+
+        (*data) += 4;
+    }
+    else {
+        (*data)++;
+
+        return LXB_URL_DECODE_ERROR;
+    }
+
+    return cp;
+}
+
+static lxb_codepoint_t
+lxb_url_decode_valid_utf_8_single_reverse(const lxb_char_t **end,
+                                          const lxb_char_t *begin)
+{
+    lxb_codepoint_t cp;
+    const lxb_char_t *p = *end;
+
+    while (p > begin) {
+        p -= 1;
+
+        if (*p < 0x80){
+            cp = (lxb_codepoint_t) *p;
+
+            (*end) = p;
+            return cp;
+        }
+        else if ((*p & 0xe0) == 0xc0) {
+            /* 110xxxxx 10xxxxxx */
+
+            if (*end - p < 2) {
+                *end = p;
+                return LXB_URL_DECODE_ERROR;
+            }
+
+            cp  = (p[0] ^ (0xC0 & p[0])) << 6;
+            cp |= (p[1] ^ (0x80 & p[1]));
+
+            (*end) = p;
+            return cp;
+        }
+        else if ((*p & 0xf0) == 0xe0) {
+            /* 1110xxxx 10xxxxxx 10xxxxxx */
+
+            if (*end - p < 3) {
+                *end = p;
+                return LXB_URL_DECODE_ERROR;
+            }
+
+            cp  = (p[0] ^ (0xE0 & p[0])) << 12;
+            cp |= (p[1] ^ (0x80 & p[1])) << 6;
+            cp |= (p[2] ^ (0x80 & p[2]));
+
+            (*end) = p;
+            return cp;
+        }
+        else if ((*p & 0xf8) == 0xf0) {
+            /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
+
+            if (*end - p < 4) {
+                *end = p;
+                return LXB_URL_DECODE_ERROR;
+            }
+
+            cp  = (p[0] ^ (0xF0 & p[0])) << 18;
+            cp |= (p[1] ^ (0x80 & p[1])) << 12;
+            cp |= (p[2] ^ (0x80 & p[2])) << 6;
+            cp |= (p[3] ^ (0x80 & p[3]));
+
+            (*end) = p;
+            return cp;
+        }
+        else if (*end - p >= 4) {
+            break;
+        }
+    }
+
+    *end = p;
+
+    return LXB_URL_DECODE_ERROR;
+}
diff --git a/contrib/url/lexbor/url/url.h b/contrib/url/lexbor/url/url.h
new file mode 100644
index 0000000000..fba1840369
--- /dev/null
+++ b/contrib/url/lexbor/url/url.h
@@ -0,0 +1,581 @@
+/*
+ * Copyright (C) 2023 Alexander Borisov
+ *
+ * Author: Alexander Borisov <borisov@lexbor.com>
+ *
+ * The URL Standard.
+ * By specification: https://url.spec.whatwg.org/
+ */
+
+#ifndef LEXBOR_URL_H
+#define LEXBOR_URL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "lexbor/url/base.h"
+#include "lexbor/core/mraw.h"
+#include "lexbor/core/plog.h"
+
+
+typedef enum {
+    LXB_URL_ERROR_TYPE_DOMAIN_TO_ASCII = 0x00,
+    LXB_URL_ERROR_TYPE_DOMAIN_TO_UNICODE,
+    LXB_URL_ERROR_TYPE_DOMAIN_INVALID_CODE_POINT,
+    LXB_URL_ERROR_TYPE_HOST_INVALID_CODE_POINT,
+    LXB_URL_ERROR_TYPE_IPV4_EMPTY_PART,
+    LXB_URL_ERROR_TYPE_IPV4_TOO_MANY_PARTS,
+    LXB_URL_ERROR_TYPE_IPV4_NON_NUMERIC_PART,
+    LXB_URL_ERROR_TYPE_IPV4_NON_DECIMAL_PART,
+    LXB_URL_ERROR_TYPE_IPV4_OUT_OF_RANGE_PART,
+    LXB_URL_ERROR_TYPE_IPV6_UNCLOSED,
+    LXB_URL_ERROR_TYPE_IPV6_INVALID_COMPRESSION,
+    LXB_URL_ERROR_TYPE_IPV6_TOO_MANY_PIECES,
+    LXB_URL_ERROR_TYPE_IPV6_MULTIPLE_COMPRESSION,
+    LXB_URL_ERROR_TYPE_IPV6_INVALID_CODE_POINT,
+    LXB_URL_ERROR_TYPE_IPV6_TOO_FEW_PIECES,
+    LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_TOO_MANY_PIECES,
+    LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_INVALID_CODE_POINT,
+    LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_OUT_OF_RANGE_PART,
+    LXB_URL_ERROR_TYPE_IPV4_IN_IPV6_TOO_FEW_PARTS,
+    LXB_URL_ERROR_TYPE_INVALID_URL_UNIT,
+    LXB_URL_ERROR_TYPE_SPECIAL_SCHEME_MISSING_FOLLOWING_SOLIDUS,
+    LXB_URL_ERROR_TYPE_MISSING_SCHEME_NON_RELATIVE_URL,
+    LXB_URL_ERROR_TYPE_INVALID_REVERSE_SOLIDUS,
+    LXB_URL_ERROR_TYPE_INVALID_CREDENTIALS,
+    LXB_URL_ERROR_TYPE_HOST_MISSING,
+    LXB_URL_ERROR_TYPE_PORT_OUT_OF_RANGE,
+    LXB_URL_ERROR_TYPE_PORT_INVALID,
+    LXB_URL_ERROR_TYPE_FILE_INVALID_WINDOWS_DRIVE_LETTER,
+    LXB_URL_ERROR_TYPE_FILE_INVALID_WINDOWS_DRIVE_LETTER_HOST,
+    LXB_URL_ERROR_TYPE__LAST_ENTRY
+}
+lxb_url_error_type_t;
+
+typedef enum {
+    LXB_URL_STATE__UNDEF = 0x00,
+    LXB_URL_STATE_SCHEME_START_STATE,
+    LXB_URL_STATE_SCHEME_STATE,
+    LXB_URL_STATE_NO_SCHEME_STATE,
+    LXB_URL_STATE_SPECIAL_RELATIVE_OR_AUTHORITY_STATE,
+    LXB_URL_STATE_PATH_OR_AUTHORITY_STATE,
+    LXB_URL_STATE_RELATIVE_STATE,
+    LXB_URL_STATE_RELATIVE_SLASH_STATE,
+    LXB_URL_STATE_SPECIAL_AUTHORITY_SLASHES_STATE,
+    LXB_URL_STATE_SPECIAL_AUTHORITY_IGNORE_SLASHES_STATE,
+    LXB_URL_STATE_AUTHORITY_STATE,
+    LXB_URL_STATE_HOST_STATE,
+    LXB_URL_STATE_HOSTNAME_STATE,
+    LXB_URL_STATE_PORT_STATE,
+    LXB_URL_STATE_FILE_STATE,
+    LXB_URL_STATE_FILE_SLASH_STATE,
+    LXB_URL_STATE_FILE_HOST_STATE,
+    LXB_URL_STATE_PATH_START_STATE,
+    LXB_URL_STATE_PATH_STATE,
+    LXB_URL_STATE_OPAQUE_PATH_STATE,
+    LXB_URL_STATE_QUERY_STATE,
+    LXB_URL_STATE_FRAGMENT_STATE
+}
+lxb_url_state_t;
+
+/*
+ * New values can only be added downwards.
+ * Before LXB_URL_SCHEMEL_TYPE__LAST_ENTRY.
+ *
+ * Please, see lxb_url_scheme_res in /lexbor/url/url.c.
+ */
+typedef enum {
+    LXB_URL_SCHEMEL_TYPE__UNDEF      = 0x00,
+    LXB_URL_SCHEMEL_TYPE__UNKNOWN    = 0x01,
+    LXB_URL_SCHEMEL_TYPE_HTTP        = 0x02,
+    LXB_URL_SCHEMEL_TYPE_HTTPS       = 0x03,
+    LXB_URL_SCHEMEL_TYPE_WS          = 0x04,
+    LXB_URL_SCHEMEL_TYPE_WSS         = 0x05,
+    LXB_URL_SCHEMEL_TYPE_FTP         = 0x06,
+    LXB_URL_SCHEMEL_TYPE_FILE        = 0x07,
+    LXB_URL_SCHEMEL_TYPE__LAST_ENTRY
+}
+lxb_url_scheme_type_t;
+
+typedef struct {
+    const lexbor_str_t    name;
+    uint16_t              port;
+    lxb_url_scheme_type_t type;
+}
+lxb_url_scheme_data_t;
+
+typedef struct {
+    lexbor_str_t          name;
+    lxb_url_scheme_type_t type;
+}
+lxb_url_scheme_t;
+
+typedef enum {
+    LXB_URL_HOST_TYPE__UNDEF = 0x00,
+    LXB_URL_HOST_TYPE_DOMAIN = 0x01,
+    LXB_URL_HOST_TYPE_OPAQUE = 0x02,
+    LXB_URL_HOST_TYPE_IPV4   = 0x03,
+    LXB_URL_HOST_TYPE_IPV6   = 0x04,
+    LXB_URL_HOST_TYPE_EMPTY  = 0x05
+}
+lxb_url_host_type_t;
+
+typedef struct {
+    lxb_url_host_type_t type;
+
+    union {
+        uint16_t     ipv6[8];
+        uint32_t     ipv4;
+        lexbor_str_t opaque;
+        lexbor_str_t domain;
+    } u;
+}
+lxb_url_host_t;
+
+typedef struct {
+    lexbor_str_t str;
+    size_t       length;
+    bool         opaque;
+}
+lxb_url_path_t;
+
+typedef struct {
+    lxb_url_scheme_t   scheme;
+
+    lxb_url_host_t     host;
+
+    lexbor_str_t       username;
+    lexbor_str_t       password;
+
+    uint16_t           port;
+    bool               has_port;
+
+    lxb_url_path_t     path;
+
+    lexbor_str_t       query;
+    lexbor_str_t       fragment;
+
+    lexbor_mraw_t      *mraw;
+}
+lxb_url_t;
+
+typedef struct {
+    lxb_url_t          *url;
+    lexbor_mraw_t      *mraw;
+    lexbor_plog_t      *log;
+}
+lxb_url_parser_t;
+
+
+/*
+ * Create lxb_url_parser_t object.
+ *
+ * @return lxb_url_parser_t * if successful, otherwise NULL.
+ */
+LXB_API lxb_url_parser_t *
+lxb_url_parser_create(void);
+
+/*
+ * Initialization of lxb_url_parser_t object.
+ *
+ * The parser is not bound to the received URLs in any way. That is, after
+ * parsing the lxb_url_parser_t object can be destroyed and we can continue
+ * working with the received URLs.
+ *
+ * Memory for created URLs is taken from lexbor_mraw_t object, which you can
+ * pass during initialization of lxb_url_parser_t object, or a new lexbor_mraw_t
+ * object will be created during initialization if NULL is passed.
+ *
+ * Each created URL will have a pointer to the lexbor_mraw_t object.
+ *
+ * By destroying the lexbor_mraw_t object you destroy all the URL objects
+ * created by the parser. Use the lxb_url_destroy() function to destroy a
+ * specific URL.
+ *
+ * Destroying the lxb_url_parser_t object with lxb_url_parser_destroy() does
+ * not destroy the lexbor_mraw_t memory object.
+ *
+ * Please, see functions lxb_url_parser_memory_destroy(), lxb_url_destroy(),
+ * lxb_url_memory_destroy().
+ *
+ * @param[in] lxb_url_parser_t *
+ * @param[in] lexbor_mraw_t *. Can be NULL. If pass NULL, it will create its own
+ * memory object inside parser and it will be bound to all created URLs.
+ *
+ * @return LXB_STATUS_OK if successful, otherwise an error status value.
+ */
+LXB_API lxb_status_t
+lxb_url_parser_init(lxb_url_parser_t *parser, lexbor_mraw_t *mraw);
+
+/*
+ * Clears the object. Returns object to states as after initialization.
+ *
+ * This function must be called before the parsing functions can be reused.
+ *
+ * For example:
+ *     lxb_url_parse()
+ *     lxb_url_parser_clean()
+ *     lxb_url_parse()
+ *     lxb_url_destroy()
+ *
+ * @param[in] lxb_url_parser_t *
+ */
+LXB_API void
+lxb_url_parser_clean(lxb_url_parser_t *parser);
+
+/*
+ * Destroy lxb_url_parser_t object.
+ *
+ * Release of occupied resources.
+ * The lexbor_mraw_t memory object is not destroyed in this function.
+ *
+ * @param[in] lxb_url_parser_t *. Can be NULL.
+ * @param[in] if false: only destroys internal buffers.
+ * if true: destroys the lxb_url_parser_t object and all internal buffers.
+ *
+ * @return lxb_url_parser_t * if self_destroy = false, otherwise NULL.
+ */
+LXB_API lxb_url_parser_t *
+lxb_url_parser_destroy(lxb_url_parser_t *parser, bool destroy_self);
+
+/*
+ * Destroys the lexbor_mraw_t object, and thus all associated URLs.
+ *
+ * After that, new URLs cannot be parsed until a new lexbor_mraw_t object is
+ * assigned to the lxb_url_parser_t object.
+ *
+ * @param[in] lxb_url_parser_t *.
+ */
+LXB_API void
+lxb_url_parser_memory_destroy(lxb_url_parser_t *parser);
+
+/*
+ * URL parser.
+ *
+ * This functional an implementation of URL parsing according to the WHATWG
+ * specification.
+ *
+ * @param[in] lxb_url_parser_t *.
+ * @param[in] const lxb_url_t *. Base URL, can be NULL.
+ * @param[in] Input characters. Not NULL.
+ * @param[in] Length of characters. Can be 0.
+ *
+ * @return lxb_url_t * if successful, otherwise NULL.
+ */
+LXB_API lxb_url_t *
+lxb_url_parse(lxb_url_parser_t *parser, const lxb_url_t *base_url,
+              const lxb_char_t *data, size_t length);
+
+/*
+ * URL basic parser.
+ *
+ * This functional an implementation of URL parsing according to the WHATWG
+ * specification.
+ *
+ * Use the lxb_url_get() function to get the URL object.
+ *
+ * @param[in] lxb_url_parser_t *.
+ * @param[in] lxb_url_t *. Can be NULL.
+ * @param[in] const lxb_url_t *. Base URL, can be NULL.
+ * @param[in] Input characters. Not NULL.
+ * @param[in] Length of characters. Can be 0.
+ * @param[in] lxb_url_state_t, for default set to LXB_URL_STATE__UNDEF.
+ * @param[in] lxb_encoding_t, default (LXB_ENCODING_DEFAULT) LXB_ENCODING_UTF_8.
+ *
+ * @return LXB_STATUS_OK if successful, otherwise an error status value.
+ */
+LXB_API lxb_status_t
+lxb_url_parse_basic(lxb_url_parser_t *parser, lxb_url_t *url,
+                    const lxb_url_t *base_url,
+                    const lxb_char_t *data, size_t length,
+                    lxb_url_state_t override_state);
+
+/*
+ * Erase URL.
+ *
+ * Frees all internal memory occupied by the URL object, but does not destroy
+ * the object.
+ *
+ * @param[in] lxb_url_t *.
+ *
+ * @return NULL.
+ */
+LXB_API void
+lxb_url_erase(lxb_url_t *url);
+
+/*
+ * Destroys URL.
+ *
+ * @param[in] lxb_url_t *.
+ *
+ * @return NULL.
+ */
+LXB_API lxb_url_t *
+lxb_url_destroy(lxb_url_t *url);
+
+/*
+ * Destroys the lexbor_mraw_t memory object.
+ *
+ * The function will destroy all URLs associated with the lexbor_mraw_t memory
+ * object, including the passed one.
+ *
+ * Keep in mind, if you have a live lxb_url_parser_t parsing object, you will
+ * have a pointer to garbage after calling this function instead of a pointer
+ * to the lexbor_mraw_t object.
+ * In this case you need to assign a new memory object lexbor_mraw_t for the
+ * parser. Use the lxb_url_mraw_set() function.
+ *
+ * @param[in] lxb_url_t *.
+ */
+LXB_API void
+lxb_url_memory_destroy(lxb_url_t *url);
+
+
+/*
+ * Below is an API for modifying the URL object according to the
+ * https://url.spec.whatwg.org/#api specification.
+ *
+ * It is not necessary to pass the lxb_url_parser_t object to API functions.
+ * You need to pass the parser if you want to have logs of parsing.
+ *
+ * All API functions can be passed NULL as "const lxb_char_t *" data.
+ */
+
+LXB_API lxb_status_t
+lxb_url_api_href_set(lxb_url_t *url, lxb_url_parser_t *parser,
+                     const lxb_char_t *href, size_t length);
+
+LXB_API lxb_status_t
+lxb_url_api_protocol_set(lxb_url_t *url, lxb_url_parser_t *parser,
+                         const lxb_char_t *protocol, size_t length);
+
+LXB_API lxb_status_t
+lxb_url_api_username_set(lxb_url_t *url,
+                         const lxb_char_t *username, size_t length);
+
+LXB_API lxb_status_t
+lxb_url_api_password_set(lxb_url_t *url,
+                         const lxb_char_t *password, size_t length);
+
+LXB_API lxb_status_t
+lxb_url_api_host_set(lxb_url_t *url, lxb_url_parser_t *parser,
+                     const lxb_char_t *host, size_t length);
+
+LXB_API lxb_status_t
+lxb_url_api_hostname_set(lxb_url_t *url, lxb_url_parser_t *parser,
+                         const lxb_char_t *hostname, size_t length);
+
+LXB_API lxb_status_t
+lxb_url_api_port_set(lxb_url_t *url, lxb_url_parser_t *parser,
+                     const lxb_char_t *port, size_t length);
+
+LXB_API lxb_status_t
+lxb_url_api_pathname_set(lxb_url_t *url, lxb_url_parser_t *parser,
+                         const lxb_char_t *pathname, size_t length);
+
+LXB_API lxb_status_t
+lxb_url_api_search_set(lxb_url_t *url, lxb_url_parser_t *parser,
+                       const lxb_char_t *search, size_t length);
+
+LXB_API lxb_status_t
+lxb_url_api_hash_set(lxb_url_t *url, lxb_url_parser_t *parser,
+                     const lxb_char_t *hash, size_t length);
+
+
+/*
+ * Length of.
+ */
+
+LXB_API size_t
+lxb_url_length(const lxb_url_t *url, bool exclude_fragment);
+
+LXB_API size_t
+lxb_url_scheme_length(const lxb_url_t *url);
+
+LXB_API size_t
+lxb_url_username_length(const lxb_url_t *url);
+
+LXB_API size_t
+lxb_url_password_length(const lxb_url_t *url);
+
+LXB_API size_t
+lxb_url_host_length(const lxb_url_host_t *host);
+
+LXB_API size_t
+lxb_url_host_unicode_length(const lxb_url_host_t *host);
+
+LXB_API size_t
+lxb_url_port_length(const lxb_url_t *url);
+
+LXB_API size_t
+lxb_url_path_length(const lxb_url_path_t *path);
+
+LXB_API size_t
+lxb_url_query_length(const lxb_url_t *url);
+
+LXB_API size_t
+lxb_url_fragment_length(const lxb_url_t *url);
+
+
+/*
+ * Below are functions for serializing a URL object and its individual
+ * parameters.
+ *
+ * Note that the callback may be called more than once.
+ * For example, the lxb_url_serialize() function will callback multiple times:
+ * 1. http
+ * 2. ://
+ * 3. example.com
+ * and so on.
+ */
+
+LXB_API lxb_status_t
+lxb_url_serialize(const lxb_url_t *url, lexbor_serialize_cb_f cb, void *ctx,
+                  bool exclude_fragment);
+
+LXB_API lxb_status_t
+lxb_url_serialize_scheme(const lxb_url_t *url,
+                         lexbor_serialize_cb_f cb, void *ctx);
+
+LXB_API lxb_status_t
+lxb_url_serialize_username(const lxb_url_t *url,
+                           lexbor_serialize_cb_f cb, void *ctx);
+
+LXB_API lxb_status_t
+lxb_url_serialize_password(const lxb_url_t *url,
+                           lexbor_serialize_cb_f cb, void *ctx);
+
+LXB_API lxb_status_t
+lxb_url_serialize_host(const lxb_url_host_t *host,
+                       lexbor_serialize_cb_f cb, void *ctx);
+
+LXB_API lxb_status_t
+lxb_url_serialize_host_unicode(const lxb_url_host_t *host,
+                               lexbor_serialize_cb_f cb, void *ctx);
+
+LXB_API lxb_status_t
+lxb_url_serialize_host_ipv4(uint32_t ipv4,
+                            lexbor_serialize_cb_f cb, void *ctx);
+
+LXB_API lxb_status_t
+lxb_url_serialize_host_ipv6(const uint16_t *ipv6,
+                            lexbor_serialize_cb_f cb, void *ctx);
+
+LXB_API lxb_status_t
+lxb_url_serialize_port(const lxb_url_t *url,
+                       lexbor_serialize_cb_f cb, void *ctx);
+
+LXB_API lxb_status_t
+lxb_url_serialize_path(const lxb_url_path_t *path,
+                       lexbor_serialize_cb_f cb, void *ctx);
+
+LXB_API lxb_status_t
+lxb_url_serialize_query(const lxb_url_t *url,
+                        lexbor_serialize_cb_f cb, void *ctx);
+
+LXB_API lxb_status_t
+lxb_url_serialize_fragment(const lxb_url_t *url,
+                           lexbor_serialize_cb_f cb, void *ctx);
+
+/*
+ * Creates a clone of the object's URL.
+ *
+ * For lexbor_mraw_t *, use url->mraw or another lexbor_mraw_t * object.
+ *
+ * @param[in] lexbor_mraw_t *.
+ * @param[in] lxb_url_t *.
+ *
+ * @return a new URL object if successful, otherwise NULL value.
+ */
+LXB_API lxb_url_t *
+lxb_url_clone(lexbor_mraw_t *mraw, lxb_url_t *url);
+
+/*
+ * Inline functions.
+ */
+
+lxb_inline const lexbor_str_t *
+lxb_url_scheme(const lxb_url_t *url)
+{
+    return &url->scheme.name;
+}
+
+lxb_inline const lexbor_str_t *
+lxb_url_username(const lxb_url_t *url)
+{
+    return &url->username;
+}
+
+lxb_inline const lexbor_str_t *
+lxb_url_password(const lxb_url_t *url)
+{
+    return &url->password;
+}
+
+lxb_inline const lxb_url_host_t *
+lxb_url_host(const lxb_url_t *url)
+{
+    return &url->host;
+}
+
+lxb_inline uint16_t
+lxb_url_port(const lxb_url_t *url)
+{
+    return url->port;
+}
+
+lxb_inline bool
+lxb_url_has_port(const lxb_url_t *url)
+{
+    return url->has_port;
+}
+
+lxb_inline const lxb_url_path_t *
+lxb_url_path(const lxb_url_t *url)
+{
+    return &url->path;
+}
+
+lxb_inline const lexbor_str_t *
+lxb_url_path_str(const lxb_url_t *url)
+{
+    return &url->path.str;
+}
+
+lxb_inline const lexbor_str_t *
+lxb_url_query(const lxb_url_t *url)
+{
+    return &url->query;
+}
+
+lxb_inline const lexbor_str_t *
+lxb_url_fragment(const lxb_url_t *url)
+{
+    return &url->fragment;
+}
+
+lxb_inline lexbor_mraw_t *
+lxb_url_mraw(lxb_url_parser_t *parser)
+{
+    return parser->mraw;
+}
+
+lxb_inline void
+lxb_url_mraw_set(lxb_url_parser_t *parser, lexbor_mraw_t *mraw)
+{
+    parser->mraw = mraw;
+}
+
+lxb_inline lxb_url_t *
+lxb_url_get(lxb_url_parser_t *parser)
+{
+    return parser->url;
+}
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* LEXBOR_URL_H */
diff --git a/contrib/url/meson.build b/contrib/url/meson.build
new file mode 100644
index 0000000000..ad1d1e6d24
--- /dev/null
+++ b/contrib/url/meson.build
@@ -0,0 +1,52 @@
+
+if not icu.found()
+  subdir_done()
+endif
+
+url_sources = files(
+  'url.c',
+  'lexbor/core/array.c',
+	'lexbor/core/array_obj.c',
+	'lexbor/core/bst.c',
+	'lexbor/core/conv.c',
+	'lexbor/core/diyfp.c',
+	'lexbor/core/dobject.c',
+	'lexbor/core/dtoa.c',
+	'lexbor/core/mem.c',
+	'lexbor/core/memory.c',
+	'lexbor/core/mraw.c',
+	'lexbor/core/plog.c',
+	'lexbor/core/str.c',
+	'lexbor/core/strtod.c',
+	'lexbor/url/url.c',
+)
+
+if host_system == 'windows'
+  ltree_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'url',
+    '--FILEDESC', 'url - Uniform Resource Locator data type',])
+endif
+
+url = shared_module('url',
+  url_sources,
+  include_directories: include_directories('.'),
+  kwargs: contrib_mod_args,
+)
+contrib_targets += url
+
+install_data(
+  'url.control',
+  'url--1.0.sql',
+  kwargs: contrib_data_args,
+)
+
+tests += {
+  'name': 'url',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'regress': {
+    'sql': [
+      'url'
+    ],
+  },
+}
diff --git a/contrib/url/sql/url.sql b/contrib/url/sql/url.sql
new file mode 100644
index 0000000000..fe5e8d04e2
--- /dev/null
+++ b/contrib/url/sql/url.sql
@@ -0,0 +1,126 @@
+--
+-- Basic URL tests for the behavior of functions.
+-- The tests for compliance with the specification are located separately.
+--
+
+-- The tests are designed for a UTF-8 database.  Skip otherwise.
+SELECT getdatabaseencoding() NOT IN ('UTF8')
+  AS skip_test \gset
+\if :skip_test
+  \quit
+\endif
+
+SELECT getdatabaseencoding(); -- label the results files
+
+CREATE EXTENSION url;
+
+-- Getters
+select ('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url).scheme; -- OK, https
+select ('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url).username; -- OK, root
+select ('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url).password; -- OK, qwerty
+select ('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url).host; -- OK, example.com
+select ('https://root:qwerty@εxαmπle.cθm:8080/path/to/home?abc=xyz#anchor'::url).host; -- OK, xn--xmle-0ldw4f.xn--cm-x9b
+select ('https://root:qwerty@εxαmπle.cθm:8080/path/to/home?abc=xyz#anchor'::url).host_unicode; -- OK, εxαmπle.cθm
+select ('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url).port; -- OK, 8080
+select ('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url).path; -- OK, /path/to/home
+select ('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url).query; -- OK, abc=xyz
+select ('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url).fragment; -- OK, anchor
+
+-- Setters
+
+select url_scheme_set('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, 'wss'); -- OK
+select url_username_set('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, 'guest'); -- OK
+select url_password_set('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, '12345'); -- OK
+select url_host_set('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, 'postgresql.org'); -- OK
+select url_port_set('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, '80'); -- OK
+select url_path_set('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, '/docs/books/'); -- OK
+select url_query_set('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, 'xyz=abc'); -- OK
+select url_fragment_set('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, 'general_questions'); -- OK
+
+-- Base
+select url_base(NULL::url, NULL); -- OK, NULL
+select url_base('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, NULL); -- OK
+select url_base('https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor'::url, '/change/path'); -- OK
+
+-- Unicode
+select ('https://εxαmπle.cθm/'::url).host; -- OK, xn--xmle-0ldw4f.xn--cm-x9b
+select ('https://εxαmπle.cθm/'::url).host_unicode; -- OK, εxαmπle.cθm
+
+-- Percent Encode
+select ('https://βεst@example.com'::url).username; -- OK
+select ('https://:παssφord@example.com'::url).password; -- OK
+select ('https://example.com/pαth/to/hθmε'::url).path; -- OK
+select ('https://xample.com/?αβγ=χψω'::url).query; -- OK
+select ('https://xample.com/#αnchθrΩ'::url).fragment; -- OK
+
+-- Getters Ok, Error
+select ''::url;  -- ERROR
+select NULL::url; -- OK
+
+select (NULL::url).scheme; -- OK
+select ('file://path/to'::url).scheme; -- OK
+select ('/bad/url'::url).scheme; -- ERROR
+
+select (NULL::url).username; -- OK
+select ('https://example.com'::url).username; -- OK
+
+select (NULL::url).password; -- OK
+select ('https://example.com'::url).password; -- OK
+
+select (NULL::url).host; -- OK
+select ('file://host/to'::url).host; -- OK
+select ('file:/path/to'::url).host; -- OK
+select (NULL::url).host_unicode; -- OK
+select ('file://host/to'::url).host_unicode; -- OK
+select ('file:/path/to'::url).host_unicode; -- OK
+
+select (NULL::url).port; -- OK
+select ('https://example.com'::url).port; -- OK
+
+select (NULL::url).path; -- OK
+select ('https://example.com'::url).path; -- OK
+select ('file:/path/to'::url).path; -- OK
+
+select (NULL::url).query; -- OK
+select ('https://example.com'::url).query; -- OK
+
+select (NULL::url).fragment; -- OK
+select ('https://example.com'::url).fragment; -- OK
+
+-- Setters Ok, Error
+select url_scheme_set('https://example.com'::url, NULL); -- ERROR
+select url_scheme_set('https://example.com'::url, ''); -- ERROR
+select url_scheme_set('https://example.com'::url, '---+'); -- ERROR
+
+select url_username_set('https://root:qwerty@example.com'::url, NULL); -- OK
+select url_username_set('https://root:qwerty@example.com'::url, ''); -- OK
+select url_username_set('https://root:qwerty@example.com'::url, 'αβγ'); -- OK
+
+select url_password_set('https://root:qwerty@example.com'::url, NULL); -- OK
+select url_password_set('https://root:qwerty@example.com'::url, ''); -- OK
+select url_password_set('https://root:qwerty@example.com'::url, 'αβγ'); -- OK
+
+select url_port_set('https://example.com:8080'::url, NULL); -- OK
+select url_port_set('https://example.com:8080'::url, ''); -- OK
+select url_port_set('https://example.com:8080'::url, '80'); -- OK
+select url_port_set('https://example.com:8080'::url, '123456'); -- ERROR
+select url_port_set('https://example.com:8080'::url, 80); -- OK
+select url_port_set('https://example.com:8080'::url, 123456); -- ERROR
+
+select url_host_set('https://example.com'::url, NULL); -- ERROR
+select url_host_set('https://example.com'::url, ''); -- ERROR
+select url_host_set('https://example.com'::url, '123'); -- OK
+select url_host_set('https://example.com'::url, 'αβγ'); -- OK
+
+select url_path_set('https://example.com/path/to/home'::url, NULL); -- OK
+select url_path_set('https://example.com/path/to/home'::url, ''); -- OK
+select url_path_set('https://example.com/path/to/home'::url, '/'); -- OK
+select url_path_set('https://example.com/path/to/home'::url, 'αβγ'); -- OK
+
+select url_query_set('https://example.com?abc=xyz'::url, NULL); -- OK
+select url_query_set('https://example.com?abc=xyz'::url, ''); -- OK
+select url_query_set('https://example.com?abc=xyz'::url, 'αβγ'); -- OK
+
+select url_fragment_set('https://example.com#anchor'::url, NULL); -- OK
+select url_fragment_set('https://example.com#anchor'::url, ''); -- OK
+select url_fragment_set('https://example.com#anchor'::url, 'αβγ'); -- OK
diff --git a/contrib/url/url--1.0.sql b/contrib/url/url--1.0.sql
new file mode 100644
index 0000000000..9521bbc2b8
--- /dev/null
+++ b/contrib/url/url--1.0.sql
@@ -0,0 +1,141 @@
+CREATE TYPE url;
+
+
+CREATE FUNCTION url_in(cstring) RETURNS url
+    IMMUTABLE
+    STRICT
+    LANGUAGE C
+    AS '$libdir/url';
+
+CREATE FUNCTION url_out(url) RETURNS cstring
+    IMMUTABLE
+    STRICT
+    LANGUAGE C
+    AS '$libdir/url';
+
+CREATE TYPE url (
+    INTERNALLENGTH = -1,
+    INPUT = url_in,
+    OUTPUT = url_out
+);
+
+
+CREATE CAST (url AS text) WITH INOUT AS ASSIGNMENT;
+CREATE CAST (text AS url) WITH INOUT AS ASSIGNMENT;
+
+
+CREATE FUNCTION scheme(url) RETURNS text
+    IMMUTABLE
+    STRICT
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_scheme';
+
+CREATE FUNCTION username(url) RETURNS text
+    IMMUTABLE
+    STRICT
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_username';
+
+CREATE FUNCTION password(url) RETURNS text
+    IMMUTABLE
+    STRICT
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_password';
+
+CREATE FUNCTION port(url) RETURNS integer
+    IMMUTABLE
+    STRICT
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_port';
+
+CREATE FUNCTION host(url) RETURNS text
+    IMMUTABLE
+    STRICT
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_host';
+
+CREATE FUNCTION host_unicode(url) RETURNS text
+    IMMUTABLE
+    STRICT
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_host_unicode';
+
+CREATE FUNCTION path(url) RETURNS text
+    IMMUTABLE
+    STRICT
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_path';
+
+CREATE FUNCTION query(url) RETURNS text
+    IMMUTABLE
+    STRICT
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_query';
+
+CREATE FUNCTION fragment(url) RETURNS text
+    IMMUTABLE
+    STRICT
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_fragment';
+
+CREATE FUNCTION to_url(text) RETURNS url
+    IMMUTABLE
+    STRICT
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_create';
+
+CREATE FUNCTION url_base(url, text) RETURNS url
+    IMMUTABLE
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_base';
+
+
+CREATE FUNCTION url_scheme_set(url, text) RETURNS url
+    IMMUTABLE
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_scheme_set';
+
+CREATE FUNCTION url_username_set(url, text) RETURNS url
+    IMMUTABLE
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_username_set';
+
+CREATE FUNCTION url_password_set(url, text) RETURNS url
+    IMMUTABLE
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_password_set';
+
+CREATE FUNCTION url_host_set(url, text) RETURNS url
+    IMMUTABLE
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_host_set';
+
+CREATE FUNCTION url_hostname_set(url, text) RETURNS url
+    IMMUTABLE
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_hostname_set';
+
+CREATE FUNCTION url_port_set(url, text) RETURNS url
+    IMMUTABLE
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_port_set';
+
+CREATE FUNCTION url_port_set(url, integer) RETURNS url
+    IMMUTABLE
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_port_num_set';
+
+CREATE FUNCTION url_path_set(url, text) RETURNS url
+    IMMUTABLE
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_path_set';
+
+CREATE FUNCTION url_query_set(url, text) RETURNS url
+    IMMUTABLE
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_query_set';
+
+CREATE FUNCTION url_fragment_set(url, text) RETURNS url
+    IMMUTABLE
+    LANGUAGE C
+    AS 'MODULE_PATHNAME','url_fragment_set';
diff --git a/contrib/url/url.c b/contrib/url/url.c
new file mode 100644
index 0000000000..fa2f1b82f1
--- /dev/null
+++ b/contrib/url/url.c
@@ -0,0 +1,994 @@
+/*-------------------------------------------------------------------------
+ * url/url.c
+ *
+ * By the WHATWG URL specification https://url.spec.whatwg.org/.
+ * Supports Unicode ToASCII, ToUnicode.
+ *
+ * A complete URL entry consists of the following parts:
+ *
+ *     https://root:qwerty@example.com:8080/path/to/home?abc=xyz#anchor
+ *     |___|   |__| |____| |_________| |__||___________| |_____| |____|
+ *       |      |     |        |        |        |          |      |
+ *     scheme   |  password    |       port      |        query    |
+ *              |              |                 |                 |
+ *           username         host              path            fragment
+ *
+ *
+ * Functions to get separate parts of a URL:
+ *     scheme, username, password, host, host_unicode, port, path, query,
+ *     fragment.
+ *
+ * Example:
+ *     SELECT ('https://example.com/'::url).host;
+ * Result:
+ *     example.com
+ *
+ *
+ * Functions to set separate parts of a URL:
+ *     url_scheme_set, url_username_set, url_password_set, url_host_set,
+ *     url_hostname_set, url_port_set, url_path_set, url_query_set,
+ *     url_fragment_set.
+ *
+ * Example:
+ *     SELECT url_host_set('https://example.com/'::url, 'postgresql.org');
+ * Result:
+ *     https://postgresql.org/
+ *
+ * All URL modification functions return the full modified URL.
+ * All URL functions will return NULL if a NULL value is passed as the URL.
+ *
+ *
+ * The url_base() function:
+ *     The function allows to create a new URL based on the base URL and
+ *     relative URL.
+ *
+ * Example:
+ *     SELECT url_base('https://example.com/path/to'::url, '/new/path#and-fragment');
+ *     SELECT url_base('https://example.com/path/to'::url, 'wss://postgresql.org/new/path#and-fragment');
+ *     SELECT url_base('https://example.com/path/to/home/'::url, 'world');
+ *     SELECT url_base('https://example.com/path/to/home/'::url, '../world');
+ * Result:
+ *     https://example.com/new/path#and-fragment
+ *     wss://postgresql.org/new/path#and-fragment
+ *     https://example.com/path/to/home/world
+ *     https://example.com/path/to/world
+ *
+ * More information about functions can be found in the README.
+ *
+ *-------------------------------------------------------------------------
+ */
+#include <postgres.h>
+#include <varatt.h>
+#include <utils/builtins.h>
+#include <mb/pg_wchar.h>
+
+#include "lexbor/url/url.h"
+
+
+PG_MODULE_MAGIC;
+
+
+#define PG_RETURN_URL_P(p)		PG_RETURN_POINTER(p)
+#define PG_GETARG_URL_P(n)		PG_DETOAST_DATUM(PG_GETARG_DATUM(n))
+
+#define URL_LXB_STR_ARGS(str)	(const char *) (str)->data, (str)->length
+#define URL_VARDATA(vardata)	((uint8_t *) VARDATA(vardata))
+#define URL_HEAD_SIZE			(URL_LAST_ENTRY * sizeof(uint32_t))
+
+
+typedef enum URLIndex
+{
+	URL_SCHEME = 0x00,
+	URL_USERNAME,
+	URL_PASSWORD,
+	URL_HOST,
+	URL_PATH,
+	URL_QUERY,
+	URL_FRAGMENT,
+	URL_SUM,
+	URL_PORT,
+	URL_LAST_ENTRY
+} URLIndex;
+
+typedef struct URLCallbackContext
+{
+	char	*result;
+	size_t	length;
+} URLCallbackContext;
+
+typedef struct varlena URL;
+
+typedef lxb_status_t
+(*URLSetFunc)(lxb_url_t *url, lxb_url_parser_t *parser,
+			  const lxb_char_t *data, size_t length);
+
+
+/*
+ * External dynamically-loaded functions.
+ */
+PG_FUNCTION_INFO_V1(url_in);
+PG_FUNCTION_INFO_V1(url_out);
+
+PG_FUNCTION_INFO_V1(url_scheme);
+PG_FUNCTION_INFO_V1(url_username);
+PG_FUNCTION_INFO_V1(url_password);
+PG_FUNCTION_INFO_V1(url_host);
+PG_FUNCTION_INFO_V1(url_host_unicode);
+PG_FUNCTION_INFO_V1(url_port);
+PG_FUNCTION_INFO_V1(url_path);
+PG_FUNCTION_INFO_V1(url_query);
+PG_FUNCTION_INFO_V1(url_fragment);
+PG_FUNCTION_INFO_V1(url_create);
+PG_FUNCTION_INFO_V1(url_base);
+
+PG_FUNCTION_INFO_V1(url_scheme_set);
+PG_FUNCTION_INFO_V1(url_username_set);
+PG_FUNCTION_INFO_V1(url_password_set);
+PG_FUNCTION_INFO_V1(url_host_set);
+PG_FUNCTION_INFO_V1(url_hostname_set);
+PG_FUNCTION_INFO_V1(url_port_set);
+PG_FUNCTION_INFO_V1(url_port_num_set);
+PG_FUNCTION_INFO_V1(url_path_set);
+PG_FUNCTION_INFO_V1(url_query_set);
+PG_FUNCTION_INFO_V1(url_fragment_set);
+
+/*
+ * Internal declarations.
+ */
+static lxb_url_t *url_parse(char *data, size_t length,const lxb_url_t *base);
+static URL *url_change_part(URL *var_url, char *data,
+							size_t length, URLSetFunc set, const char *name);
+static URL *url_new(char *data);
+static lxb_status_t url_api_username_set(lxb_url_t *url, lxb_url_parser_t *parser,
+										 const lxb_char_t *username, size_t length);
+static lxb_status_t url_api_password_set(lxb_url_t *url, lxb_url_parser_t *parser,
+										 const lxb_char_t *password, size_t length);
+static URL *url_pack(lxb_url_t *url);
+static uint32_t url_pack_size(lxb_url_t *url, uint32_t *head);
+static void url_pack_string(const lexbor_str_t *str, uint8_t *data);
+static void url_pack_scheme(const lxb_url_scheme_t *scheme, uint8_t *data);
+static void url_pack_host(const lxb_url_host_t *host, uint8_t *data);
+static void url_pack_path(const lxb_url_path_t *path, uint8_t *data);
+static uint32_t url_string_size(const lexbor_str_t *str);
+static uint32_t url_scheme_size(const lxb_url_scheme_t *scheme);
+static uint32_t url_host_size(const lxb_url_host_t *host);
+static uint32_t url_path_size(const lxb_url_path_t *path);
+static void url_unpack(lxb_url_t *url, const uint8_t *data, lexbor_mraw_t *mraw);
+static void url_unpack_string(lexbor_str_t *str, const uint8_t *data,
+							  lexbor_mraw_t *mraw, URLIndex idx);
+static void url_unpack_scheme(lxb_url_scheme_t *scheme, const uint8_t *data,
+							  lexbor_mraw_t *mraw);
+static void url_unpack_host(lxb_url_host_t *host, const uint8_t *data,
+							lexbor_mraw_t *mraw);
+static void url_unpack_port(lxb_url_t *url, const uint8_t *data);
+static void url_unpack_path(lxb_url_path_t *path, const uint8_t *data,
+							lexbor_mraw_t *mraw);
+static void url_copy_data(lexbor_str_t *str, lexbor_mraw_t *mraw,
+						  const uint8_t *data, uint32_t length);
+static lxb_status_t url_callback(const lxb_char_t *data, size_t len, void *ctx);
+static void *url_palloc0(size_t num, size_t size);
+
+/*
+ * Inline functions.
+ */
+static inline char *
+url_encoding_encode(char *str, uint32_t length)
+{
+	return (char *) pg_do_encoding_conversion((unsigned char *) str, length,
+											  PG_UTF8, GetDatabaseEncoding());
+}
+
+static inline char *
+url_encoding_decode(char *str, uint32_t length)
+{
+	return (char *) pg_do_encoding_conversion((unsigned char *) str, length,
+											  GetDatabaseEncoding(), PG_UTF8);
+}
+
+static inline const uint32_t *
+url_head_entry(const uint8_t *data, URLIndex idx)
+{
+	return ((uint32_t *) data) + idx;
+}
+
+static inline uint32_t
+url_entry_offset(const uint8_t *data, URLIndex idx)
+{
+	return *url_head_entry(data, idx);
+}
+
+static inline uint32_t
+url_entry_length(const uint8_t *data, URLIndex idx)
+{
+	const uint32_t *off = url_head_entry(data, idx);
+	return off[1] - off[0];
+}
+
+
+/*
+ * All NULL arguments will be considered as empty value.
+ */
+static char url_empty_str[] = "";
+
+
+/*
+ * Module load callback.
+ */
+void
+_PG_init(void)
+{
+	/* Lexbor supports overriding the allocation routines. */
+	lexbor_memory_setup(palloc, repalloc, url_palloc0, pfree);
+}
+
+/*
+ * Input/Output.
+ */
+
+/*
+ * The Input function parses/validated the URL and packs the parsed URL into an
+ * internal storage format.
+ *
+ * Format:
+ * Head contains offsets for each part of the URL:
+ *     Bergin: 0 byte. End: (sizeof(uint32_t) * URL_LAST_ENTRY) byte.
+ *
+ * To get the necessary offset we just need sizeof(uint32_t) * URLIndex.
+ *
+ * Body:
+ *     After head, the body with the URL entries begins.
+ *
+ * URL_PORT stores the port directly, not the offset to the body.
+ */
+Datum
+url_in(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_URL_P(url_new(PG_GETARG_CSTRING(0)));
+}
+
+Datum
+url_out(PG_FUNCTION_ARGS)
+{
+	URL *vardata = PG_GETARG_URL_P(0);
+	lxb_url_t url;
+	URLCallbackContext ctx;
+
+	url_unpack(&url, URL_VARDATA(vardata), NULL);
+
+	ctx.result = palloc(lxb_url_length(&url, false) + 1);
+	ctx.length = 0;
+
+	lxb_url_serialize(&url, url_callback, &ctx, false);
+
+	ctx.result[ctx.length] = 0x00;
+
+	/*
+	 * We will not convert the encoding (no matter what encoding is in the base)
+	 * because the URL is always returned in ASCII.  All encodings that Postgres
+	 * supports understand ASCII < 0x80.
+	 */
+
+	PG_RETURN_CSTRING(ctx.result);
+}
+
+/*
+ * Getter functions for get parts of URL.
+ * Scheme, username, password, host, host_unicode, port, path, query, fragment.
+ */
+Datum
+url_scheme(PG_FUNCTION_ARGS)
+{
+	URL *vardata = PG_GETARG_URL_P(0);
+	lxb_url_scheme_t scheme;
+
+	url_unpack_scheme(&scheme, URL_VARDATA(vardata), NULL);
+
+	if (scheme.type == LXB_URL_SCHEMEL_TYPE__UNDEF)
+		PG_RETURN_NULL();
+
+	PG_RETURN_TEXT_P(cstring_to_text_with_len(URL_LXB_STR_ARGS(&scheme.name)));
+}
+
+Datum
+url_username(PG_FUNCTION_ARGS)
+{
+	URL *vardata = PG_GETARG_URL_P(0);
+	lexbor_str_t str;
+
+	url_unpack_string(&str, URL_VARDATA(vardata), NULL, URL_USERNAME);
+
+	if (str.length == 0)
+		PG_RETURN_NULL();
+
+	PG_RETURN_TEXT_P(cstring_to_text_with_len(URL_LXB_STR_ARGS(&str)));
+}
+
+Datum
+url_password(PG_FUNCTION_ARGS)
+{
+	URL *vardata = PG_GETARG_URL_P(0);
+	lexbor_str_t str;
+
+	url_unpack_string(&str, URL_VARDATA(vardata), NULL, URL_PASSWORD);
+
+	if (str.length == 0)
+		PG_RETURN_NULL();
+
+	PG_RETURN_TEXT_P(cstring_to_text_with_len(URL_LXB_STR_ARGS(&str)));
+}
+
+Datum
+url_host(PG_FUNCTION_ARGS)
+{
+	URL *vardata = PG_GETARG_URL_P(0);
+	text *txt;
+	size_t length;
+	URLCallbackContext ctx;
+	lxb_url_host_t host;
+
+	url_unpack_host(&host, URL_VARDATA(vardata), NULL);
+
+	if (host.type == LXB_URL_HOST_TYPE__UNDEF)
+		PG_RETURN_NULL();
+
+	length = lxb_url_host_length(&host);
+	txt = (text *) palloc(length + VARHDRSZ);
+
+	ctx.result = VARDATA(txt);
+	ctx.length = 0;
+
+	lxb_url_serialize_host(&host, url_callback, &ctx);
+
+	SET_VARSIZE(txt, ctx.length + VARHDRSZ);
+
+	PG_RETURN_TEXT_P(txt);
+}
+
+Datum
+url_host_unicode(PG_FUNCTION_ARGS)
+{
+	URL *vardata = PG_GETARG_URL_P(0);
+	char *result;
+	text *txt;
+	size_t length;
+	URLCallbackContext ctx;
+	lxb_url_host_t host;
+
+	url_unpack_host(&host, URL_VARDATA(vardata), NULL);
+
+	if (host.type == LXB_URL_HOST_TYPE__UNDEF)
+		PG_RETURN_NULL();
+
+	length = lxb_url_host_unicode_length(&host);
+
+	txt = (text *) palloc(length + VARHDRSZ);
+
+	ctx.result = VARDATA(txt);
+	ctx.length = 0;
+
+	lxb_url_serialize_host_unicode(&host, url_callback, &ctx);
+
+	result = url_encoding_encode(ctx.result, ctx.length);
+
+	if (ctx.result != result)
+	{
+		ctx.length = strlen(result);
+
+		/*
+		 * Perhaps we should check the size, if it has not changed, then do not
+		 * reallocate memory.
+		 */
+		pfree(txt);
+		txt = (text *) palloc(ctx.length + VARHDRSZ);
+
+		memcpy(VARDATA(txt), result, ctx.length);
+		pfree(result);
+	}
+
+	SET_VARSIZE(txt, ctx.length + VARHDRSZ);
+
+	PG_RETURN_TEXT_P(txt);
+}
+
+Datum
+url_port(PG_FUNCTION_ARGS)
+{
+	URL *vardata = PG_GETARG_URL_P(0);
+	lxb_url_t url;
+
+	url_unpack_port(&url, URL_VARDATA(vardata));
+
+	if (!url.has_port)
+		PG_RETURN_NULL();
+
+	PG_RETURN_UINT16(url.port);
+}
+
+Datum
+url_path(PG_FUNCTION_ARGS)
+{
+	URL *vardata = PG_GETARG_URL_P(0);
+	lxb_url_path_t path;
+
+	url_unpack_path(&path, URL_VARDATA(vardata), NULL);
+
+	if (path.str.length == 0)
+		PG_RETURN_NULL();
+
+	PG_RETURN_TEXT_P(cstring_to_text_with_len(URL_LXB_STR_ARGS(&path.str)));
+}
+
+Datum
+url_query(PG_FUNCTION_ARGS)
+{
+	URL *vardata = PG_GETARG_URL_P(0);
+	lexbor_str_t str;
+
+	url_unpack_string(&str, URL_VARDATA(vardata), NULL, URL_QUERY);
+
+	if (str.length == 0)
+		PG_RETURN_NULL();
+
+	PG_RETURN_TEXT_P(cstring_to_text_with_len(URL_LXB_STR_ARGS(&str)));
+}
+
+Datum
+url_fragment(PG_FUNCTION_ARGS)
+{
+	URL *vardata = PG_GETARG_URL_P(0);
+	lexbor_str_t str;
+
+	url_unpack_string(&str, URL_VARDATA(vardata), NULL, URL_FRAGMENT);
+
+	if (str.length == 0)
+		PG_RETURN_NULL();
+
+	PG_RETURN_TEXT_P(cstring_to_text_with_len(URL_LXB_STR_ARGS(&str)));
+}
+
+Datum
+url_create(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_URL_P(url_new(TextDatumGetCString(PG_GETARG_DATUM(0))));
+}
+
+Datum
+url_base(PG_FUNCTION_ARGS)
+{
+	char *str;
+	URL *varbase;
+	lxb_url_t *url, url_base;
+
+	if (PG_ARGISNULL(0))
+		PG_RETURN_NULL();
+
+	varbase = PG_GETARG_URL_P(0);
+	str = (PG_ARGISNULL(1)) ? url_empty_str
+							: TextDatumGetCString(PG_GETARG_DATUM(1));
+
+	url_unpack(&url_base, URL_VARDATA(varbase), NULL);
+
+	url = url_parse(str, strlen(str), &url_base);
+	if (url == NULL)
+		ereport(ERROR,
+				errcode(ERRCODE_SYNTAX_ERROR),
+				errmsg("failed to parse the relative URL \"%s\"", str));
+
+	varbase = url_pack(url);
+
+	lxb_url_memory_destroy(url);
+
+	PG_RETURN_URL_P(varbase);
+}
+
+/*
+ * Setter functions for change parts of URL.
+ * Scheme, username, password, host, hostname, port, path, query, fragment.
+ */
+#define URL_MAKE_SETTER_FUNCTION(name, func)	\
+	Datum	\
+	url_ ## name ## _set(PG_FUNCTION_ARGS)	\
+	{	\
+		char *str;	\
+		URL *vardata;	\
+	\
+		if (PG_ARGISNULL(0))	\
+			PG_RETURN_NULL();	\
+	\
+		vardata = PG_GETARG_URL_P(0);	\
+		str = (PG_ARGISNULL(1)) ? url_empty_str	\
+								: TextDatumGetCString(PG_GETARG_DATUM(1));	\
+	\
+		vardata = url_change_part(vardata, str, strlen(str), (func), #name);	\
+	\
+		PG_RETURN_URL_P(vardata);	\
+	}
+
+URL_MAKE_SETTER_FUNCTION(scheme, lxb_url_api_protocol_set)
+URL_MAKE_SETTER_FUNCTION(username, url_api_username_set)
+URL_MAKE_SETTER_FUNCTION(password, url_api_password_set)
+URL_MAKE_SETTER_FUNCTION(host, lxb_url_api_host_set)
+URL_MAKE_SETTER_FUNCTION(hostname, lxb_url_api_hostname_set)
+URL_MAKE_SETTER_FUNCTION(port, lxb_url_api_port_set)
+URL_MAKE_SETTER_FUNCTION(path, lxb_url_api_pathname_set)
+URL_MAKE_SETTER_FUNCTION(query, lxb_url_api_search_set)
+URL_MAKE_SETTER_FUNCTION(fragment, lxb_url_api_hash_set)
+
+Datum
+url_port_num_set(PG_FUNCTION_ARGS)
+{
+	URL *vardata = PG_GETARG_URL_P(0);
+	uint32_t port = PG_GETARG_UINT32(1);
+	int len;
+	char buf[12]; /* 10 digits, '\0' */
+
+	len = pg_ultoa_n(port, buf);
+
+	vardata = url_change_part(vardata, buf, len, lxb_url_api_port_set, "port");
+
+	PG_RETURN_URL_P(vardata);
+}
+
+/*
+ * Utilities for parsing, packaging, and unpacking URLs.
+ */
+static lxb_url_t *
+url_parse(char *data, size_t length, const lxb_url_t *base)
+{
+	char *dst;
+	lxb_url_t *url;
+	lxb_status_t status;
+	lxb_url_parser_t parser;
+
+	status = lxb_url_parser_init(&parser, NULL);
+	if (status != LXB_STATUS_OK)
+		ereport(ERROR,
+				errcode(ERRCODE_INTERNAL_ERROR),
+				errmsg("failed to create URL parser"));
+
+	dst = url_encoding_decode(data, length);
+
+	if (data == dst)
+		url = lxb_url_parse(&parser, base, (const lxb_char_t *) data, length);
+	else
+	{
+		url = lxb_url_parse(&parser, base,
+							(const lxb_char_t *) dst, strlen(dst));
+		pfree(dst);
+	}
+
+	lxb_url_parser_destroy(&parser, false);
+
+	return url;
+}
+
+static URL *
+url_change_part(URL *var_url, char *data, size_t length,
+				URLSetFunc set, const char *name)
+{
+	char *dst;
+	URL *result;
+	lxb_url_t url;
+	lxb_status_t status;
+	lexbor_mraw_t mraw;
+
+	lexbor_mraw_init(&mraw, 4096);
+
+	url_unpack(&url, URL_VARDATA(var_url), &mraw);
+
+	dst = url_encoding_decode(data, length);
+
+	if (data == dst)
+		status = set(&url, NULL, (const lxb_char_t *) data, length);
+	else
+	{
+		status = set(&url, NULL, (const lxb_char_t *) dst, strlen(dst));
+		pfree(dst);
+	}
+
+	if (status != LXB_STATUS_OK)
+		ereport(ERROR,
+				errcode(ERRCODE_SYNTAX_ERROR),
+				errmsg("failed to parse \"%s\" part \"%.*s\" of URL",
+					   name, (int) length, data));
+
+	result = url_pack(&url);
+
+	lexbor_mraw_destroy(&mraw, false);
+
+	return result;
+}
+
+static URL *
+url_new(char *data)
+{
+	URL *vardata;
+	lxb_url_t *url;
+
+	url = url_parse(data, strlen(data), NULL);
+	if (url == NULL)
+		ereport(ERROR,
+				errcode(ERRCODE_SYNTAX_ERROR),
+				errmsg("failed to parse the URL \"%s\"", data));
+
+	vardata = url_pack(url);
+
+	lxb_url_memory_destroy(url);
+
+	return vardata;
+}
+
+static lxb_status_t
+url_api_username_set(lxb_url_t *url, lxb_url_parser_t *parser,
+					 const lxb_char_t *username, size_t length)
+{
+	(void) parser;
+	return lxb_url_api_username_set(url, username, length);
+}
+
+static lxb_status_t
+url_api_password_set(lxb_url_t *url, lxb_url_parser_t *parser,
+					 const lxb_char_t *password, size_t length)
+{
+	(void) parser;
+	return lxb_url_api_password_set(url, password, length);
+}
+
+static URL *
+url_pack(lxb_url_t *url)
+{
+	URL *vardata;
+	uint8_t *data;
+	uint32_t size;
+	uint32_t head[URL_LAST_ENTRY];
+
+	size = url_pack_size(url, head);
+
+	vardata = palloc(VARHDRSZ + size);
+	data = URL_VARDATA(vardata);
+
+	/* Store head. */
+	memcpy(data, head, URL_HEAD_SIZE);
+
+	/* Store body. */
+	url_pack_scheme(&url->scheme, data + head[URL_SCHEME]);
+	url_pack_string(&url->username, data + head[URL_USERNAME]);
+	url_pack_string(&url->password, data + head[URL_PASSWORD]);
+	url_pack_host(&url->host, data + head[URL_HOST]);
+	url_pack_path(&url->path, data + head[URL_PATH]);
+	url_pack_string(&url->query, data + head[URL_QUERY]);
+	url_pack_string(&url->fragment, data + head[URL_FRAGMENT]);
+
+	SET_VARSIZE(vardata, VARHDRSZ + size);
+
+	return vardata;
+}
+
+static uint32_t
+url_pack_size(lxb_url_t *url, uint32_t *head)
+{
+	head[URL_SCHEME] = URL_HEAD_SIZE;
+	head[URL_USERNAME] = head[URL_SCHEME] + url_scheme_size(&url->scheme);
+	head[URL_PASSWORD] = head[URL_USERNAME] + url_string_size(&url->username);
+	head[URL_HOST] = head[URL_PASSWORD] + url_string_size(&url->password);
+	head[URL_PATH] = head[URL_HOST] + url_host_size(&url->host);
+	head[URL_QUERY] = head[URL_PATH] + url_path_size(&url->path);
+	head[URL_FRAGMENT] = head[URL_QUERY] + url_string_size(&url->query);
+	head[URL_SUM] = head[URL_FRAGMENT] + url_string_size(&url->fragment);
+
+	/*
+	 * Port has size uint16_t there is no sense to write it separately, we can
+	 * write it to the header at once.
+	 */
+	head[URL_PORT] = url->port << 8 | (uint8_t) url->has_port;
+
+	return head[URL_SUM];
+}
+
+static void
+url_pack_string(const lexbor_str_t *str, uint8_t *data)
+{
+	if (str->length > 0)
+		memcpy(data, str->data, str->length);
+}
+
+static void
+url_pack_scheme(const lxb_url_scheme_t *scheme, uint8_t *data)
+{
+	if (scheme->type == LXB_URL_SCHEMEL_TYPE__UNDEF)
+		return;
+
+	/* type + string. */
+	*data = (uint8_t) scheme->type;
+	url_pack_string(&scheme->name, data + sizeof(uint8_t));
+}
+
+static void
+url_pack_host(const lxb_url_host_t *host, uint8_t *data)
+{
+	*data = (uint8_t) host->type;
+	data += sizeof(uint8_t);
+
+	switch (host->type)
+	{
+		case LXB_URL_HOST_TYPE_DOMAIN:
+		case LXB_URL_HOST_TYPE_OPAQUE:
+			url_pack_string(&host->u.domain, data);
+			break;
+
+		case LXB_URL_HOST_TYPE_IPV4:
+			memcpy(data, &host->u.ipv4, sizeof(host->u.ipv4));
+			break;
+
+		case LXB_URL_HOST_TYPE_IPV6:
+			memcpy(data, host->u.ipv6, sizeof(host->u.ipv6));
+			break;
+
+		default:
+			break;
+	}
+}
+
+static void
+url_pack_path(const lxb_url_path_t *path, uint8_t *data)
+{
+	if (path->length == 0)
+		return;
+
+	/* opaque + length + string. */
+	*data = (uint8_t) path->opaque;
+	data += sizeof(uint8_t);
+
+	*((uint32_t *) data) = (uint32_t) path->length;
+	data += sizeof(uint32_t);
+
+	url_pack_string(&path->str, data);
+}
+
+static uint32_t
+url_string_size(const lexbor_str_t *str)
+{
+	return (uint32_t) str->length;
+}
+
+static uint32_t
+url_scheme_size(const lxb_url_scheme_t *scheme)
+{
+	if (scheme->type == LXB_URL_SCHEMEL_TYPE__UNDEF)
+		return 0;
+
+	/* type + string. */
+	return sizeof(uint8_t) + url_string_size(&scheme->name);
+}
+
+static uint32_t
+url_host_size(const lxb_url_host_t *host)
+{
+	uint32_t size;
+
+	switch (host->type)
+	{
+		case LXB_URL_HOST_TYPE_DOMAIN:
+		case LXB_URL_HOST_TYPE_OPAQUE:
+			size = url_string_size(&host->u.domain);
+			break;
+
+		case LXB_URL_HOST_TYPE_IPV4:
+			size = sizeof(host->u.ipv4);
+			break;
+
+		case LXB_URL_HOST_TYPE_IPV6:
+			size = sizeof(host->u.ipv6);
+			break;
+
+		default:
+			return 0;
+	}
+
+	/* type + data. */
+	return sizeof(uint8_t) + size;
+}
+
+static uint32_t
+url_path_size(const lxb_url_path_t *path)
+{
+	if (path->length == 0)
+		return 0;
+
+	/* opaque + length + string. */
+	return sizeof(uint8_t) + sizeof(uint32_t) + url_string_size(&path->str);
+}
+
+static void
+url_unpack(lxb_url_t *url, const uint8_t *data, lexbor_mraw_t *mraw)
+{
+	/*
+	 * TODO: We should check the size of the header and body before unpacking.
+	 * It is possible that the data will arrive broken and we will get “hello”.
+	 */
+
+	/*
+	 * We can use the current memory partitioning to serialize/read lxb_url_t
+	 * (all getter functions).
+	 *
+	 * To change the URL object, we need to access the URL parser, which has
+	 * its own memory partition for lxb_url_t.  Therefore, we copy data for all
+	 * setter functions.
+	 */
+	url->mraw = mraw;
+
+	url_unpack_scheme(&url->scheme, data, mraw);
+	url_unpack_string(&url->username, data, mraw, URL_USERNAME);
+	url_unpack_string(&url->password, data, mraw, URL_PASSWORD);
+	url_unpack_host(&url->host, data, mraw);
+	url_unpack_port(url, data);
+	url_unpack_path(&url->path, data, mraw);
+	url_unpack_string(&url->query, data, mraw, URL_QUERY);
+	url_unpack_string(&url->fragment, data, mraw, URL_FRAGMENT);
+}
+
+static void
+url_unpack_string(lexbor_str_t *str, const uint8_t *data, lexbor_mraw_t *mraw,
+				  URLIndex idx)
+{
+	lxb_char_t *begin;
+	const uint32_t offset = url_entry_offset(data, idx);
+	const uint32_t length = url_entry_length(data, idx);
+
+	if (length == 0)
+	{
+		memset(str, 0x00, sizeof(lexbor_str_t));
+		return;
+	}
+
+	begin = (lxb_char_t *) &data[offset];
+
+	if (mraw == NULL)
+	{
+		str->data = begin;
+		str->length = length;
+	}
+	else
+		url_copy_data(str, mraw, begin, length);
+}
+
+static void
+url_unpack_scheme(lxb_url_scheme_t *scheme, const uint8_t *data,
+				  lexbor_mraw_t *mraw)
+{
+	lxb_char_t *begin;
+	lexbor_str_t *str;
+	const uint32_t offset = url_entry_offset(data, URL_SCHEME);
+	uint32_t length = url_entry_length(data, URL_SCHEME);
+
+	if (length == 0)
+	{
+		memset(scheme, 0x00, sizeof(lxb_url_scheme_t));
+		return;
+	}
+
+	str = &scheme->name;
+	length -= 1; /* skip type */
+
+	scheme->type = (lxb_url_scheme_type_t) data[offset];
+	begin = (lxb_char_t *) &data[offset + 1];
+
+	if (mraw == NULL)
+	{
+		str->data = begin;
+		str->length = length;
+	} else
+		url_copy_data(str, mraw, begin, length);
+}
+
+static void
+url_unpack_host(lxb_url_host_t *host, const uint8_t *data, lexbor_mraw_t *mraw)
+{
+	lxb_char_t *begin;
+	const uint32_t offset = url_entry_offset(data, URL_HOST);
+	uint32_t length = url_entry_length(data, URL_HOST);
+
+	if (length == 0)
+	{
+		memset(host, 0x00, sizeof(lxb_url_host_t));
+		return;
+	}
+
+	length -= 1; /* skip type */
+
+	host->type = (lxb_url_host_type_t) data[offset];
+	begin = (lxb_char_t *) &data[offset + 1];
+
+	switch (host->type)
+	{
+		case LXB_URL_HOST_TYPE_DOMAIN:
+		case LXB_URL_HOST_TYPE_OPAQUE:
+			if (mraw == NULL)
+			{
+				host->u.domain.data = begin;
+				host->u.domain.length = length;
+			} else
+				url_copy_data(&host->u.domain, mraw, begin, length);
+			break;
+
+		case LXB_URL_HOST_TYPE_IPV4:
+			memcpy(&host->u.ipv4, begin, length);
+			break;
+
+		case LXB_URL_HOST_TYPE_IPV6:
+			memcpy(host->u.ipv6, begin, length);
+			break;
+
+		default:
+			break;
+	}
+}
+
+static void
+url_unpack_port(lxb_url_t *url, const uint8_t *data)
+{
+	const uint32_t num = url_entry_offset(data, URL_PORT);
+
+	url->port = num >> 8;
+	url->has_port = num & 1;
+}
+
+static void
+url_unpack_path(lxb_url_path_t *path, const uint8_t *data, lexbor_mraw_t *mraw)
+{
+	lxb_char_t *begin;
+	lexbor_str_t *str;
+	const uint32_t offset = url_entry_offset(data, URL_PATH);
+	uint32_t length = url_entry_length(data, URL_PATH);
+
+	if (length == 0)
+	{
+		memset(path, 0x00, sizeof(lxb_url_path_t));
+		return;
+	}
+
+	str = &path->str;
+	length = length - sizeof(uint32_t) - 1;
+
+	/* +sizeof(uint32_t) skip path->length; +1 skip path->opaque */
+	begin = (lxb_char_t *) &data[offset + sizeof(uint32_t) + 1];
+
+	if (mraw == NULL)
+	{
+		str->data = begin;
+		str->length = length;
+	} else
+		url_copy_data(str, mraw, begin, length);
+
+	path->opaque = (lxb_url_host_type_t) data[offset];
+	path->length = *((uint32_t *) &data[offset + 1]); /* +1 skip path->opaque */
+}
+
+static void
+url_copy_data(lexbor_str_t *str, lexbor_mraw_t *mraw,
+			  const uint8_t *data, uint32_t length)
+{
+	str->data = lexbor_mraw_alloc(mraw, length + 1);
+
+	memcpy(str->data, data, length);
+
+	str->data[length] = 0x00;
+	str->length = length;
+}
+
+static lxb_status_t
+url_callback(const lxb_char_t *data, size_t len, void *ctx)
+{
+	URLCallbackContext *context = ctx;
+
+	memcpy(context->result + context->length, data, len);
+	context->length += len;
+
+	return LXB_STATUS_OK;
+}
+
+static void *
+url_palloc0(size_t num, size_t size)
+{
+	return palloc0(size * num);
+}
diff --git a/contrib/url/url.control b/contrib/url/url.control
new file mode 100644
index 0000000000..bca8375a51
--- /dev/null
+++ b/contrib/url/url.control
@@ -0,0 +1,5 @@
+# url extension
+comment = 'data type for Uniform Resource Locator'
+default_version = '1.0'
+module_pathname = '$libdir/url'
+relocatable = true
-- 
2.39.5 (Apple Git-154)