        ; ------------------------------------------------------------------------
        ; HeavyThing x86_64 assembly language library and showcase programs
        ; Copyright © 2015-2018 2 Ton Digital
        ; Homepage: https://2ton.com.au/
        ; Author: Jeff Marrison <jeff@2ton.com.au>
        ;
        ; This file is part of the HeavyThing library.
        ;
        ; HeavyThing is free software: you can redistribute it and/or modify
        ; it under the terms of the GNU General Public License, or
        ; (at your option) any later version.
        ;
        ; HeavyThing is distributed in the hope that it will be useful,
        ; but WITHOUT ANY WARRANTY; without even the implied warranty of
        ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        ; GNU General Public License for more details.
        ;
        ; You should have received a copy of the GNU General Public License along
        ; with the HeavyThing library. If not, see <http://www.gnu.org/licenses/>.
        ; ------------------------------------------------------------------------
        ;
        ; url.inc: URL string parsing and relative handling, as well as url encode/decode
        ;
        ; This is not intended to be the end-all-be-all in RFC compliance, but does the job
        ; that I require nicely. Specifically:
        ; During parse, we don't percent decode the protocol (aka scheme), host, port, or the
        ; authinfo (aka username and password)
        ; The same applies to when we turn it back into a string form with url$tostring.
        ; Also, we don't deal with path parameters (any such will just end up as part of the
        ; path/file).
        ;
        ; we populate all strings such that they don't need to be tested for null
        ; (though of course they may be empty)
        ;

url_protocol_ofs = 0            ; string
url_host_ofs = 8                ; string
url_port_ofs = 16               ; int
url_file_ofs = 24               ; string
url_query_ofs = 32              ; string
url_authority_ofs = 40          ; string
url_path_ofs = 48               ; string
url_authinfo_ofs = 56           ; string
url_ref_ofs = 64                ; string
url_user_ofs = 72               ; not used in here, but we initialize it to zero on alloc

url_size = 80


if used url$init | defined include_everything

globals
{
        url$schemeportmap       dq      0
}

        ; no arguments, called from ht$init to initialize our default ports for the schemes we are about
        ; this list is intentionally short based on my own personal use cases.
falign
url$init:
        prolog  url$init
        xor     edi, edi
        call    stringmap$new
        mov     [url$schemeportmap], rax

macro url_addone port*, [val*] {
common
        local .NAME, .POST
        jmp     .POST
        cleartext .NAME, val
        .POST:
        mov     rdi, [url$schemeportmap]
        mov     rsi, .NAME
        mov     edx, port
        call    stringmap$insert_unique
}

        url_addone 80, 'http'
        url_addone 443, 'https'
        url_addone 21, 'ftp'
        url_addone 3690, 'svn'
        url_addone 9418, 'git'
        url_addone 6667, 'irc'
        url_addone 143, 'imap'
        url_addone 119, 'nntp'

        epilog

end if


if used url$new | defined include_everything
        ; two arguments: rdi == optional base context url, rsi == string
        ; returns new url object in rax, or null on error (e.g. no context and /some.path passed in == error, bad port)
falign
url$new:
        prolog  url$new
        mov     eax, [rsi]
if string_bits = 32
        mov     ecx, [rsi+8]
else
        movzx   ecx, word [rsi+8]
end if
        push    rbx r12 r13
        mov     rbx, rdi
        mov     r12, rsi
        xor     r13d, r13d
        push    r14 r15
        sub     rsp, 128
        mov     r14d, eax
        test    eax, eax
        jz      .initial_nullret
        sub     eax, 1
if string_bits = 32
        mov     edx, [rsi+rax*4+8]
else
        movzx   edx, word [rsi+rax*2+8]
end if
        cmp     ecx, 32
        jbe     .chopspaces
        cmp     edx, 32
        jbe     .chopspaces
calign
.checkurlstart:
        mov     rdi, r12
        mov     rsi, .urlcolon
        mov     edx, r13d
        call    string$indexof_ofs
        lea     edx, [r13d+4]
        cmp     rax, r13
        cmove   r13d, edx
        cmp     r13d, r14d
        jae     .initial_nullret
        call    string$new
        mov     [rsp+url_protocol_ofs], rax
if string_bits = 32
        cmp     dword [r12+r13*4+8], '#'
else
        cmp     word [r12+r13*2+8], '#'
end if
        je      .checkprotocol
        mov     r15d, r13d
calign
.protoloop:
        cmp     r15d, r14d
        jae     .checkprotocol
if string_bits = 32
        mov     eax, [r12+r15*4+8]
else
        movzx   eax, word [r12+r15*2+8]
end if
        cmp     eax, '/'
        je      .checkprotocol
        cmp     eax, ':'
        je      .protoloop_found
        add     r15d, 1
        jmp     .protoloop
cleartext .urlcolon, 'url:'
calign
.chopspaces:
.trailing_ws:
        test    r14d, r14d
        jz      .initial_nullret
        mov     rdi, r12
        mov     esi, r14d
        sub     esi, 1
        call    string$charat
        cmp     eax, 32
        ja      .leading_ws
        sub     r14d, 1
        jnz     .trailing_ws
        jz      .initial_nullret
calign
.leading_ws:
        cmp     r13d, r14d
        jae     .initial_nullret
        mov     rdi, r12
        mov     esi, r13d
        call    string$charat
        cmp     eax, 32
        ja      .checkurlstart
        add     r13d, 1
        jmp     .leading_ws
calign
.protoloop_found:
        mov     rdi, r12
        mov     esi, r13d
        mov     edx, r15d
        call    string$substring
        push    rax
        mov     rdi, rax
        call    string$to_lower_inplace
        mov     rdi, [rsp+8]
        call    heap$free
        pop     rdi
        mov     [rsp+url_protocol_ofs], rdi
        mov     r13d, r15d
        add     r13d, 1
calign
.checkprotocol:
        ; so at this point we have a possible protocol string sitting in [rsp]
        test    rbx, rbx
        jz      .verifyprotocol
        mov     rdi, [rsp+url_protocol_ofs]
        mov     rsi, [rbx+url_protocol_ofs]
        call    string$equals
        mov     rsi, [rsp+url_protocol_ofs]
        xor     ecx, ecx
        mov     edx, 1
        cmp     qword [rsi], 0
        cmove   ecx, edx
        or      eax, ecx
        jz      .verifyprotocol
        mov     rdi, [rbx+url_path_ofs]
        mov     esi, '/'
        call    string$indexof_charcode
        test    rax, rax
        jnz     .checkemptyproto
        ; otherwise, set it to empty
        call    string$new
        mov     rdi, [rsp]
        mov     [rsp+url_protocol_ofs], rax
        call    heap$free
calign
.checkemptyproto:
        mov     rdi, [rsp+url_protocol_ofs]
        cmp     qword [rdi], 0
        jne     .verifyprotocol
        mov     rdi, [rbx+url_protocol_ofs]
        cmp     qword [rdi], 0
        je      .protocol_nullret
        ; otherwise, set us up for full relative action
        call    string$copy
        mov     rdi, [rsp+url_protocol_ofs]
        mov     [rsp+url_protocol_ofs], rax
        call    heap$free
        ; set host, port, file, authority, path, authinfo
        mov     rdi, [rbx+url_host_ofs]
        call    string$copy
        mov     [rsp+url_host_ofs], rax
        mov     rdi, [rbx+url_port_ofs]
        mov     [rsp+url_port_ofs], rdi
        mov     rdi, [rbx+url_file_ofs]
        call    string$copy
        mov     [rsp+url_file_ofs], rax
        mov     rdi, [rbx+url_authority_ofs]
        call    string$copy
        mov     [rsp+url_authority_ofs], rax
        mov     rdi, [rbx+url_path_ofs]
        call    string$copy
        mov     [rsp+url_path_ofs], rax
        mov     rdi, [rbx+url_authinfo_ofs]
        call    string$copy
        mov     [rsp+url_authinfo_ofs], rax
        ; now, query and ref still need set
        mov     rdi, r12
        mov     esi, '#'
        mov     edx, r13d
        call    string$indexof_charcode_ofs
        cmp     rax, -1
        jne     .relative_withref
        call    string$new
        mov     [rsp+url_ref_ofs], rax

        cmp     r13d, r14d
        je      .relative_withquery
        call    string$new
        mov     [rsp+url_query_ofs], rax
        jmp     .parseurl
calign
.relative_withquery:
        mov     rdi, [rbx+url_query_ofs]
        call    string$copy
        mov     [rsp+url_query_ofs], rax
        mov     rdi, [rsp+url_ref_ofs]
        cmp     qword [rdi], 0
        jne     .parseurl
        mov     rdi, [rsp+url_ref_ofs]
        call    heap$free
        mov     rdi, [rbx+url_ref_ofs]
        call    string$copy
        mov     [rsp+url_ref_ofs], rax
        jmp     .parseurl
calign
.relative_withref:
        mov     rdi, r12
        mov     esi, eax
        mov     edx, r14d
        mov     r14d, eax
        add     esi, 1
        call    string$substring
        mov     [rsp+url_ref_ofs], rax
        cmp     r13d, r14d
        jne     .relative_emptyquery
        mov     rdi, [rbx+url_query_ofs]
        call    string$copy
        mov     [rsp+url_query_ofs], rax
        mov     rdi, [rsp+url_ref_ofs]
        cmp     qword [rdi], 0
        jne     .parseurl
        mov     rdi, [rsp+url_ref_ofs]
        call    heap$free
        mov     rdi, [rbx+url_ref_ofs]
        call    string$copy
        mov     [rsp+url_ref_ofs], rax
        jmp     .parseurl
calign
.relative_emptyquery:
        call    string$new
        mov     [rsp+url_query_ofs], rax
        jmp     .parseurl
calign
.verifyprotocol:
        mov     rdi, [rsp+url_protocol_ofs]
        cmp     qword [rdi], 0
        je      .protocol_nullret
        mov     rdi, r12
        mov     esi, '#'
        mov     edx, r13d
        call    string$indexof_charcode_ofs
        cmp     rax, -1
        je      .empty_ref
        mov     rdi, r12
        mov     esi, eax
        mov     edx, r14d
        mov     r14d, eax
        add     esi, 1
        call    string$substring
        mov     [rsp+url_ref_ofs], rax
        jmp     .notrelative_empties
calign
.empty_ref:
        call    string$new
        mov     [rsp+url_ref_ofs], rax
        ; fallthrough to notrelative_empties
calign
.notrelative_empties:
        ; so at this point, we have protocol and ref setup, we need to set the rest
        ; to all empty strings
        call    string$new
        mov     [rsp+url_host_ofs], rax
        mov     qword [rsp+url_port_ofs], 0
        call    string$new
        mov     [rsp+url_file_ofs], rax
        call    string$new
        mov     [rsp+url_query_ofs], rax
        call    string$new
        mov     [rsp+url_authority_ofs], rax
        call    string$new
        mov     [rsp+url_path_ofs], rax
        call    string$new
        mov     [rsp+url_authinfo_ofs], rax
        ; fallthrough to parseurl
calign
.parseurl:
        ; let the meaty bits begin
        mov     qword [rsp+72], 0
        cmp     r13d, r14d
        ja      .parseurl_checkstart
        mov     rdi, r12
        mov     esi, '?'
        mov     edx, r13d
        call    string$indexof_charcode_ofs
        xor     ecx, ecx
        mov     edx, 1
        cmp     rax, r13
        cmove   ecx, edx
        mov     [rsp+76], ecx           ; are we only sitting on a query?
        cmp     rax, -1
        je      .parseurl_checkstart
        cmp     eax, r14d
        jae     .parseurl_checkstart
        mov     rdi, r12
        mov     esi, eax
        mov     edx, r14d
        cmp     r14d, eax
        cmova   r14d, eax
        add     esi, 1
        call    string$substring
        mov     rdi, [rsp+url_query_ofs]
        mov     [rsp+url_query_ofs], rax
        call    heap$free
calign
.parseurl_checkstart:
        mov     eax, r14d
        sub     eax, r13d
        cmp     eax, 4
        jb      .parseurl_checkpath
if string_bits = 32
        cmp     dword [r12+r13*4+8], '/'
else
        cmp     word [r12+r13*2+8], '/'
end if
        jne     .parseurl_checkpath
if string_bits = 32
        cmp     dword [r12+r13*4+12], '/'
else
        cmp     word [r12+r13*2+10], '/'
end if
        jne     .parseurl_checkpath
if string_bits = 32
        cmp     dword [r12+r13*4+16], '/'
else
        cmp     word [r12+r13*2+12], '/'
end if
        je      .parseurl_checkpath
        ; otherwise, we got // as start, do our authority
        add     r13d, 2
        mov     rdi, r12
        mov     esi, '/'
        mov     edx, r13d
        call    string$indexof_charcode_ofs
        mov     r15, rax
        cmp     rax, 0
        jge     .parseurl_setauthority
        mov     rdi, r12
        mov     esi, '?'
        mov     edx, r13d
        call    string$indexof_charcode_ofs
        mov     r15, rax
        cmp     rax, 0
        jge     .parseurl_setauthority
        mov     r15d, r14d
calign
.parseurl_setauthority:
        mov     rdi, r12
        mov     esi, r13d
        mov     edx, r15d
        call    string$substring
        mov     rdi, [rsp+url_host_ofs]
        mov     [rsp+url_host_ofs], rax
        call    heap$free
        mov     rdi, [rsp+url_authority_ofs]
        call    heap$free
        mov     rdi, [rsp+url_host_ofs]
        call    string$copy
        mov     [rsp+url_authority_ofs], rax
        mov     rdi, rax
        mov     esi, '@'
        call    string$indexof_charcode
        cmp     rax, 0
        jl      .parseurl_setauthinfo_empty
        mov     [rsp+80], rax
        mov     rdi, [rsp+url_authority_ofs]
        mov     esi, 0
        mov     edx, eax
        call    string$substr
        mov     rdi, [rsp+url_authinfo_ofs]
        mov     [rsp+url_authinfo_ofs], rax
        call    heap$free
        mov     rdi, [rsp+url_authority_ofs]
        mov     esi, [rsp+80]
        mov     rdx, -1
        add     esi, 1
        call    string$substr
        mov     rdi, [rsp+url_host_ofs]
        mov     [rsp+url_host_ofs], rax
        call    heap$free
        jmp     .parseurl_checkhost
calign
.parseurl_setauthinfo_empty:
        call    string$new
        mov     rdi, [rsp+url_authinfo_ofs]
        mov     [rsp+url_authinfo_ofs], rax
        call    heap$free
calign
.parseurl_checkhost:
        mov     rdi, [rsp+url_host_ofs]
        cmp     qword [rdi], 0
        je      .parseurl_checkport
        ; hosts need to be lowercase, authority doesn't
        call    string$to_lower_inplace
        mov     rdi, [rsp+url_host_ofs]
        ; if we were going to check for IPv6 hosts, here would be the place if host.charat(0) == '['
        mov     esi, ':'
        call    string$indexof_charcode
        cmp     rax, 0
        jl      .parseurl_checkport
        mov     [rsp+80], rax
        mov     rdi, [rsp+url_host_ofs]
        mov     esi, eax
        add     esi, 1
        cmp     qword [rdi], rsi
        jbe     .parseurl_chophost
        mov     rdx, -1
        call    string$substr
        mov     [rsp+88], rax
        mov     rdi, rax
        call    string$to_unsigned
        mov     rdi, [rsp+88]
        mov     [rsp+url_port_ofs], rax
        call    heap$free
calign
.parseurl_chophost:
        mov     rdi, [rsp+url_host_ofs]
        xor     esi, esi
        mov     rdx, [rsp+80]
        call    string$substr
        mov     rdi, [rsp+url_host_ofs]
        mov     [rsp+url_host_ofs], rax
        call    heap$free
calign
.parseurl_checkport:
        mov     eax, [rsp+url_port_ofs]
        cmp     eax, 0
        je      .parseurl_defaultport
        jl      .badport_nullret
        cmp     eax, 65536
        jae     .badport_nullret
calign
.parseurl_checkport_okay:
        mov     r13d, r15d
        mov     rdi, [rsp+url_authority_ofs]
        cmp     qword [rdi], 0
        je      .parseurl_checkpath
        call    string$new
        mov     rdi, [rsp+url_path_ofs]
        mov     [rsp+url_path_ofs], rax
        call    heap$free
calign
.parseurl_checkpath:
        cmp     r13d, r14d
        jae     .parseurl_checkpath_qonly
if string_bits = 32
        cmp     dword [r12+r13*4+8], '/'
else
        cmp     word [r12+r13*2+8], '/'
end if
        jne     .parseurl_relativepath
        mov     rdi, r12
        mov     esi, r13d
        mov     edx, r14d
        call    string$substring
        mov     rdi, [rsp+url_path_ofs]
        mov     [rsp+url_path_ofs], rax
        call    heap$free
        jmp     .parseurl_construct
calign
.parseurl_defaultport:
        mov     rdi, [url$schemeportmap]
        mov     rsi, [rsp+url_protocol_ofs]
        call    stringmap$find_value
        test    eax, eax
        jz      .badport_nullret
        mov     [rsp+url_port_ofs], edx
        jmp     .parseurl_checkport_okay
cleartext .emptystr, ''
cleartext .slash, '/'
cleartext .slashslash, '//'
cleartext .dotslash, './'
cleartext .slashdot, '/.'
cleartext .slashdotslash, '/./'
cleartext .slashdotdot, '/..'
cleartext .slashdotdotslash, '/../'
cleartext .percentdoublezero, '%00'
calign
.parseurl_relativepath:
        mov     rdi, [rsp+url_path_ofs]
        cmp     qword [rdi], 0
        je      .parseurl_setpath
        ; otherwise, dealing with a relative path
        mov     rsi, .slash
        call    string$last_indexof
        cmp     rax, 0
        jl      .parseurl_setpath
        ; otherwise, substr that + the new
        mov     rdi, [rsp+url_path_ofs]
        xor     esi, esi
        mov     rdx, rax
        add     rdx, 1
        call    string$substr
        mov     [rsp+80], rax
        mov     rdi, r12
        mov     esi, r13d
        mov     edx, r14d
        call    string$substring
        mov     [rsp+88], rax
        mov     rdi, [rsp+80]
        mov     rsi, rax
        call    string$concat
        mov     rdi, [rsp+url_path_ofs]
        mov     [rsp+url_path_ofs], rax
        call    heap$free
        mov     rdi, [rsp+80]
        call    heap$free
        mov     rdi, [rsp+88]
        call    heap$free
        ; so now, proceed to construct where we'll fixup any nasties in the path
        jmp     .parseurl_construct
calign
.parseurl_checkpath_qonly:
        cmp     dword [rsp+76], 1
        jne     .parseurl_construct
        mov     rdi, [rsp+url_path_ofs]
        cmp     qword [rdi], 0
        je      .parseurl_construct
        mov     esi, '/'
        call    string$indexof_charcode
        xor     ecx, ecx
        cmp     rax, 0
        cmovl   rax, rcx
        mov     rdi, [rsp+url_path_ofs]
        xor     esi, esi
        mov     rdx, rax
        call    string$substr
        mov     rdi, [rsp+url_path_ofs]
        mov     [rsp+url_path_ofs], rax
        call    heap$free
        mov     rdi, [rsp+url_path_ofs]
        mov     rsi, .slash
        call    string$concat
        mov     rdi, [rsp+url_path_ofs]
        mov     [rsp+url_path_ofs], rax
        call    heap$free
        jmp     .parseurl_construct
calign
.parseurl_setpath:
        mov     rdi, [rsp+url_authority_ofs]
        cmp     qword [rdi], 0
        je      .parseurl_setpath_nosep
        mov     rdi, r12
        mov     esi, r13d
        mov     edx, r14d
        call    string$substring
        mov     [rsp+88], rax
        mov     rdi, .slash
        mov     rsi, rax
        call    string$concat
        mov     rdi, [rsp+url_path_ofs]
        mov     [rsp+url_path_ofs], rax
        call    heap$free
        mov     rdi, [rsp+88]
        call    heap$free
        jmp     .parseurl_construct
calign
.parseurl_setpath_nosep:
        mov     rdi, r12
        mov     esi, r13d
        mov     edx, r14d
        call    string$substring
        mov     rdi, [rsp+url_path_ofs]
        mov     [rsp+url_path_ofs], rax
        call    heap$free
calign
.parseurl_construct:
        ; so before we construct our final return url object:
        ; first, we have to urldecode the relevant bits of our url

        ; if the path contains %00, die
        mov     rdi, [rsp+url_path_ofs]
        mov     rsi, .percentdoublezero
        call    string$indexof
        cmp     rax, 0
        jge     .badport_nullret

        mov     rdi, [rsp+url_path_ofs]
        call    url$decode_path
        mov     rdi, [rsp+url_path_ofs]
        mov     [rsp+url_path_ofs], rax
        call    heap$free

if defined url_querystring_decode
        mov     rdi, [rsp+url_query_ofs]
        call    url$decode
        mov     rdi, [rsp+url_query_ofs]
        mov     [rsp+url_query_ofs], rax
        call    heap$free
end if

        mov     rdi, [rsp+url_ref_ofs]
        call    url$decode
        mov     rdi, [rsp+url_ref_ofs]
        mov     [rsp+url_ref_ofs], rax
        call    heap$free
        ; make sure the path is sane (removing ../../.. etc)

        ; string$replace is nasty, so only do this if it actually contains a violator
        xor     r15d, r15d
        mov     rdi, [rsp+url_path_ofs]
        mov     rsi, .slashslash
        call    string$indexof
        cmp     rax, 0
        jl      .parseurl_sanepath
        mov     rdi, [rsp+url_path_ofs]
        mov     rsi, .slashslash
        mov     rdx, .slash
        call    string$replace
        mov     rdi, [rsp+url_path_ofs]
        mov     [rsp+url_path_ofs], rax
        call    heap$free
calign
.parseurl_sanepath:
        mov     rdi, [rsp+url_path_ofs]
        mov     rsi, .slashdotslash
        call    string$indexof
        cmp     rax, 0
        jl      .parseurl_sanepath2
        mov     [rsp+80], rax
        mov     rdi, [rsp+url_path_ofs]
        xor     esi, esi
        mov     rdx, rax
        call    string$substr
        mov     [rsp+88], rax
        mov     rdi, [rsp+url_path_ofs]
        mov     rsi, [rsp+80]
        mov     rdx, -1
        add     rsi, 2
        call    string$substr
        mov     [rsp+80], rax
        mov     rdi, [rsp+88]
        mov     rsi, rax
        call    string$concat
        mov     rdi, [rsp+url_path_ofs]
        mov     [rsp+url_path_ofs], rax
        call    heap$free
        mov     rdi, [rsp+88]
        call    heap$free
        mov     rdi, [rsp+80]
        call    heap$free
        jmp     .parseurl_sanepath
calign
.parseurl_sanepath2:
        mov     rdi, [rsp+url_path_ofs]
        mov     rsi, .slashdotdotslash
        mov     edx, r15d
        call    string$indexof_ofs
        mov     r15, rax
        cmp     rax, 0
        jl      .parseurl_sanepath3
        test    rax, rax
        jz      .parseurl_sanepath2_skip
        mov     rdi, [rsp+url_path_ofs]
        mov     rsi, .slash
        mov     edx, r15d
        sub     edx, 1
        call    string$last_indexof_ofs
        mov     r14, rax
        cmp     rax, 0
        jl      .parseurl_sanepath2_skip
        mov     rdi, [rsp+url_path_ofs]
        mov     rsi, .slashdotdotslash
        mov     edx, r14d
        call    string$indexof_ofs
        test    rax, rax
        jz      .parseurl_sanepath2_skip
        mov     rdi, [rsp+url_path_ofs]
        xor     esi, esi
        mov     edx, r14d
        call    string$substr
        mov     [rsp+80], rax
        mov     rdi, [rsp+url_path_ofs]
        mov     esi, r15d
        mov     rdx, -1
        add     esi, 3
        call    string$substr
        mov     [rsp+88], rax
        mov     rdi, [rsp+80]
        mov     rsi, rax
        call    string$concat
        mov     rdi, [rsp+url_path_ofs]
        mov     [rsp+url_path_ofs], rax
        call    heap$free
        mov     rdi, [rsp+80]
        call    heap$free
        mov     rdi, [rsp+88]
        call    heap$free
        xor     r15d, r15d
        jmp     .parseurl_sanepath2
calign
.parseurl_sanepath2_skip:
        add     r15d, 3
        jmp     .parseurl_sanepath2
calign
.parseurl_sanepath3:
        mov     rdi, [rsp+url_path_ofs]
        mov     rsi, .slashdotdot
        call    string$ends_with
        test    rax, rax
        jz      .parseurl_sanepath4
        mov     rdi, [rsp+url_path_ofs]
        mov     rsi, .slashdotdot
        call    string$indexof
        mov     r15, rax
        mov     rdi, [rsp+url_path_ofs]
        mov     rsi, .slash
        mov     edx, r15d
        sub     edx, 1
        call    string$last_indexof_ofs
        cmp     rax, 0
        jl      .parseurl_sanepath4
        mov     rdi, [rsp+url_path_ofs]
        xor     esi, esi
        mov     rdx, rax
        add     rdx, 1
        call    string$substr
        mov     rdi, [rsp+url_path_ofs]
        mov     [rsp+url_path_ofs], rax
        call    heap$free
        jmp     .parseurl_sanepath3
calign
.parseurl_sanepath4:
        mov     rdi, [rsp+url_path_ofs]
        mov     rsi, .dotslash
        call    string$starts_with
        test    rax, rax
        jz      .parseurl_sanepath5
        mov     rdi, [rsp+url_path_ofs]
        mov     esi, 2
        mov     rdx, -1
        cmp     qword [rdi], 2
        jbe     .parseurl_sanepath5
        call    string$substr
        mov     rdi, [rsp+url_path_ofs]
        mov     [rsp+url_path_ofs], rax
        call    heap$free
calign
.parseurl_sanepath5:
        mov     rdi, [rsp+url_path_ofs]
        mov     rsi, .slashdot
        call    string$ends_with
        test    rax, rax
        jz      .parseurl_sanepath6
        mov     rdi, [rsp+url_path_ofs]
        xor     esi, esi
        mov     rdx, [rdi]
        sub     rdx, 1
        call    string$substr
        mov     rdi, [rsp+url_path_ofs]
        mov     [rsp+url_path_ofs], rax
        call    heap$free
calign
.parseurl_sanepath6:
        ; any starting backpath references can simply be deleted
        mov     rdi, [rsp+url_path_ofs]
        mov     rsi, .slashdotdot
        call    string$starts_with
        test    rax, rax
        jz      .parseurl_setfile
        mov     rdi, [rsp+url_path_ofs]
        mov     esi, 3
        mov     rdx, -1
        call    string$substr
        mov     rdi, [rsp+url_path_ofs]
        mov     [rsp+url_path_ofs], rax
        call    heap$free
        jmp     .parseurl_sanepath6
calign
.parseurl_setfile:
        ; and last but not least, we have to set our file to whatever the last bit of the path is
        mov     rdi, [rsp+url_path_ofs]
        mov     rsi, .slash
        call    string$last_indexof
        cmp     rax, 0
        jl      .parseurl_final
        mov     rdi, [rsp+url_path_ofs]
        mov     rsi, rax
        mov     rdx, -1
        add     rsi, 1
        call    string$substr
        mov     rdi, [rsp+url_file_ofs]
        mov     [rsp+url_file_ofs], rax
        call    heap$free
calign
.parseurl_final:
        mov     edi, url_size
        call    heap$alloc_clear
        mov     rbx, rax
        mov     rdi, rax
        mov     rsi, rsp
        mov     edx, url_size - 8       ; don't write over the user offset (leave it at zero)
        call    memcpy
        mov     rax, rbx
        add     rsp, 128
        pop     r15 r14 r13 r12 rbx
        epilog
calign
.badport_nullret:
        ; free all of our strings
        mov     rdi, [rsp+url_host_ofs]
        call    heap$free
        mov     rdi, [rsp+url_file_ofs]
        call    heap$free
        mov     rdi, [rsp+url_query_ofs]
        call    heap$free
        mov     rdi, [rsp+url_authority_ofs]
        call    heap$free
        mov     rdi, [rsp+url_path_ofs]
        call    heap$free
        mov     rdi, [rsp+url_authinfo_ofs]
        call    heap$free
        mov     rdi, [rsp+url_ref_ofs]
        call    heap$free
        ; fall through
calign
.protocol_nullret:
        mov     rdi, [rsp+url_protocol_ofs]
        call    heap$free
        ; fallthrough
calign
.initial_nullret:
        xor     eax, eax
        add     rsp, 128
        pop     r15 r14 r13 r12 rbx
        epilog

end if


if used url$destroy | defined include_everything
        ; single argument in rdi: the url object to destroy
falign
url$destroy:
        prolog  url$destroy
        push    rbx
        mov     rbx, rdi
        mov     rdi, [rdi+url_protocol_ofs]
        call    heap$free
        mov     rdi, [rbx+url_host_ofs]
        call    heap$free
        mov     rdi, [rbx+url_file_ofs]
        call    heap$free
        mov     rdi, [rbx+url_query_ofs]
        call    heap$free
        mov     rdi, [rbx+url_authority_ofs]
        call    heap$free
        mov     rdi, [rbx+url_path_ofs]
        call    heap$free
        mov     rdi, [rbx+url_authinfo_ofs]
        call    heap$free
        mov     rdi, [rbx+url_ref_ofs]
        call    heap$free
        mov     rdi, rbx
        call    heap$free
        pop     rbx
        epilog

end if

if used url$equals | defined include_everything
        ; two arguments: rdi == url object, rsi == other url object to compare
falign
url$equals:
        prolog  url$equals
        push    r12 r13
        mov     r12, rdi
        mov     r13, rsi
        mov     eax, [r12+url_port_ofs]
        cmp     eax, [r13+url_port_ofs]
        jne     .nope
        mov     rdi, [r12+url_protocol_ofs]
        mov     rsi, [r13+url_protocol_ofs]
        call    string$equals
        test    eax, eax
        jz      .nope
        mov     rdi, [r12+url_host_ofs]
        mov     rsi, [r13+url_host_ofs]
        call    string$equals
        test    eax, eax
        jz      .nope
        mov     rdi, [r12+url_file_ofs]
        mov     rsi, [r13+url_file_ofs]
        call    string$equals
        test    eax, eax
        jz      .nope
        mov     rdi, [r12+url_query_ofs]
        mov     rsi, [r13+url_query_ofs]
        call    string$equals
        test    eax, eax
        jz      .nope
        mov     rdi, [r12+url_path_ofs]
        mov     rsi, [r13+url_path_ofs]
        call    string$equals
        test    eax, eax
        jz      .nope
        mov     eax, 1
        pop     r13 r12
        epilog
calign
.nope:
        xor     eax, eax
        pop     r13 r12
        epilog

end if

if used url$debug | defined include_everything
        ; single argument in rdi: a url object
        ; just dumps everything out to stdout
falign
url$debug:
        prolog  url$debug
        push    rbx
        mov     rbx, rdi
        mov     rdi, .pref
        call    string$to_stdoutln
        mov     rdi, .s1
        call    string$to_stdout
        mov     rdi, [rbx+url_protocol_ofs]
        call    string$to_stdoutln
        mov     rdi, .s2
        call    string$to_stdout
        mov     rdi, [rbx+url_host_ofs]
        call    string$to_stdoutln
        mov     rdi, .s3
        call    string$to_stdout
        mov     rdi, [rbx+url_port_ofs]
        mov     esi, 10
        call    string$from_unsigned
        push    rax
        mov     rdi, rax
        call    string$to_stdoutln
        pop     rdi
        call    heap$free
        mov     rdi, .s4
        call    string$to_stdout
        mov     rdi, [rbx+url_file_ofs]
        call    string$to_stdoutln
        mov     rdi, .s5
        call    string$to_stdout
        mov     rdi, [rbx+url_query_ofs]
        call    string$to_stdoutln
        mov     rdi, .s6
        call    string$to_stdout
        mov     rdi, [rbx+url_authority_ofs]
        call    string$to_stdoutln
        mov     rdi, .s7
        call    string$to_stdout
        mov     rdi, [rbx+url_path_ofs]
        call    string$to_stdoutln
        mov     rdi, .s8
        call    string$to_stdout
        mov     rdi, [rbx+url_authinfo_ofs]
        call    string$to_stdoutln
        mov     rdi, .s9
        call    string$to_stdout
        mov     rdi, [rbx+url_ref_ofs]
        call    string$to_stdoutln
        pop     rbx
        epilog
cleartext .pref, '::URL debug::'
cleartext .s1, ' protocol: '
cleartext .s2, '     host: '
cleartext .s3, '     port: '
cleartext .s4, '     file: '
cleartext .s5, '    query: '
cleartext .s6, 'authority: '
cleartext .s7, '     path: '
cleartext .s8, ' authinfo: '
cleartext .s9, '      ref: '

end if



if used url$setprotocol | defined include_everything
        ; two arguments: rdi == url object, rsi == string protocol (we copy it)
falign
url$setprotocol:
        prolog  url$setprotocol
        push    rdi rsi
        mov     rdi, [rdi+url_protocol_ofs]
        call    heap$free
        pop     rdi
        call    string$copy
        pop     rdi
        mov     [rdi+url_protocol_ofs], rax
        epilog

end if

if used url$sethost | defined include_everything
        ; two arguments: rdi == url object, rsi == string host (we copy it)
falign
url$sethost:
        prolog  url$sethost
        push    rdi rsi
        mov     rdi, [rdi+url_host_ofs]
        call    heap$free
        pop     rdi
        call    string$copy
        pop     rdi
        mov     [rdi+url_host_ofs], rax
        epilog

end if

if used url$setport | defined include_everything
        ; two arguments: rdi == url object, esi == port (integer)
falign
url$setport:
        prolog  url$setport
        mov     [rdi+url_port_ofs], esi
        epilog

end if

if used url$setfile | defined include_everything
        ; two arguments: rdi == url object, rsi == string file (we copy it)
falign
url$setfile:
        prolog  url$setfile
        push    rdi rsi
        mov     rdi, [rdi+url_file_ofs]
        call    heap$free
        pop     rdi
        call    string$copy
        pop     rdi
        mov     [rdi+url_file_ofs], rax
        epilog

end if

if used url$setquery | defined include_everything
        ; two arguments: rdi == url object, rsi == string query (we copy it)
falign
url$setquery:
        prolog  url$setquery
        push    rdi rsi
        mov     rdi, [rdi+url_query_ofs]
        call    heap$free
        pop     rdi
        call    string$copy
        pop     rdi
        mov     [rdi+url_query_ofs], rax
        epilog

end if

if used url$setauthority | defined include_everything
        ; two arguments: rdi == url object, rsi == string authority (we copy it)
falign
url$setauthority:
        prolog  url$setauthority
        push    rdi rsi
        mov     rdi, [rdi+url_authority_ofs]
        call    heap$free
        pop     rdi
        call    string$copy
        pop     rdi
        mov     [rdi+url_authority_ofs], rax
        epilog

end if

if used url$setpath | defined include_everything
        ; two arguments: rdi == url object, rsi == string path (we copy it)
falign
url$setpath:
        prolog  url$setpath
        push    rdi rsi
        mov     rdi, [rdi+url_path_ofs]
        call    heap$free
        pop     rdi
        call    string$copy
        pop     rdi
        mov     [rdi+url_path_ofs], rax
        epilog

end if

if used url$setauthinfo | defined include_everything
        ; two arguments: rdi == url object, rsi == string authinfo (we copy it)
falign
url$setauthinfo:
        prolog  url$setauthinfo
        push    rdi rsi
        mov     rdi, [rdi+url_authinfo_ofs]
        call    heap$free
        pop     rdi
        call    string$copy
        pop     rdi
        mov     [rdi+url_authinfo_ofs], rax
        epilog

end if

if used url$setref | defined include_everything
        ; two arguments: rdi == url object, rsi == string ref (we copy it)
falign
url$setref:
        prolog  url$setref
        push    rdi rsi
        mov     rdi, [rdi+url_ref_ofs]
        call    heap$free
        pop     rdi
        call    string$copy
        pop     rdi
        mov     [rdi+url_ref_ofs], rax
        epilog

end if

if used url$topreface | defined include_everything
        ; single argument in rdi: url object
        ; returns external representation of the url object suitable for an HTTP request preface
falign
url$topreface:
        prolog  url$topreface
        push    rbx r12
        mov     rbx, rdi
        call    buffer$new
        mov     r12, rax
        mov     rsi, [rbx+url_path_ofs]
        cmp     qword [rsi], 0
        je      .slashonly
        ; url encode the path:
        mov     rdi, rsi
        mov     esi, 1          ; path encoding
        call    url$encode
        push    rax
        mov     rdi, r12
        mov     rsi, rax
        call    buffer$append_rawstring
        pop     rdi
        call    heap$free
        jmp     .checkquery
cleartext .slash, '/'
calign
.slashonly:
        mov     rdi, r12
        mov     rsi, .slash
        call    buffer$append_rawstring
calign
.checkquery:
        mov     rsi, [rbx+url_query_ofs]
        cmp     qword [rsi], 0
        je      .checkref
        mov     rdi, r12
        mov     rsi, .questionmark
        call    buffer$append_rawstring

if defined url_querystring_decode
        ; url encode the query
        mov     rdi, [rbx+url_query_ofs]
        xor     esi, esi
        call    url$encode
        push    rax
        mov     rdi, r12
        mov     rsi, rax
        call    buffer$append_rawstring
        pop     rdi
        call    heap$free
else
        mov     rdi, r12
        mov     rsi, [rbx+url_query_ofs]
        call    buffer$append_rawstring

end if
calign
.checkref:
        mov     rsi, [rbx+url_ref_ofs]
        cmp     qword [rsi], 0
        je      .alldone
        mov     rdi, r12
        mov     rsi, .hash
        call    buffer$append_rawstring
        ; url encode the ref (aka fragment)
        mov     rdi, [rbx+url_ref_ofs]
        xor     esi, esi
        call    url$encode
        push    rax
        mov     rdi, r12
        mov     rsi, rax
        call    buffer$append_rawstring
        pop     rdi
        call    heap$free
calign
.alldone:
        mov     rdi, [r12+buffer_itself_ofs]
        mov     rsi, [r12+buffer_length_ofs]
if string_bits = 32
        call    string$from_utf32
else
        call    string$from_utf16
end if
        mov     rdi, r12
        mov     r12, rax
        call    buffer$destroy
        mov     rax, r12
        pop     r12 rbx
        epilog
cleartext .questionmark, '?'
cleartext .hash, '#'

end if

if used url$tostring | defined include_everything
        ; single argument in rdi: url object
        ; returns external representation of the url object in a new string (note: does not add authinfo)
falign
url$tostring:
        prolog  url$tostring
        push    rbx r12
        mov     rbx, rdi
        call    buffer$new
        mov     r12, rax
        mov     rdi, rax
        mov     rsi, [rbx+url_protocol_ofs]
        call    buffer$append_rawstring
        mov     rsi, [rbx+url_authority_ofs]
        cmp     qword [rsi], 0
        je      .skipauthority
        mov     rdi, r12
        mov     rsi, .colonslashslash
        call    buffer$append_rawstring
        mov     rdi, r12
        mov     rsi, [rbx+url_authority_ofs]
        call    buffer$append_rawstring
        jmp     .checkpath
calign
.skipauthority:
        mov     rdi, r12
        mov     rsi, .colon
        call    buffer$append_rawstring
calign
.checkpath:
        mov     rsi, [rbx+url_path_ofs]
        cmp     qword [rsi], 0
        je      .checkquery
        ; url encode the path:
        mov     rdi, rsi
        mov     esi, 1          ; path encoding
        call    url$encode
        push    rax
        mov     rdi, r12
        mov     rsi, rax
        call    buffer$append_rawstring
        pop     rdi
        call    heap$free
calign
.checkquery:
        mov     rsi, [rbx+url_query_ofs]
        cmp     qword [rsi], 0
        je      .checkref
        mov     rdi, r12
        mov     rsi, .questionmark
        call    buffer$append_rawstring

if defined url_querystring_decode
        ; url encode the query
        mov     rdi, [rbx+url_query_ofs]
        xor     esi, esi
        call    url$encode
        push    rax
        mov     rdi, r12
        mov     rsi, rax
        call    buffer$append_rawstring
        pop     rdi
        call    heap$free
else
        mov     rdi, r12
        mov     rsi, [rbx+url_query_ofs]
        call    buffer$append_rawstring
end if
calign
.checkref:
        mov     rsi, [rbx+url_ref_ofs]
        cmp     qword [rsi], 0
        je      .alldone
        mov     rdi, r12
        mov     rsi, .hash
        call    buffer$append_rawstring
        ; url encode the ref (aka fragment)
        mov     rdi, [rbx+url_ref_ofs]
        xor     esi, esi
        call    url$encode
        push    rax
        mov     rdi, r12
        mov     rsi, rax
        call    buffer$append_rawstring
        pop     rdi
        call    heap$free
calign
.alldone:
        mov     rdi, [r12+buffer_itself_ofs]
        mov     rsi, [r12+buffer_length_ofs]
if string_bits = 32
        call    string$from_utf32
else
        call    string$from_utf16
end if
        mov     rdi, r12
        mov     r12, rax
        call    buffer$destroy
        mov     rax, r12
        pop     r12 rbx
        epilog
cleartext .colonslashslash, '://'
cleartext .colon, ':'
cleartext .questionmark, '?'
cleartext .hash, '#'

end if



if used url$encode | defined include_everything
        ; two arguments: rdi == string containing [possibly] invalid url characters, bool in esi as to whether it is a path or not
        ; returns a new string in rax with the offenders converted to %XX format
        ; NOTE: this is not terribly efficient, because it converts to UTF8 and back
falign
url$encode:
        prolog  url$encode
        cmp     qword [rdi], 0
        je      .emptystring
        push    rbx r12 r13 r14 r15
        mov     rbx, rdi
        mov     r15d, esi
        call    string$utf8_length
        mov     r12, rax
        add     rax, 0xf
        and     rax, not 0xf
        sub     rsp, rax
        mov     rdi, rbx
        mov     rsi, rsp
        call    string$to_utf8
        call    buffer$new
        mov     r13, rax
        ; determine the buffer length we need
        mov     r14, r12
        mov     rdi, rsp
        mov     rcx, r12
calign
.lengthscan:
        mov     rdx, r14
        movzx   eax, byte [rdi]
        add     rdx, 2
        cmp     byte [rax*4+.enctable], '%'
        cmove   r14, rdx
        add     rdi, 1
        sub     rcx, 1
        jnz     .lengthscan
        mov     rdi, r13
        mov     rsi, r14
        call    buffer$reserve
        mov     rdi, [r13+buffer_itself_ofs]
        mov     rsi, rsp
        mov     rcx, r12
calign
.doit:
        movzx   eax, byte [rsi]
        add     rsi, 1
        mov     r8d, [rax*4+.pathenctable]
        mov     eax, [rax*4+.enctable]
        test    r15d, r15d
        cmovnz  eax, r8d
        cmp     al, '%'
        je      .doit_perc
        mov     byte [rdi], al
        add     rdi, 1
        sub     rcx, 1
        jnz     .doit
        jmp     .done
calign
.doit_perc:
        mov     [rdi], eax
        add     rdi, 3
        sub     rcx, 1
        jnz     .doit
calign
.done:
        mov     rsi, rdi
        mov     rdi, [r13+buffer_itself_ofs]
        sub     rsi, rdi
        call    string$from_utf8
        mov     r14, rax
        mov     rdi, r13
        call    buffer$destroy
        mov     rax, r12
        add     rax, 0xf
        and     rax, not 0xf
        add     rsp, rax
        mov     rax, r14
        pop     r15 r14 r13 r12 rbx
        epilog
calign
.emptystring:
        call    string$new
        epilog
dalign
.enctable:
        db      '%00',0,'%01',0,'%02',0,'%03',0,'%04',0,'%05',0,'%06',0,'%07',0
        db      '%08',0,'%09',0,'%0A',0,'%0B',0,'%0C',0,'%0D',0,'%0E',0,'%0F',0
        db      '%10',0,'%11',0,'%12',0,'%13',0,'%14',0,'%15',0,'%16',0,'%17',0
        db      '%18',0,'%19',0,'%1A',0,'%1B',0,'%1C',0,'%1D',0,'%1E',0,'%1F',0
        db      '+',0,0,0,'!',0,0,0,'%22',0,'%23',0,'$',0,0,0,'%25',0,'%26',0,0x27,0,0,0
        db      '(',0,0,0,')',0,0,0,'*',0,0,0,'%2B',0,',',0,0,0,'-',0,0,0,'.',0,0,0,'%2F',0
        db      '0',0,0,0,'1',0,0,0,'2',0,0,0,'3',0,0,0,'4',0,0,0,'5',0,0,0,'6',0,0,0,'7',0,0,0
        db      '8',0,0,0,'9',0,0,0,'%3A',0,'%3B',0,'%3C',0,'%3D',0,'%3E',0,'%3F',0
        db      '%40',0,'A',0,0,0,'B',0,0,0,'C',0,0,0,'D',0,0,0,'E',0,0,0,'F',0,0,0,'G',0,0,0
        db      'H',0,0,0,'I',0,0,0,'J',0,0,0,'K',0,0,0,'L',0,0,0,'M',0,0,0,'N',0,0,0,'O',0,0,0
        db      'P',0,0,0,'Q',0,0,0,'R',0,0,0,'S',0,0,0,'T',0,0,0,'U',0,0,0,'V',0,0,0,'W',0,0,0
        db      'X',0,0,0,'Y',0,0,0,'Z',0,0,0,'%5B',0,'%5C',0,'%5D',0,'%5E',0,'_',0,0,0
        db      '%60',0,'a',0,0,0,'b',0,0,0,'c',0,0,0,'d',0,0,0,'e',0,0,0,'f',0,0,0,'g',0,0,0
        db      'h',0,0,0,'i',0,0,0,'j',0,0,0,'k',0,0,0,'l',0,0,0,'m',0,0,0,'n',0,0,0,'o',0,0,0
        db      'p',0,0,0,'q',0,0,0,'r',0,0,0,'s',0,0,0,'t',0,0,0,'u',0,0,0,'v',0,0,0,'w',0,0,0
        db      'x',0,0,0,'y',0,0,0,'z',0,0,0,'%7B',0,'%7C',0,'%7D',0,'%7E',0,'%7F',0
        db      '%80',0,'%81',0,'%82',0,'%83',0,'%84',0,'%85',0,'%86',0,'%87',0
        db      '%88',0,'%89',0,'%8A',0,'%8B',0,'%8C',0,'%8D',0,'%8E',0,'%8F',0
        db      '%90',0,'%91',0,'%92',0,'%93',0,'%94',0,'%95',0,'%96',0,'%97',0
        db      '%98',0,'%99',0,'%9A',0,'%9B',0,'%9C',0,'%9D',0,'%9E',0,'%9F',0
        db      '%A0',0,'%A1',0,'%A2',0,'%A3',0,'%A4',0,'%A5',0,'%A6',0,'%A7',0
        db      '%A8',0,'%A9',0,'%AA',0,'%AB',0,'%AC',0,'%AD',0,'%AE',0,'%AF',0
        db      '%B0',0,'%B1',0,'%B2',0,'%B3',0,'%B4',0,'%B5',0,'%B6',0,'%B7',0
        db      '%B8',0,'%B9',0,'%BA',0,'%BB',0,'%BC',0,'%BD',0,'%BE',0,'%BF',0
        db      '%C0',0,'%C1',0,'%C2',0,'%C3',0,'%C4',0,'%C5',0,'%C6',0,'%C7',0
        db      '%C8',0,'%C9',0,'%CA',0,'%CB',0,'%CC',0,'%CD',0,'%CE',0,'%CF',0
        db      '%D0',0,'%D1',0,'%D2',0,'%D3',0,'%D4',0,'%D5',0,'%D6',0,'%D7',0
        db      '%D8',0,'%D9',0,'%DA',0,'%DB',0,'%DC',0,'%DD',0,'%DE',0,'%DF',0
        db      '%E0',0,'%E1',0,'%E2',0,'%E3',0,'%E4',0,'%E5',0,'%E6',0,'%E7',0
        db      '%E8',0,'%E9',0,'%EA',0,'%EB',0,'%EC',0,'%ED',0,'%EE',0,'%EF',0
        db      '%F0',0,'%F1',0,'%F2',0,'%F3',0,'%F4',0,'%F5',0,'%F6',0,'%F7',0
        db      '%F8',0,'%F9',0,'%FA',0,'%FB',0,'%FC',0,'%FD',0,'%FE',0,'%FF'
dalign
.pathenctable:
        db      '%00',0,'%01',0,'%02',0,'%03',0,'%04',0,'%05',0,'%06',0,'%07',0
        db      '%08',0,'%09',0,'%0A',0,'%0B',0,'%0C',0,'%0D',0,'%0E',0,'%0F',0
        db      '%10',0,'%11',0,'%12',0,'%13',0,'%14',0,'%15',0,'%16',0,'%17',0
        db      '%18',0,'%19',0,'%1A',0,'%1B',0,'%1C',0,'%1D',0,'%1E',0,'%1F',0
        db      '%20',0,'!',0,0,0,'%22',0,'%23',0,'$',0,0,0,'%25',0,'%26',0,0x27,0,0,0
        db      '(',0,0,0,')',0,0,0,'*',0,0,0,'+',0,0,0,',',0,0,0,'-',0,0,0,'.',0,0,0,'/',0,0,0
        db      '0',0,0,0,'1',0,0,0,'2',0,0,0,'3',0,0,0,'4',0,0,0,'5',0,0,0,'6',0,0,0,'7',0,0,0
        db      '8',0,0,0,'9',0,0,0,'%3A',0,'%3B',0,'%3C',0,'=',0,0,0,'%3E',0,'%3F',0
        db      '%40',0,'A',0,0,0,'B',0,0,0,'C',0,0,0,'D',0,0,0,'E',0,0,0,'F',0,0,0,'G',0,0,0
        db      'H',0,0,0,'I',0,0,0,'J',0,0,0,'K',0,0,0,'L',0,0,0,'M',0,0,0,'N',0,0,0,'O',0,0,0
        db      'P',0,0,0,'Q',0,0,0,'R',0,0,0,'S',0,0,0,'T',0,0,0,'U',0,0,0,'V',0,0,0,'W',0,0,0
        db      'X',0,0,0,'Y',0,0,0,'Z',0,0,0,'%5B',0,'%5C',0,'%5D',0,'%5E',0,'_',0,0,0
        db      '%60',0,'a',0,0,0,'b',0,0,0,'c',0,0,0,'d',0,0,0,'e',0,0,0,'f',0,0,0,'g',0,0,0
        db      'h',0,0,0,'i',0,0,0,'j',0,0,0,'k',0,0,0,'l',0,0,0,'m',0,0,0,'n',0,0,0,'o',0,0,0
        db      'p',0,0,0,'q',0,0,0,'r',0,0,0,'s',0,0,0,'t',0,0,0,'u',0,0,0,'v',0,0,0,'w',0,0,0
        db      'x',0,0,0,'y',0,0,0,'z',0,0,0,'%7B',0,'%7C',0,'%7D',0,'%7E',0,'%7F',0
        db      '%80',0,'%81',0,'%82',0,'%83',0,'%84',0,'%85',0,'%86',0,'%87',0
        db      '%88',0,'%89',0,'%8A',0,'%8B',0,'%8C',0,'%8D',0,'%8E',0,'%8F',0
        db      '%90',0,'%91',0,'%92',0,'%93',0,'%94',0,'%95',0,'%96',0,'%97',0
        db      '%98',0,'%99',0,'%9A',0,'%9B',0,'%9C',0,'%9D',0,'%9E',0,'%9F',0
        db      '%A0',0,'%A1',0,'%A2',0,'%A3',0,'%A4',0,'%A5',0,'%A6',0,'%A7',0
        db      '%A8',0,'%A9',0,'%AA',0,'%AB',0,'%AC',0,'%AD',0,'%AE',0,'%AF',0
        db      '%B0',0,'%B1',0,'%B2',0,'%B3',0,'%B4',0,'%B5',0,'%B6',0,'%B7',0
        db      '%B8',0,'%B9',0,'%BA',0,'%BB',0,'%BC',0,'%BD',0,'%BE',0,'%BF',0
        db      '%C0',0,'%C1',0,'%C2',0,'%C3',0,'%C4',0,'%C5',0,'%C6',0,'%C7',0
        db      '%C8',0,'%C9',0,'%CA',0,'%CB',0,'%CC',0,'%CD',0,'%CE',0,'%CF',0
        db      '%D0',0,'%D1',0,'%D2',0,'%D3',0,'%D4',0,'%D5',0,'%D6',0,'%D7',0
        db      '%D8',0,'%D9',0,'%DA',0,'%DB',0,'%DC',0,'%DD',0,'%DE',0,'%DF',0
        db      '%E0',0,'%E1',0,'%E2',0,'%E3',0,'%E4',0,'%E5',0,'%E6',0,'%E7',0
        db      '%E8',0,'%E9',0,'%EA',0,'%EB',0,'%EC',0,'%ED',0,'%EE',0,'%EF',0
        db      '%F0',0,'%F1',0,'%F2',0,'%F3',0,'%F4',0,'%F5',0,'%F6',0,'%F7',0
        db      '%F8',0,'%F9',0,'%FA',0,'%FB',0,'%FC',0,'%FD',0,'%FE',0,'%FF'


end if



if used url$decode | defined include_everything
        ; single argument in rdi: string containing %XX url characters
        ; returns a new string in rax of the decoded goods
falign
url$decode:
        prolog  url$decode
        cmp     qword [rdi], 0
        je      .emptystring
        push    rbx r12 r13
        sub     rsp, 32
        mov     qword [rsp], 4
if string_bits = 32
        mov     dword [rsp+8], '0'
        mov     dword [rsp+12], 'x'
else
        mov     word [rsp+8], '0'
        mov     word [rsp+10], 'x'
end if
        mov     rbx, rdi
        mov     r12, [rdi]
        call    buffer$new
        mov     r13, rax
        mov     rdi, rax
        mov     rsi, r12
        call    buffer$reserve
        mov     rdi, [r13+buffer_itself_ofs]
        lea     rsi, [rbx+8]
calign
.doit:
if string_bits = 32
        mov     eax, [rsi]
        add     rsi, 4
else
        movzx   eax, word [rsi]
        add     rsi, 2
end if
        cmp     eax, '%'
        je      .doit_perc
        cmp     eax, '+'
        je      .doit_plus

        mov     [rdi], al
        inc     rdi

        sub     r12, 1
        jnz     .doit
        jmp     .done
calign
.doit_perc:
        sub     r12, 1
        jz      .done
        cmp     r12, 2
        jb      .done

if string_bits = 32
        mov     eax, [rsi]
        mov     ecx, [rsi+4]
        add     rsi, 8
        mov     [rsp+16], eax
        mov     [rsp+20], ecx
else
        movzx   eax, word [rsi]
        movzx   ecx, word [rsi+2]
        add     rsi, 4
        mov     [rsp+12], ax
        mov     [rsp+14], cx
end if

        push    rdi rsi
        lea     rdi, [rsp+16]
        call    string$to_unsigned
        pop     rsi rdi
        mov     [rdi], al
        inc     rdi

        sub     r12, 2
        jnz     .doit
        jmp     .done

calign
.doit_plus:
        mov     byte [rdi], ' '
        inc     rdi
        sub     r12, 1
        jnz     .doit

calign
.done:
        mov     rsi, rdi
        mov     rdi, [r13+buffer_itself_ofs]
        sub     rsi, rdi

        call    string$from_utf8

        mov     r12, rax
        mov     rdi, r13
        call    buffer$destroy
        add     rsp, 32
        mov     rax, r12
        pop     r13 r12 rbx
        epilog
calign
.emptystring:
        call    string$new
        epilog

end if


if used url$decode_path | defined include_everything
        ; single argument in rdi: string containing %XX url characters
        ; returns a new string in rax of the decoded goods
        ; NOTE: this is the same as url$decode but leaves + signs alone
falign
url$decode_path:
        prolog  url$decode_path
        cmp     qword [rdi], 0
        je      .emptystring
        push    rbx r12 r13
        sub     rsp, 32
        mov     qword [rsp], 4

if string_bits = 32
        mov     dword [rsp+8], '0'
        mov     dword [rsp+12], 'x'
else
        mov     word [rsp+8], '0'
        mov     word [rsp+10], 'x'
end if
        mov     rbx, rdi
        mov     r12, [rdi]
        call    buffer$new
        mov     r13, rax
        mov     rdi, rax
        mov     rsi, r12
        call    buffer$reserve
        mov     rdi, [r13+buffer_itself_ofs]
        lea     rsi, [rbx+8]
calign
.doit:
if string_bits = 32
        mov     eax, [rsi]
        add     rsi, 4
else
        movzx   eax, word [rsi]
        add     rsi, 2
end if
        cmp     eax, '%'
        je      .doit_perc

        mov     [rdi], al
        inc     rdi

        sub     r12, 1
        jnz     .doit
        jmp     .done

calign
.doit_perc:
        sub     r12, 1
        jz      .done
        cmp     r12, 2
        jb      .done

if string_bits = 32
        mov     eax, [rsi]
        mov     ecx, [rsi+4]
        add     rsi, 8
        mov     [rsp+16], eax
        mov     [rsp+20], ecx
else
        movzx   eax, word [rsi]
        movzx   ecx, word [rsi+2]
        add     rsi, 4
        mov     [rsp+12], ax
        mov     [rsp+14], cx
end if

        push    rdi rsi
        lea     rdi, [rsp+16]
        call    string$to_unsigned
        pop     rsi rdi
        mov     [rdi], al
        inc     rdi

        sub     r12, 2
        jnz     .doit
        jmp     .done

calign
.done:
        mov     rsi, rdi
        mov     rdi, [r13+buffer_itself_ofs]
        sub     rsi, rdi

        call    string$from_utf8

        mov     r12, rax
        mov     rdi, r13
        call    buffer$destroy
        add     rsp, 32
        mov     rax, r12
        pop     r13 r12 rbx
        epilog
calign
.emptystring:
        call    string$new
        epilog

end if
