; ------------------------------------------------------------------------
; 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/>.
; ------------------------------------------------------------------------
;
; mimelike.inc: mime and similar parsing/construction object
;
; Burning Purpose:
; Deal with MIME and HTTP parsing and construction in a nice, relatively
; pain-free way, haha. I have been carrying around in my stash of goodies
; variations on the code contained herein for, hmmm, nearly two decades...
; This is far and away the best implementation I have done, and I have had
; good success with this being the "MIME multitool" (read: parse a slice of
; ham and not crash or do anything weird, YMMV, HA!) All of the ones I have
; come across in the wild don't work quite like mine. Whether that is a
; matter of personal preference or not I'll leave to you to consider.
;
; I have made every effort to allow both email formatted "real" MIME, in
; addition to parsing/dealing with HTTP/1.1 in a seamless sorta way. It can
; be (and is) used for both purposes :-)
;
if used mimelike$new | used mimelike$new_parse | defined include_everything
mimelike_headers_ofs = 0 ; a unique stringmap of keys/values, insert order
mimelike_preface_ofs = 8 ; a heap allocated string of the preface, or null if none (think: GET / HTTP/1.0)
mimelike_body_ofs = 16 ; a buffer$new for keeping our "body" (of this part)
mimelike_xmitbody_ofs = 24 ; a buffer$new of the compiled header+body
mimelike_boundary_ofs = 32 ; a heap allocated string of the boundary, or null if none (multipart goods)
mimelike_parts_ofs = 40 ; a list$new of mimelike objects (if multipart)
mimelike_parent_ofs = 48 ; pointer to our parent (if any)
mimelike_hdrlen_ofs = 56 ; set inside new_parse, the length in bytes of the actual header including separator, set during compose as well
mimelike_parselen_ofs = 64 ; also set in new_parse, the total length we parsed from the input
mimelike_bodyextend_ofs = 72 ; a "fake" partial buffer object contained herein to make things easier
mimelike_bodyextlen_ofs = 80 ; length of above
mimelike_bodyext_ofs = 88 ; pointer, if nonzero, this and bodyextlen are used _instead_ of body during compose
mimelike_user_ofs = 96 ; not used in here, but cleared on mimelike$new/new_parse
mimelike_nomolest_ofs = 104 ; bool, if set then mimelike$compose does not alter encoding/content-length/gzip/etc
mimelike_size = 112
end if
if used mimelike$new | defined include_everything
; no arguments, returns a new but ready to use mimelike object in rax
falign
mimelike$new:
prolog mimelike$new
mov edi, 1
call stringmap$new
push rax
call buffer$new
push rax
call buffer$new
push rax
call list$new
push rax
mov edi, mimelike_size
call heap$alloc_clear
pop rcx rdx rsi rdi
mov [rax+mimelike_headers_ofs], rdi
mov [rax+mimelike_body_ofs], rsi
mov [rax+mimelike_xmitbody_ofs], rdx
mov [rax+mimelike_parts_ofs], rcx
epilog
end if
if used mimelike$destroy | defined include_everything
; single argument in rdi: the mimelike object to destroy
falign
mimelike$destroy:
prolog mimelike$destroy
push rbx
mov rbx, rdi
mov rdi, [rdi+mimelike_parts_ofs]
mov rsi, mimelike$destroy
call list$clear
mov rdi, [rbx+mimelike_parts_ofs]
call heap$free
mov rdi, [rbx+mimelike_boundary_ofs]
call .maybefree
mov rdi, [rbx+mimelike_xmitbody_ofs]
call buffer$destroy
mov rdi, [rbx+mimelike_body_ofs]
call buffer$destroy
mov rdi, [rbx+mimelike_preface_ofs]
call .maybefree
mov rdi, [rbx+mimelike_headers_ofs]
mov rsi, .hdrfree
call stringmap$clear
mov rdi, [rbx+mimelike_headers_ofs]
call heap$free
mov rdi, rbx
call heap$free
pop rbx
epilog
falign
.hdrfree:
; called with rdi == key, rsi == value, we need to free both
push rsi
call heap$free
pop rdi
call heap$free
ret
falign
.maybefree:
test rdi, rdi
jz .nofree
call heap$free
ret
.nofree:
ret
end if
if used mimelike$setpreface | defined include_everything
; two arguments: rdi == mimelike object, rsi == string to set preface to (we'll copy it)
falign
mimelike$setpreface:
prolog mimelike$setpreface
push rbx
push qword [rdi+mimelike_preface_ofs]
mov rbx, rdi
mov rdi, rsi
call string$copy
mov [rbx+mimelike_preface_ofs], rax
pop rdi
test rdi, rdi
jz .nofree
call heap$free
calign
.nofree:
pop rbx
epilog
end if
if used mimelike$setpreface_nocopy | defined include_everything
; two arguments: rdi == mimelike object, rsi == string to set preface to (we assume ownership of it, no copy)
falign
mimelike$setpreface_nocopy:
prolog mimelike$setpreface_nocopy
push rbx
mov rbx, rdi
mov rdi, [rdi+mimelike_preface_ofs]
mov [rbx+mimelike_preface_ofs], rsi
test rdi, rdi
jz .nofree
call heap$free
calign
.nofree:
pop rbx
epilog
end if
if used mimelike$setheader | defined include_everything
; three arguments: rdi == mimelike object, rsi == string name, rdx == string value (we copy both)
falign
:
prolog mimelike$setheader
push rbx r12 r13
mov rbx, rdi
mov r12, rsi
mov rdi, rdx
call string$copy
mov r13, rax
mov rdi, r12
call string$copy
mov r12, rax
mov rdi, [rbx+mimelike_headers_ofs]
mov rsi, rax
mov rdx, r13
call stringmap$insert_unique
test eax, eax
jz .alreadythere
pop r13 r12 rbx
epilog
calign
.alreadythere:
; we know the node that matched is sitting in r8 as a leftover from stringmap$insert_unique
mov rdi, [r8+_avlofs_value]
mov [r8+_avlofs_value], r13
call heap$free
mov rdi, r12
call heap$free
pop r13 r12 rbx
epilog
end if
if used mimelike$addheader | defined include_everything
; three arguments: rdi == mimelike object, rsi == string name, rdx == string value (we copy both)
; NOTE: this is the same as above, only we do NOT perform a find/replace, so only use this if you know it isn't already here
falign
:
prolog mimelike$addheader
push qword [rdi+mimelike_headers_ofs]
push rsi
mov rdi, rdx
call string$copy
mov rdi, [rsp]
mov [rsp], rax
call string$copy
pop rdx
mov rsi, rax
pop rdi
call stringmap$insert_unique
epilog
end if
if used mimelike$setheader_novaluecopy | defined include_everything
; three arguments: rdi == mimelike object, rsi == string name, rdx == string value (name gets copied, value does not)
falign
:
prolog mimelike$setheader_novaluecopy
push rbx r12 r13
mov rbx, rdi
mov r13, rdx
mov rdi, rsi
call string$copy
mov r12, rax
mov rdi, [rbx+mimelike_headers_ofs]
mov rsi, rax
mov rdx, r13
call stringmap$insert_unique
test eax, eax
jz .alreadythere
pop r13 r12 rbx
epilog
calign
.alreadythere:
; we know the node that matched is sitting in r8 as a leftover from stringmap$insert_unique
mov rdi, [r8+_avlofs_value]
mov [r8+_avlofs_value], r13
call heap$free
mov rdi, r12
call heap$free
pop r13 r12 rbx
epilog
end if
if used mimelike$addheader_novaluecopy | defined include_everything
; three arguments: rdi == mimelike object, rsi == string name, rdx == string value (name gets copied, value does not)
; NOTE: this is the same as above, only we do NOT perform a find/replace, so only use this if you know it isn't already here
falign
:
prolog mimelike$addheader_novaluecopy
push qword [rdi+mimelike_headers_ofs]
push rdx
mov rdi, rsi
call string$copy
pop rdx rdi
mov rsi, rax
call stringmap$insert_unique
epilog
end if
if used mimelike$setheader_nocopy | defined include_everything
; three arguments: rdi == mimelike object, rsi == string name, rdx == string value (assumes ownership of both)
falign
:
prolog mimelike$setheader_nocopy
push rbx r12
mov rdi, [rdi+mimelike_headers_ofs]
mov rbx, rsi
mov r12, rdx
call stringmap$insert_unique
test eax, eax
jz .alreadythere
pop r12 rbx
epilog
calign
.alreadythere:
; we know the node that matched is sitting in r8 as a leftover from stringmap$insert_unique
mov rdi, [r8+_avlofs_value]
mov [r8+_avlofs_value], r12
call heap$free
mov rdi, rbx
call heap$free
pop r12 rbx
epilog
end if
if used mimelike$addheader_nocopy | defined include_everything
; three arguments: rdi == mimelike object, rsi == string name, rdx == string value (assumes ownership of both)
; NOTE: this is the same as above, only we do NOT perform a find/replace, so only use this if you know it isn't already here
falign
:
prolog mimelike$addheader_nocopy
mov rdi, [rdi+mimelike_headers_ofs]
call stringmap$insert_unique
epilog
end if
if used mimelike$getheader | defined include_everything
; two arguments: rdi == mimelike object, rsi == string name
; returns value string ptr in rax, or null if no such header
falign
:
prolog mimelike$getheader
mov rdi, [rdi+mimelike_headers_ofs]
call stringmap$find
test rax, rax
jz .bailout
mov rax, [rax+_avlofs_value]
epilog
calign
.bailout:
epilog
end if
if used mimelike$removeheader | defined include_everything
; two arguments: rdi == mimelike object, rsi == string name
falign
:
prolog mimelike$removeheader
mov rdi, [rdi+mimelike_headers_ofs]
call stringmap$erase
test eax, eax
jz .nodeal
; otherwise, rdx == key of the node, r8 == value
push rdx
mov rdi, r8
call heap$free
pop rdi
call heap$free
epilog
calign
.nodeal:
epilog
end if
if used mimelike$setdefaultboundary | defined include_everything
; two arguments: rdi == mimelike object, rsi == content type (or null for multipart/alternative)
falign
mimelike$setdefaultboundary:
prolog mimelike$setdefaultboundary
push rbx r12
mov rbx, rdi
mov r12, rsi
sub rsp, 16
mov rdi, rsp
mov esi, 16
call rng$block
mov rdi, rsp
mov esi, 16
xor edx, edx
call string$from_bintobase64
mov [rsp], rax
mov rdi, .pref
mov rsi, rax
call string$concat
mov rdi, [rsp]
mov [rsp], rax
call heap$free
mov rdi, [rsp]
mov rsi, .post
call string$concat
mov rdi, [rsp]
mov [rsp], rax
call heap$free
mov rdi, rbx
mov rsi, [rsp]
mov rdx, r12
call mimelike$setboundary
add rsp, 16
pop r12 rbx
epilog
cleartext .pref, '=_HeavyThing-'
cleartext .post, '_='
end if
if used mimelike$setboundary | defined include_everything
; three arguments: rdi == mimelike object, rsi == boundary string (assumes ownership of string), rdx == content type (or null for multipart/alternative)
falign
mimelike$setboundary:
prolog mimelike$setboundary
mov rcx, [rdi+mimelike_boundary_ofs]
test rcx, rcx
jnz .withfree
mov [rdi+mimelike_boundary_ofs], rsi
mov rsi, rdx
call mimelike$multipartcontenttype
epilog
calign
.withfree:
push rdi rdx
mov [rdi+mimelike_boundary_ofs], rsi
mov rdi, rcx
call heap$free
pop rsi rdi
call mimelike$multipartcontenttype
epilog
end if
if used mimelike$multipartcontenttype | defined include_everything
; two arguments: rdi == mimelike object, rsi == content type (or null for multipart/alternative)
; NOTE: boundary _must_ be set prior to calling this
falign
mimelike$multipartcontenttype:
prolog mimelike$multipartcontenttype
push rbx r12 r13
mov rbx, rdi
mov r12, rsi
test rsi, rsi
jz .default
cmp qword [rsi], 0
je .default
; ideally, the Content-Type header needs to have the content type, then a semicolon, then a space, then boundary=".."
; so, we'll be nice and see what the last non-space character of our passed string is and determine which bits to add
mov r13d, [rsi]
sub r13d, 1
calign
.lastchar:
mov rdi, r12
mov esi, r13d
call string$charat
cmp eax, ' '
jne .lastchar_done
test r13d, r13d
jz .lastchar_done
sub r13d, 1
jmp .lastchar
calign
.lastchar_done:
cmp eax, ';'
jne .addsemicolon
mov r13d, [r12]
sub r13d, 1
mov rdi, r12
mov esi, r13d
call string$charat
cmp eax, ' '
jne .addspace
; otherwise, add it like it is
mov rdi, r12
mov rsi, .pref
calign
.doit:
call string$concat
mov r12, rax
mov rdi, rax
mov rsi, [rbx+mimelike_boundary_ofs]
call string$concat
mov rdi, r12
mov r12, rax
call heap$free
mov rdi, r12
mov rsi, .post
call string$concat
mov rdi, r12
mov r12, rax
call heap$free
mov rdi, rbx
mov rsi, .ctype
mov rdx, r12
call mimelike$setheader_novaluecopy
pop r13 r12 rbx
epilog
calign
.addsemicolon:
mov rdi, r12
mov rsi, .semipref
jmp .doit
calign
.addspace:
mov rdi, r12
mov rsi, .spacepref
jmp .doit
calign
.default:
mov rdi, .defaultpref
mov rsi, [rbx+mimelike_boundary_ofs]
call string$concat
mov r12, rax
mov rdi, rax
mov rsi, .post
call string$concat
mov rdi, r12
mov r12, rax
call heap$free
mov rdi, rbx
mov rsi, .ctype
mov rdx, r12
call mimelike$setheader_novaluecopy
pop r13 r12 rbx
epilog
cleartext .semipref, '; boundary="'
cleartext .spacepref, ' boundary="'
cleartext .pref, 'boundary="'
cleartext .defaultpref, 'multipart/alternative; boundary="'
cleartext .post, '"'
cleartext .ctype, 'Content-Type'
end if
if used mimelike$shouldgzip | defined include_everything
; single argument in rdi: mimelike object
; checks for a content-type header, and if present, scans for "best practice" ones that we actually should gzip
; returns bool in eax
falign
mimelike$shouldgzip:
prolog mimelike$shouldgzip
push rbx r12
mov rbx, rdi
mov rsi, mimelike$contenttype
call mimelike$getheader
test rax, rax
jz .nope
mov r12, rax
mov rdi, rax
mov rsi, .textstr
call string$starts_with
test eax, eax
jnz .yep
mov rdi, r12
mov rsi, .javascript
call string$indexof
cmp rax, 0
jge .yep
mov rdi, r12
mov rsi, .xml
call string$indexof
cmp rax, 0
jge .yep
mov rdi, r12
mov rsi, .icon
call string$indexof
cmp rax, 0
jge .yep
mov rdi, r12
mov rsi, .json
call string$indexof
cmp rax, 0
jge .yep
mov rdi, r12
mov rsi, .rtf
call string$indexof
cmp rax, 0
jge .yep
mov rdi, r12
mov rsi, .svg
call string$starts_with
test eax, eax
jnz .yep
; otherwise, nope
xor eax, eax
pop r12 rbx
epilog
cleartext .textstr, 'text/'
cleartext .javascript, 'javascript'
cleartext .xml, 'xml'
cleartext .icon, 'x-icon'
cleartext .rtf, '/rtf'
cleartext .json, 'json'
cleartext .svg, 'image/svg+xml'
calign
.yep:
mov eax, 1
pop r12 rbx
epilog
calign
.nope:
xor eax, eax
pop r12 rbx
epilog
end if
if used mimelike$static_etag | defined include_everything
; three arguments: rdi == mimelike object, rsi == string filename, rdx == some integer (usually mtime)
; NOTE: we truncate a SHA224 by one byte, so that our base64 results don't end up padded with two equal signs
falign
mimelike$static_etag:
prolog mimelike$static_etag
push rbx r12 r13
mov rbx, rdi
mov r12, rsi
mov r13, rdx
sub rsp, sha224_state_size
mov rdi, rsp
call sha224$init
mov rdi, rsp
push r13
mov rsi, rsp
mov edx, 8
call sha224$update
mov rdx, [r12]
add rsp, 8
mov rdi, rsp
lea rsi, [r12+8]
if string_bits = 32
shl rdx, 2
else
shl rdx, 1
end if
call sha224$update
mov rdi, rsp
sub rsp, 32
mov rsi, rsp
xor edx, edx ; don't attempt to free the sha224 state
call sha224$final
mov rdi, rsp
mov esi, 27 ; TRUNCATED SHA224 by one byte
xor edx, edx
call string$from_bintobase64
if base64_linebreaks
; CRLFs were added that we need to remove
mov r12, rax
mov rdi, rax
mov esi, 13
call string$indexof_charcode
mov rdi, r12
xor esi, esi
mov rdx, rax
call string$substr
mov rdi, r12
mov r12, rax
call heap$free
mov rax, r12
end if
; that needs to be quoted
mov r12, rax
mov rdi, .quote
mov rsi, rax
call string$concat
mov rdi, r12
mov r12, rax
call heap$free
mov rdi, r12
mov rsi, .quote
call string$concat
mov rdi, r12
mov r12, rax
call heap$free
mov rax, r12
mov rdi, rbx
mov rsi, mimelike$etag
mov rdx, rax
call mimelike$setheader_novaluecopy
add rsp, 32 + sha224_state_size
pop r13 r12 rbx
epilog
cleartext .quote, '"'
end if
if used mimelike$dynamic_etag | defined include_everything
; three arguments: rdi == mimelike object, rsi == ptr to data, rdx == length of same
; NOTE: we truncate a SHA224 by one byte, so that our base64 results don't end up padded with two equal signs
falign
mimelike$dynamic_etag:
prolog mimelike$dynamic_etag
push rbx r12 r13
mov rbx, rdi
mov r12, rsi
mov r13, rdx
sub rsp, sha224_state_size + 32
mov rdi, rsp
call sha224$init
mov rdi, rsp
mov rsi, r12
mov rdx, r13
call sha224$update
mov rdi, rsp
lea rsi, [rsp+sha224_state_size]
xor edx, edx ; don't attempt to free the sha224 state
call sha224$final
lea rdi, [rsp+sha224_state_size]
mov esi, 27 ; TRUNCATED SHA224 by one byte
xor edx, edx
call string$from_bintobase64
if base64_linebreaks
; CRLFs were added that we need to remove
mov r12, rax
mov rdi, rax
mov esi, 13
call string$indexof_charcode
mov rdi, r12
xor esi, esi
mov rdx, rax
call string$substr
mov rdi, r12
mov r12, rax
call heap$free
mov rax, r12
end if
; that needs to be quoted
mov r12, rax
mov rdi, .quote
mov rsi, rax
call string$concat
mov rdi, r12
mov r12, rax
call heap$free
mov rdi, r12
mov rsi, .quote
call string$concat
mov rdi, r12
mov r12, rax
call heap$free
mov rax, r12
mov rdi, rbx
mov rsi, mimelike$etag
mov rdx, rax
call mimelike$setheader_novaluecopy
add rsp, sha224_state_size + 32
pop r13 r12 rbx
epilog
cleartext .quote, '"'
end if
if used mimelike$compose | defined include_everything
; single argument in rdi: mimelike object
; this populates xmitbody "intelligently" based on header contents
; e.g. we pay attention to Content-Encoding and Transfer-Encoding headers, etc
falign
mimelike$compose:
prolog mimelike$compose
push rbx
mov rbx, rdi
; if there is no body length, then no encoding takes place whatsoever (and we leave headers alone)
mov rax, [rbx+mimelike_bodyext_ofs]
mov rdi, [rbx+mimelike_body_ofs]
or rax, [rdi+buffer_length_ofs]
jz .doparts
; determine our xmitbody encoding for our body based on what headers are present:
push r12 r13 r14 r15
; we'll use r13 to determine whether or not we have an intermediate buffer or not
xor r13d, r13d
cmp dword [rbx+mimelike_nomolest_ofs], 0
jne .body_straightin_skiplength
mov rdi, rbx
mov rsi, mimelike$contenttransferencoding
call mimelike$getheader
test rax, rax
jz .body_checkcontentencoding
; otherwise, lower case that one and check it for ones we recognize
mov rdi, rax
call string$to_lower
mov r12, rax
mov rdi, rax
mov rsi, mimelike$base64
call string$indexof
cmp rax, 0
jge .body_base64
mov rdi, r12
mov rsi, mimelike$quotedprintable
call string$indexof
cmp rax, 0
jge .body_qp
; so if it is anything ELSE, we just leave it well enough alone
mov rdi, r12
call heap$free
; fallthrough to check content encoding
calign
.body_checkcontentencoding:
; check to see whether we have a gzip content encoding
mov rdi, rbx
mov rsi, mimelike$contentencoding
call mimelike$getheader
test rax, rax
jz .body_checktransferencoding
; otherwise, lower case that and check it for gzip
mov rdi, rax
call string$to_lower
mov r12, rax
mov rdi, rax
mov rsi, mimelike$gzip
call string$indexof
push rax
mov rdi, r12
call heap$free
pop rax
cmp rax, 0
jl .body_checktransferencoding
; otherwise, gzip was specified...
; our library setting determines whether we actually bother with gzip or not
lea rdx, [rbx+mimelike_bodyextend_ofs] ; pointer to our "fake" buffer object
mov r14, [rbx+mimelike_body_ofs]
cmp qword [rbx+mimelike_bodyext_ofs], 0
cmovne r14, rdx
test r13, r13
cmovnz r14, r13
mov rdi, rbx
call mimelike$shouldgzip
test eax, eax
jz .body_removecontentencoding
; make sure it isn't already gzipped
mov rax, [r14+buffer_itself_ofs]
mov edi, [rax]
mov esi, [rax]
and edi, 0xffffff
shr esi, 24
cmp edi, 0x88b1f
jne .notalreadygzipped
test esi, 0xe0
jz .body_checktransferencoding
calign
.notalreadygzipped:
cmp qword [r14+buffer_length_ofs], mimelike_mingzip
jb .body_removecontentencoding
; otherwise, go ahead and gzip it on the fly
sub rsp, zlib_stream_size
mov rdi, rsp
mov esi, 2
call zlib$deflateInit
mov r15, r13
call buffer$new
mov r13, rax
mov rdi, rsp
mov esi, zlib_finish
mov [rsp+zlib_inbuf_ofs], r14
mov [rsp+zlib_outbuf_ofs], rax
call zlib$deflate
mov rdi, rsp
call zlib$deflateEnd
add rsp, zlib_stream_size
; now, depending on whether we really had an intermediate buffer or not, destroy it
test r15, r15
jz .body_checktransferencoding
mov rdi, r15
call buffer$destroy
jmp .body_checktransferencoding
cleartext .headersep, 13,10
macro headers_addheadersep {
mov rdi, [rbx+mimelike_xmitbody_ofs]
if string_bits = 32
mov rax, [.headersep+8]
else
mov eax, [.headersep+8]
end if
mov rsi, [rdi+buffer_endptr_ofs]
if string_bits = 32
add qword [rdi+buffer_endptr_ofs], 8
add qword [rdi+buffer_length_ofs], 8
mov [rsi], rax
else
add qword [rdi+buffer_endptr_ofs], 4
add qword [rdi+buffer_length_ofs], 4
mov [rsi], eax
end if
}
; update 20160809: our x1024 bytes per header pre-reservation in
; the destination buffers in certain (albeit rare) circumstances
; is wholly insufficient and can cause (when it overruns) memory
; corruption... so we leave the x1024 pre-reserve, but put the
; rest of the noreserves as an optional conditional. In certain
; circumstances, performance can be improved by enablign the
; noreserve additions.
falign
.headers:
mov rdx, [rbx+mimelike_headers_ofs]
mov rdi, [rbx+mimelike_xmitbody_ofs]
mov esi, [rdx+_avlofs_right] ; node count in headers
shl esi, 10 ; x 1024 bytes per header
call buffer$reset_reserve
; make sure all our children have valid xmitbodies
mov rdi, [rbx+mimelike_parts_ofs]
mov rsi, mimelike$compose
call list$foreach
; preface first
mov rdi, [rbx+mimelike_xmitbody_ofs]
mov rsi, [rbx+mimelike_preface_ofs]
test rsi, rsi
jz .nopreface
if defined mimelike_optimistic_headersizes
call buffer$append_rawstring_noreserve
else
call buffer$append_rawstring
end if
headers_addheadersep
calign
.nopreface:
; header lines next
mov rdi, [rbx+mimelike_headers_ofs]
mov rsi, .headerline
mov rdx, [rbx+mimelike_xmitbody_ofs]
call stringmap$foreach_arg
; and maybe a separator line
mov rdi, [rbx+mimelike_xmitbody_ofs]
cmp qword [rdi+buffer_length_ofs], 0
je .nohdrsep
headers_addheadersep
; now convert our buffer to UTF8 in place, noting here this is a bit
; of evil trickery: 1) we know that every heap allocated object has
; an 8 byte preface before it for heap management
; so we can pretend our actual header buffer is a real string by
; placing the value where the heap management 8 bytes is, then
; calling to_utf8, and then putting our heap management back
mov rdx, [rbx+mimelike_xmitbody_ofs]
mov rdi, [rdx+buffer_itself_ofs]
mov rsi, [rdx+buffer_length_ofs]
lea rcx, [rdi-8]
if string_bits = 32
shr rsi, 2
else
shr rsi, 1
end if
push qword [rcx] ; store our heap management value
mov [rcx], rsi ; set the number of characters
mov rsi, rdi
mov rdi, rcx
push rcx
call string$to_utf8
; so now, our correct number of characters is in rax
mov rcx, [rbx+mimelike_xmitbody_ofs]
pop rsi rdx
mov rdi, [rcx+buffer_itself_ofs]
; put back the heap management value
mov [rsi], rdx
; now set our new buffer pointers
lea rsi, [rdi+rax]
mov [rcx+buffer_endptr_ofs], rsi
mov [rcx+buffer_length_ofs], rax
; so our preface+headers is complete
; set our hdrlen
mov [rbx+mimelike_hdrlen_ofs], rax
ret
calign
.nohdrsep:
mov rdi, [rbx+mimelike_xmitbody_ofs]
mov rsi, [rdi+buffer_length_ofs]
mov [rbx+mimelike_hdrlen_ofs], rsi
ret
calign
.body_removecontentencoding:
; the body length didn't meet our minimum required to bother with gzip, so remove
; the Content-Encoding header and fallthrough to check Transfer-Encoding
mov rdi, rbx
mov rsi, mimelike$contentencoding
call mimelike$removeheader
; fallthrough
calign
.body_checktransferencoding:
; check to see whether we have a chunked transfer encoding
mov rdi, rbx
mov rsi, mimelike$transferencoding
call mimelike$getheader
test rax, rax
jz .body_straightin
; otherwise, lower case that and check it for chunked
mov rdi, rax
call string$to_lower
mov r12, rax
mov rdi, rax
mov rsi, mimelike$chunked
call string$indexof
push rax
mov rdi, r12
call heap$free
pop rax
cmp rax, 0
jl .body_straightin
; otherwise, chunked was specified
; our library setting determines whether we actually bother with chunked encoding or not
lea rdx, [rbx+mimelike_bodyextend_ofs] ; pointer to our "fake" buffer object
mov r14, [rbx+mimelike_body_ofs]
cmp qword [rbx+mimelike_bodyext_ofs], 0
cmovne r14, rdx
test r13, r13
cmovnz r14, r13
if mimelike_minchunked
cmp qword [r14+buffer_length_ofs], mimelike_minchunked
jb .body_removetransferencoding
; we have to remove the content-length if it was specified
mov rdi, rbx
mov rsi, mimelike$contentlength
call mimelike$getheader
test rax, rax
jz .body_dochunks_noclength
mov rdi, rbx
mov rsi, mimelike$contentlength
call mimelike$removeheader
calign
.body_dochunks_noclength:
mov r15, r13
call buffer$new
mov r13, rax
mov r12, [r14+buffer_itself_ofs]
mov r14, [r14+buffer_length_ofs]
sub rsp, 16
calign
.body_dochunks:
mov esi, mimelike_chunksize
mov rdi, r14
cmp r14, rsi
cmova rdi, rsi
mov esi, 16
mov [rsp], rdi
call string$from_unsigned
mov [rsp+8], rax
mov rdi, r13
mov rsi, rax
call buffer$append_string
mov rdi, [rsp+8]
call heap$free
mov rdi, r13
mov esi, 0xa0d
call buffer$append_word
mov rdi, r13
mov rsi, r12
mov rdx, [rsp]
add r12, rdx
sub r14, rdx
call buffer$append
mov rdi, r13
mov esi, 0xa0d
call buffer$append_word
test r14, r14
jnz .body_dochunks
; terminator chunk
mov rdi, r13
mov esi, '0'
call buffer$append_byte
mov rdi, r13
mov esi, 0xa0d0a0d
call buffer$append_dword
add rsp, 16
; now, depending on whether we really had an intermediate buffer or not, destroy it
test r15, r15
jz .body_straightin_skiplength
mov rdi, r15
call buffer$destroy
jmp .body_straightin_skiplength
calign
.body_removetransferencoding:
; so the body length was too small to bother with chunked encoding
; r14 is the source buffer
end if
mov rdi, rbx
mov rsi, mimelike$transferencoding
call mimelike$removeheader
; now we need a length representation of our buffer as a string
; so we can set the content-length header
mov rdi, [r14+buffer_length_ofs]
mov esi, 10
call string$from_unsigned
mov rdi, rbx
mov rsi, mimelike$contentlength
mov rdx, rax
call mimelike$setheader_novaluecopy
; all good, dump the body straight in
jmp .body_straightin
calign
.body_qp:
; free our string first
mov rdi, r12
call heap$free
; build an intermediate buffer in r13
; note: the default buffer size starts out with enough room for a single line
; so we only call buffer$reserve on our intermediate buffer when we finish a line
call buffer$new
lea rdx, [rbx+mimelike_bodyextend_ofs] ; our "fake" buffer object
mov rdi, [rbx+mimelike_body_ofs]
cmp qword [rbx+mimelike_bodyext_ofs], 0
cmovne rdi, rdx
mov r13, rax
mov r12, [rdi+buffer_itself_ofs]
mov r14, [rdi+buffer_length_ofs]
mov r15d, 75
calign
.body_qp_outer:
; walk forward while we don't have to do =XX or = encoding
mov rdi, r13
mov rsi, r12
xor edx, edx
calign
.body_qp_linesearch:
movzx eax, byte [r12]
add r12, 1
cmp eax, 13
je .body_qp_linesearch_cr
cmp eax, 32
jb .body_qp_escapechar
cmp eax, 61
je .body_qp_escapechar
cmp eax, 126
ja .body_qp_escapechar
; otherwise, we can leave it as is
add edx, 1
sub r14, 1
jz .body_qp_eod
sub r15d, 1
jnz .body_qp_linesearch
; otherwise, soft linebreak
call buffer$append
mov rdi, r13
mov esi, '='
call buffer$append_byte
mov rdi, r13
mov esi, 0xa0d
call buffer$append_word
mov r15d, 75
jmp .body_qp_outer
calign
.body_qp_linesearch_cr:
cmp r14, 1
je .body_qp_escapechar
cmp byte [r12], 0x0a
jne .body_qp_escapechar
add r12, 1
add edx, 2
call buffer$append
sub r14, 2
jz .body_qp_eod_noappend
mov r15d, 75
jmp .body_qp_outer
calign
.body_qp_escapechar:
cmp r15d, 3
jb .body_qp_escapechar_softbreak
; otherwise, there is room for =XX
; add what we have first, if it is nonzero
push rax
test edx, edx
jz .body_qp_escapechar_noappend
call buffer$append
calign
.body_qp_escapechar_noappend:
mov rax, [rsp]
mov ecx, eax
shr ecx, 4
and eax, 0xf
and ecx, 0xf
mov eax, [eax+.body_qp_hexchars]
mov ecx, [ecx+.body_qp_hexchars]
mov byte [rsp], '='
mov byte [rsp+1], cl
mov byte [rsp+2], al
mov rdi, r13
mov rsi, rsp
mov edx, 3
call buffer$append
add rsp, 8
sub r15d, 3
jnz .body_qp_escapechar_next
; otherwise, soft linebreak
call buffer$append
mov rdi, r13
mov esi, '='
call buffer$append_byte
mov rdi, r13
mov esi, 0xa0d
call buffer$append_word
mov r15d, 75
sub r14, 1
jz .body_qp_eod_noappend
jmp .body_qp_outer
calign
.body_qp_escapechar_next:
sub r14, 1
jz .body_qp_eod_noappend
jmp .body_qp_outer
calign
.body_qp_hexchars:
db '0123456789ABCDEF'
calign
.body_qp_escapechar_softbreak:
mov r15d, eax
call buffer$append
mov rdi, r13
mov esi, '='
call buffer$append_byte
mov rdi, r13
mov esi, 0xa0d
call buffer$append_word
mov rdi, r13
mov rsi, r12
xor edx, edx
mov eax, r15d
mov r15d, 75
jmp .body_qp_escapechar
calign
.body_qp_eod:
; do we need to make sure it ended with a CRLF ? hmm
test rdx, rdx
jz .body_checkcontentencoding
call buffer$append
jmp .body_checkcontentencoding
calign
.body_qp_eod_noappend:
jmp .body_checkcontentencoding
calign
.body_base64:
; free our string first
mov rdi, r12
call heap$free
; build an intermediate buffer in r13
call buffer$new
lea rdx, [rbx+mimelike_bodyextend_ofs] ; our "fake" buffer object
mov rdi, [rbx+mimelike_body_ofs]
cmp qword [rbx+mimelike_bodyext_ofs], 0
cmovne rdi, rdx
mov r13, rax
mov rdi, [rdi+buffer_length_ofs]
call base64$encode_length
mov rdi, r13
mov rsi, rax
call buffer$reserve
lea rax, [rbx+mimelike_bodyextend_ofs] ; our "fake" buffer object
mov rcx, [rbx+mimelike_body_ofs]
cmp qword [rbx+mimelike_bodyext_ofs], 0
cmovne rcx, rax
mov rdx, [r13+buffer_itself_ofs]
mov rdi, [rcx+buffer_itself_ofs]
mov rsi, [rcx+buffer_length_ofs]
xor ecx, ecx
call base64$encode_latin1
mov rdi, r13
mov rsi, rax
call buffer$append_nocopy
jmp .body_checkcontentencoding
calign
.body_straightin:
; we are either adding r13, or our mimelike body
mov rdi, rbx
mov rsi, mimelike$contenttransferencoding
call mimelike$getheader
test rax, rax
jnz .body_straightin_skiplength
lea rdx, [rbx+mimelike_bodyextend_ofs] ; our "fake" buffer object
mov rcx, [rbx+mimelike_body_ofs]
cmp qword [rbx+mimelike_bodyext_ofs], 0
cmovne rcx, rdx
test r13, r13
cmovnz rcx, r13
mov rdi, [rcx+buffer_length_ofs]
mov esi, 10
call string$from_unsigned
mov rdi, rbx
mov rsi, mimelike$contentlength
mov rdx, rax
call mimelike$setheader_novaluecopy
calign
.body_straightin_skiplength:
call .headers
lea rdx, [rbx+mimelike_bodyextend_ofs] ; our "fake" buffer object
mov rcx, [rbx+mimelike_body_ofs]
cmp qword [rbx+mimelike_bodyext_ofs], 0
cmovne rcx, rdx
test r13, r13
cmovnz rcx, r13
mov rdi, [rbx+mimelike_xmitbody_ofs]
mov rsi, [rcx+buffer_itself_ofs]
mov rdx, [rcx+buffer_length_ofs]
call buffer$append
test r13, r13
jz .body_straightin_nointermediate
mov rdi, r13
call buffer$destroy
pop r15 r14 r13 r12
mov rdi, [rbx+mimelike_parts_ofs]
cmp qword [rdi+_list_first_ofs], 0
je .skipparts
mov rsi, .addpart
mov rdx, rbx
call list$foreach_arg
mov rdi, rbx
mov esi, 1 ; last part gets extra bits on the boundary marker
call .addxmitboundary
pop rbx
epilog
calign
.body_straightin_nointermediate:
pop r15 r14 r13 r12
mov rdi, [rbx+mimelike_parts_ofs]
cmp qword [rdi+_list_first_ofs], 0
je .skipparts
mov rsi, .addpart
mov rdx, rbx
call list$foreach_arg
mov rdi, rbx
mov esi, 1 ; last part gets extra bits on the boundary marker
call .addxmitboundary
pop rbx
epilog
calign
.doparts:
call .headers
mov rdi, [rbx+mimelike_parts_ofs]
cmp qword [rdi+_list_first_ofs], 0
je .skipparts
mov rsi, .addpart
mov rdx, rbx
call list$foreach_arg
mov rdi, rbx
mov esi, 1 ; last part gets extra bits on the boundary marker
call .addxmitboundary
pop rbx
epilog
calign
.skipparts:
pop rbx
epilog
falign
.addpart:
; called with rdi == mimelike object to add, rsi == parent mimelike object that we are adding to
push rsi
push qword [rdi+mimelike_xmitbody_ofs]
mov rdi, rsi
xor esi, esi ; not the last part
call .addxmitboundary
pop rcx rdi
mov rdi, [rdi+mimelike_xmitbody_ofs]
mov rsi, [rcx+buffer_itself_ofs]
mov rdx, [rcx+buffer_length_ofs]
call buffer$append
ret
cleartext .headerlinesep, ': '
falign
.headerline:
; called with rdi == key, rsi == value, rdx == hdrbuf
if mimelike_setcookie_split
cmp qword [rdi], 10
je .headerline_checksetcookie
end if
push rsi rdx
mov rsi, rdi
mov rdi, rdx
if defined mimelike_optimistic_headersizes
call buffer$append_rawstring_noreserve
else
call buffer$append_rawstring
end if
mov rdi, [rsp]
if string_bits = 32
mov rax, [.headerlinesep+8]
else
mov eax, [.headerlinesep+8]
end if
mov rsi, [rdi+buffer_endptr_ofs]
if string_bits = 32
add qword [rdi+buffer_endptr_ofs], 8
add qword [rdi+buffer_length_ofs], 8
mov [rsi], rax
else
add qword [rdi+buffer_endptr_ofs], 4
add qword [rdi+buffer_length_ofs], 4
mov [rsi], eax
end if
mov rdi, [rsp]
mov rsi, [rsp+8]
if defined mimelike_optimistic_headersizes
call buffer$append_rawstring_noreserve
else
call buffer$append_rawstring
end if
pop rdi
if string_bits = 32
mov rax, [.headersep+8]
else
mov eax, [.headersep+8]
end if
mov rsi, [rdi+buffer_endptr_ofs]
if string_bits = 32
add qword [rdi+buffer_endptr_ofs], 8
add qword [rdi+buffer_length_ofs], 8
mov [rsi], rax
else
add qword [rdi+buffer_endptr_ofs], 4
add qword [rdi+buffer_length_ofs], 4
mov [rsi], eax
end if
add rsp, 8
ret
if mimelike_setcookie_split
calign
.headerline_checksetcookie:
push rsi rdx rbx
mov rbx, rdi
mov rsi, mimelike$setcookie
call string$equals
test eax, eax
jz .headerline_notsetcookie
; otherwise, it is Set-Cookie, we need to split concatenated ones out into multiple headerlines
; we can sorta cheat here, the issue is that the expires= date format _also_ has a ', ' such that
; splitting on it alone isn't good enough
push r12 r13 r14
mov r12, [rsp+32] ; our destination buffer
mov r13, [rsp+40] ; our value string
mov rdi, r13
mov rsi, .commaspace
call string$split_str
mov r14, rax
calign
.headerline_setcookie_outer:
cmp qword [r14+_list_first_ofs], 0
je .headerline_done_nolinebreak
mov rdi, r12
mov rsi, mimelike$setcookie
if defined mimelike_optimistic_headersizes
call buffer$append_rawstring_noreserve
else
call buffer$append_rawstring
end if
mov rdi, r12
if string_bits = 32
mov rax, [.headerlinesep+8]
else
mov eax, [.headerlinesep+8]
end if
mov rsi, [rdi+buffer_endptr_ofs]
if string_bits = 32
add qword [rdi+buffer_endptr_ofs], 8
add qword [rdi+buffer_length_ofs], 8
mov [rsi], rax
else
add qword [rdi+buffer_endptr_ofs], 4
add qword [rdi+buffer_length_ofs], 4
mov [rsi], eax
end if
; add the first string regardless
cmp qword [r14+_list_first_ofs], 0
je .headerline_setcookie_done
mov rdi, r14
call list$pop_front
push rax
mov rdi, r12
mov rsi, rax
if defined mimelike_optimistic_headersizes
call buffer$append_rawstring_noreserve
else
call buffer$append_rawstring
end if
pop rdi
call heap$free
; now, walk the remaining items, and either start a new header line, or add a ', ' and keep going
calign
.headerline_setcookie_inner:
cmp qword [r14+_list_first_ofs], 0
je .headerline_setcookie_done
mov rdi, r14
call list$pop_front
push rax
; so, if this starts with '## ', then we have to add a comma-space, then the string, and keep going
; if it does -not- start with '## ', then we need to add a linebreak, push_front the string and jump back to outer
mov rdi, [rsp]
mov esi, 2
call string$charat
cmp eax, '-'
jne .headerline_setcookie_checkspace
calign
.headerline_setcookie_keepgoing:
mov rdi, [rsp]
mov esi, 0
call string$charat
cmp eax, '0'
jb .headerline_setcookie_linebreak
cmp eax, '3'
jae .headerline_setcookie_linebreak
mov rdi, [rsp]
mov esi, 1
call string$charat
cmp eax, '0'
jb .headerline_setcookie_linebreak
cmp eax, '9'
ja .headerline_setcookie_linebreak
; otherwise, add a ', ' and then our string, and jump to outer
mov rdi, r12
mov rsi, .commaspace
if defined mimelike_optimistic_headersizes
call buffer$append_rawstring_noreserve
else
call buffer$append_rawstring
end if
mov rdi, r12
mov rsi, [rsp]
if defined mimelike_optimistic_headersizes
call buffer$append_rawstring_noreserve
else
call buffer$append_rawstring
end if
mov rdi, r12
if string_bits = 32
mov rax, [.headersep+8]
else
mov eax, [.headersep+8]
end if
mov rsi, [rdi+buffer_endptr_ofs]
if string_bits = 32
add qword [rdi+buffer_endptr_ofs], 8
add qword [rdi+buffer_length_ofs], 8
mov [rsi], rax
else
add qword [rdi+buffer_endptr_ofs], 4
add qword [rdi+buffer_length_ofs], 4
mov [rsi], eax
end if
pop rdi
call heap$free
jmp .headerline_setcookie_outer
calign
.headerline_setcookie_checkspace:
cmp eax, ' '
je .headerline_setcookie_keepgoing
calign
.headerline_setcookie_linebreak:
mov rdi, r14
pop rsi
call list$push_front
mov rdi, r12
if string_bits = 32
mov rax, [.headersep+8]
else
mov eax, [.headersep+8]
end if
mov rsi, [rdi+buffer_endptr_ofs]
if string_bits = 32
add qword [rdi+buffer_endptr_ofs], 8
add qword [rdi+buffer_length_ofs], 8
mov [rsi], rax
else
add qword [rdi+buffer_endptr_ofs], 4
add qword [rdi+buffer_length_ofs], 4
mov [rsi], eax
end if
jmp .headerline_setcookie_outer
calign
.headerline_setcookie_done:
mov rdi, r12
if string_bits = 32
mov rax, [.headersep+8]
else
mov eax, [.headersep+8]
end if
mov rsi, [rdi+buffer_endptr_ofs]
if string_bits = 32
add qword [rdi+buffer_endptr_ofs], 8
add qword [rdi+buffer_length_ofs], 8
mov [rsi], rax
else
add qword [rdi+buffer_endptr_ofs], 4
add qword [rdi+buffer_length_ofs], 4
mov [rsi], eax
end if
mov rdi, r14
call heap$free
pop r14 r13 r12 rbx
add rsp, 16
ret
calign
.headerline_done_nolinebreak:
mov rdi, r14
call heap$free
pop r14 r13 r12 rbx
add rsp, 16
ret
cleartext .commaspace, ', '
calign
.headerline_notsetcookie:
mov rdi, rbx
pop rbx
mov rsi, rdi
mov rdi, [rsp]
if defined mimelike_optimistic_headersizes
call buffer$append_rawstring_noreserve
else
call buffer$append_rawstring
end if
mov rdi, [rsp]
if string_bits = 32
mov rax, [.headerlinesep+8]
else
mov eax, [.headerlinesep+8]
end if
mov rsi, [rdi+buffer_endptr_ofs]
if string_bits = 32
add qword [rdi+buffer_endptr_ofs], 8
add qword [rdi+buffer_length_ofs], 8
mov [rsi], rax
else
add qword [rdi+buffer_endptr_ofs], 4
add qword [rdi+buffer_length_ofs], 4
mov [rsi], eax
end if
mov rdi, [rsp]
mov rsi, [rsp+8]
if defined mimelike_optimistic_headersizes
call buffer$append_rawstring_noreserve
else
call buffer$append_rawstring
end if
pop rdi
if string_bits = 32
mov rax, [.headersep+8]
else
mov eax, [.headersep+8]
end if
mov rsi, [rdi+buffer_endptr_ofs]
if string_bits = 32
add qword [rdi+buffer_endptr_ofs], 8
add qword [rdi+buffer_length_ofs], 8
mov [rsi], rax
else
add qword [rdi+buffer_endptr_ofs], 4
add qword [rdi+buffer_length_ofs], 4
mov [rsi], eax
end if
add rsp, 8
ret
end if
falign
.addxmitboundary:
; called with rdi == the mimelike object, esi == bool for whether it is the last one or not
; if there is no boundary already set, create a default one
push rbx r12
mov rbx, rdi
mov r12d, esi
cmp qword [rdi+mimelike_boundary_ofs], 0
jne .boundarygood
call mimelike$setdefaultboundary
calign
.boundarygood:
mov rdi, [rbx+mimelike_xmitbody_ofs]
mov esi, 0x2d2d0a0d ; "\r\n--"
call buffer$append_dword
mov rdi, [rbx+mimelike_xmitbody_ofs]
mov rsi, [rbx+mimelike_boundary_ofs]
call buffer$append_string
mov rdi, [rbx+mimelike_xmitbody_ofs]
test r12d, r12d
jnz .lastone
mov esi, 0xa0d
call buffer$append_word
pop r12 rbx
ret
calign
.lastone:
mov esi, 0xa0d2d2d
call buffer$append_dword
pop r12 rbx
ret
end if
if used mimelike$transferencoding | defined include_everything
cleartext mimelike$transferencoding, 'Transfer-Encoding'
end if
if used mimelike$acceptencoding | defined include_everything
cleartext mimelike$acceptencoding, 'Accept-Encoding'
end if
if used mimelike$contenttransferencoding | defined include_everything
cleartext mimelike$contenttransferencoding, 'Content-Transfer-Encoding'
end if
if used mimelike$contenttype | defined include_everything
cleartext mimelike$contenttype, 'Content-Type'
end if
if used mimelike$contentlength | defined include_everything
cleartext mimelike$contentlength, 'Content-Length'
end if
if used mimelike$contentencoding | defined include_everything
cleartext mimelike$contentencoding, 'Content-Encoding'
end if
if used mimelike$connection | defined include_everything
cleartext mimelike$connection, 'Connection'
end if
if used mimelike$base64 | defined include_everything
cleartext mimelike$base64, 'base64'
end if
if used mimelike$7bit | defined include_everything
cleartext mimelike$7bit, '7bit'
end if
if used mimelike$8bit | defined include_everything
cleartext mimelike$8bit, '8bit'
end if
if used mimelike$binary | defined include_everything
cleartext mimelike$binary, 'binary'
end if
if used mimelike$quotedprintable | defined include_everything
cleartext mimelike$quotedprintable, 'quoted-printable'
end if
if used mimelike$chunked | defined include_everything
cleartext mimelike$chunked, 'chunked'
end if
if used mimelike$gzip | defined include_everything
cleartext mimelike$gzip, 'gzip'
end if
if used mimelike$texthtml | defined include_everything
cleartext mimelike$texthtml, 'text/html'
end if
if used mimelike$texthtmlutf8 | defined include_everything
cleartext mimelike$texthtmlutf8, 'text/html; charset=UTF-8'
end if
if used mimelike$textplain | defined include_everything
cleartext mimelike$textplain, 'text/plain'
end if
if used mimelike$pragma | defined include_everything
cleartext mimelike$pragma, 'Pragma'
end if
if used mimelike$cachecontrol | defined include_everything
cleartext mimelike$cachecontrol, 'Cache-Control'
end if
if used mimelike$location | defined include_everything
cleartext mimelike$location, 'Location'
end if
if used mimelike$etag | defined include_everything
cleartext mimelike$etag, 'ETag'
end if
if used mimelike$lastmodified | defined include_everything
cleartext mimelike$lastmodified, 'Last-Modified'
end if
if used mimelike$nocache | defined include_everything
cleartext mimelike$nocache, 'no-cache'
end if
if used mimelike$ifnonematch | defined include_everything
cleartext mimelike$ifnonematch, 'If-None-Match'
end if
if used mimelike$ifmodifiedsince | defined include_everything
cleartext mimelike$ifmodifiedsince, 'If-Modified-Since'
end if
if used mimelike$acceptranges | defined include_everything
cleartext mimelike$acceptranges, 'Accept-Ranges'
end if
if used mimelike$range | defined include_everything
cleartext mimelike$range, 'Range'
end if
if used mimelike$contentrange | defined include_everything
cleartext mimelike$contentrange, 'Content-Range'
end if
if used mimelike$bytes | defined include_everything
cleartext mimelike$bytes, 'bytes'
end if
if used mimelike$cookie | defined include_everything
cleartext mimelike$cookie, 'Cookie'
end if
if used mimelike$setcookie | defined include_everything
cleartext mimelike$setcookie, 'Set-Cookie'
end if
if used mimelike$setbody_external | defined include_everything
; three arguments: rdi == mimelike object, rsi == ptr to data, rdx == length of same
; NOTE: this does _not_ assume ownership of rsi, and does _not_ make a copy of it, so rsi/rdx must remain
; valid for the lifetime of this mimelike object (or at the very least through its compose stage(s))
; The data does _not_ get decoded, and absolutely nothing happens here other than setting our own
; internal copies of the values so that during compose we can use these _INSTEAD_ of mimelike_body_ofs
; see webserver.inc as to why this is used, but basically, we pass mmap'd base/size straight into here
; and we don't make a _double_ copy of it, until it is time for our final composition, at which point
; we _do_ make a copy of it
; bugfix for v1.21+: mimelike$new_parse_ext calls this, and checks the return value from here
; such that it can detect when a complete response is available, versions prior to this one were not
; actually checking for chunked transfer encoding completeness
falign
mimelike$setbody_external:
prolog mimelike$setbody_external
mov [rdi+mimelike_bodyext_ofs], rsi
mov [rdi+mimelike_bodyextlen_ofs], rdx
add rsi, rdx
mov [rdi+mimelike_bodyextend_ofs], rsi
; so at compose time, if bodyext is nonzero, we use that instead of the body itself
push rbx
mov rbx, rdi
mov rsi, mimelike$transferencoding
call mimelike$getheader
test rax, rax
jnz .transferencoding
mov eax, 1
pop rbx
epilog
calign
.transferencoding:
push r12 r13 r15
mov rdi, rax ; the transfer-encoding header
call string$to_lower
mov r12, rax
mov rdi, rax
mov rsi, mimelike$chunked
call string$indexof
mov rdi, r12
mov r12, rax
call heap$free
cmp r12, 0
jl .oneret
; otherwise, chunked is specified
mov r12, [rbx+mimelike_bodyext_ofs]
mov r13, [rbx+mimelike_bodyextlen_ofs]
sub rsp, 64
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
calign
.chunk_outer:
; walk forward til semicolon or CRLF
cmp r13, 5
jb .chunk_error_ret
mov r15, r12
calign
.chunklen:
cmp byte [r12], ';'
je .chunklen_semicolon
cmp word [r12], 0xa0d
je .chunklen_end
add r12, 1
sub r13, 1
cmp r13, 4
jb .chunk_error_ret
jmp .chunklen
calign
.chunklen_semicolon:
mov rax, r12
if string_bits = 32
lea rcx, [rsp+16]
else
lea rcx, [rsp+12]
end if
sub rax, r15
test rax, rax
jz .chunk_error_ret
cmp rax, r14
ja .chunk_error_ret
mov [rsp], rax
calign
.chunklen_semicolon_expand:
movzx edx, byte [r15]
add r15, 1
if string_bits = 32
mov [rcx], edx
add rcx, 4
else
mov [rcx], dx
add rcx, 2
end if
sub eax, 1
jnz .chunklen_semicolon_expand
; string at rsp is valid, walk forward to the CRLF
add r12, 1
sub r13, 1
cmp r13, 5
jb .chunk_error_ret
calign
.chunklen_semicolon_eol:
cmp word [r12], 0xa0d
je .chunklen_doit
add r12, 1
sub r13, 1
cmp r13, 5
jb .chunk_error_ret
jmp .chunklen_semicolon_eol
calign
.chunklen_end:
mov rax, r12
if string_bits = 32
lea rcx, [rsp+16]
else
lea rcx, [rsp+12]
end if
sub rax, r15
test rax, rax
jz .chunk_error_ret
cmp rax, r14
ja .chunk_error_ret
mov [rsp], rax
calign
.chunklen_expand:
movzx edx, byte [r15]
add r15, 1
if string_bits = 32
mov [rcx], edx
add rcx, 4
else
mov [rcx], dx
add rcx, 2
end if
sub eax, 1
jnz .chunklen_expand
calign
.chunklen_doit:
; skip the CRLF
add r12, 2
sub r13, 2
mov rdi, rsp
add qword [rsp], 2 ; include the 0x in the length
call string$to_unsigned
test rax, rax
jz .chunk_done
cmp rax, r13
ja .chunk_error_ret
; otherwise, add r12 for rax bytes
add r12, rax
sub r13, rax
cmp r13, 5
jb .chunk_error_ret
cmp word [r12], 0xa0d
jne .chunk_error_ret
add r12, 2
sub r13, 2
jmp .chunk_outer
calign
.chunk_done:
add rsp, 64
pop r15 r13 r12 rbx
mov eax, 1
epilog
.chunk_error_ret:
add rsp, 64
pop r15 r13 r12 rbx
xor eax, eax
epilog
calign
.oneret:
pop r15 r13 r12 rbx
mov eax, 1
epilog
end if
if used mimelike$setbody | defined include_everything
; three arguments: rdi == mimelike object, rsi == ptr to data, rdx == length of same
; NOTE: this is called by mimelike$new_parse during parse, but can also be used for on the fly body setting
; NOTE 2: we pay attention to headers to determine the correct decoding action, so caution is advised
; if you have headers that don't match the body content you are trying to set
falign
mimelike$setbody:
prolog mimelike$setbody
push rbx r12 r13 r14 r15
mov rbx, rdi
mov r12, rsi
mov r13, rdx
mov rdi, [rbx+mimelike_body_ofs]
call buffer$reset
; clear bodyext
mov qword [rbx+mimelike_bodyext_ofs], 0
test r13, r13
jz .nothingtodo
mov rdi, rbx
mov rsi, mimelike$transferencoding
call mimelike$getheader
mov r14, rax
mov rdi, rbx
mov rsi, mimelike$contentencoding
call mimelike$getheader
mov r15, rax
or rax, r14
jz .notgzippedorchunked
; otherwise, chunked + gzip, chunked, or gzip checks
; if no transfer encoding header, then check for gzip content encoding
test r14, r14
jz .check_gziponly
; otherwise, we got a transfer encoding, see if no content encoding
test r15, r15
jz .check_chunkedonly
; and for here, we have both headers, check chunked first, then check gzip
mov rdi, r14
call string$to_lower
mov r14, rax
mov rdi, rax
mov rsi, mimelike$chunked
call string$indexof
push rax
mov rdi, r14
call heap$free
pop r14
cmp r14, 0
jl .check_gziponly
; otherwise, chunked is specified, see if it is also gzip
mov rdi, r15
call string$to_lower
mov r15, rax
mov rdi, rax
mov rsi, mimelike$gzip
call string$indexof
push rax
mov rdi, r15
call heap$free
pop r15
cmp r15, 0
jl .do_chunkedonly
; if we made it to here, it is both gzip and chunked, which means we need an intermediate working buffer
call buffer$new
mov r14, rax
call .unchunk
test eax, eax
jz .gzipunchunkfailed
sub rsp, 64
mov rdi, rsp
mov esi, 2
call zlib$inflateInit
mov rax, [rbx+mimelike_body_ofs]
mov [rsp+zlib_inbuf_ofs], r14
mov [rsp+zlib_outbuf_ofs], rax
mov rdi, rsp
mov esi, zlib_no_flush
call zlib$inflate
mov rdi, rsp
call zlib$inflateEnd
mov rdi, r14
call buffer$destroy
add rsp, 64
mov eax, 1
pop r15 r14 r13 r12 rbx
epilog
calign
.gzipunchunkfailed:
pop r15 r14 r13 r12 rbx
xor eax, eax
epilog
calign
.check_chunkedonly:
; this one is different to having both, inthat we don't need an intermediate buffer
mov rdi, r14
call string$to_lower
mov r14, rax
mov rdi, rax
mov rsi, mimelike$chunked
call string$indexof
push rax
mov rdi, r14
call heap$free
pop rax
cmp rax, 0
jl .notgzippedorchunked
; otherwise, no gzip, but we have chunked goods
calign
.do_chunkedonly:
mov r14, [rbx+mimelike_body_ofs]
call .unchunk
; let its return be ours
pop r15 r14 r13 r12 rbx
epilog
falign
.unchunk:
; TODO: rewrite this hideous mess, hahah
; r14 is our intended destination buffer, r12/r13 source
mov rdi, r14
mov rsi, r13
call buffer$reserve
sub rsp, 64
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
calign
.unchunk_outer:
; walk forward til semicolon or CRLF
cmp r13, 5
jb .unchunk_done_error
mov r15, r12
calign
.unchunk_chunklen:
cmp byte [r12], ';'
je .unchunk_chunklen_semicolon
cmp word [r12], 0xa0d
je .unchunk_chunklen_end
add r12, 1
sub r13, 1
cmp r13, 4
jb .unchunk_done_error
jmp .unchunk_chunklen
calign
.unchunk_chunklen_semicolon:
mov rax, r12
if string_bits = 32
lea rcx, [rsp+16]
else
lea rcx, [rsp+12]
end if
sub rax, r15
test rax, rax
jz .unchunk_done_error
cmp rax, 14
ja .unchunk_done_error
mov [rsp], rax
calign
.unchunk_chunklen_semicolon_expand:
movzx edx, byte [r15]
add r15, 1
if string_bits = 32
mov [rcx], edx
add rcx, 4
else
mov [rcx], dx
add rcx, 2
end if
sub eax, 1
jnz .unchunk_chunklen_semicolon_expand
; string at rsp is valid, walk forward to the CRLF
add r12, 1
sub r13, 1
cmp r13, 5
jb .unchunk_done_error
calign
.unchunk_chunklen_semicolon_eol:
cmp word [r12], 0xa0d
je .unchunk_chunklen_doit
add r12, 1
sub r13, 1
cmp r13, 5
jb .unchunk_done_error
jmp .unchunk_chunklen_semicolon_eol
calign
.unchunk_chunklen_end:
mov rax, r12
if string_bits = 32
lea rcx, [rsp+16]
else
lea rcx, [rsp+12]
end if
sub rax, r15
test rax, rax
jz .unchunk_done_error
cmp rax, r14
ja .unchunk_done_error
mov [rsp], rax
calign
.unchunk_chunklen_expand:
movzx edx, byte [r15]
add r15, 1
if string_bits = 32
mov [rcx], edx
add rcx, 4
else
mov [rcx], dx
add rcx, 2
end if
sub eax, 1
jnz .unchunk_chunklen_expand
calign
.unchunk_chunklen_doit:
; skip the CRLF
add r12, 2
sub r13, 2
mov rdi, rsp
add qword [rsp], 2 ; include the 0x
call string$to_unsigned
test rax, rax
jz .unchunk_done
cmp rax, r13
ja .unchunk_done_error
; otehrwise, add r12 for rax bytes to r14
mov rdi, r14
mov rsi, r12
mov rdx, rax
add r12, rax
sub r13, rax
call buffer$append
; there should be a trailing crlf afterwards
cmp r13, 5
jb .unchunk_done_error
cmp word [r12], 0xa0d
jne .unchunk_done_error
add r12, 2
sub r13, 2
jmp .unchunk_outer
calign
.unchunk_done:
add rsp, 64
mov eax, 1
ret
calign
.unchunk_done_error:
add rsp, 64
xor eax, eax
ret
calign
.check_gziponly:
; if we have unchunked, plain gzip data, we don't need to copy it into a working buffer, we can create a "phantom/fake" buffer object
; that has its itself/length set to our passed contents, that way we don't double-copy it for unzip purposes
; first, lowercase the value in r15
mov rdi, r15
call string$to_lower
mov r15, rax
mov rdi, rax
mov rsi, mimelike$gzip
call string$indexof
push rax
mov rdi, r15
call heap$free
pop rax
cmp rax, 0
jl .notgzippedorchunked
; otherwise, gzip it is, so inflate it
mov rax, r12
add rax, r13
sub rsp, 128 ; buffer_object_size is 56, zlib_stream_size is also 56
mov [rsp+buffer_endptr_ofs], rax
mov [rsp+buffer_length_ofs], r13
mov [rsp+buffer_itself_ofs], r12
mov [rsp+buffer_size_ofs], r13
lea rdi, [rsp+64]
mov esi, 2
call zlib$inflateInit
mov rax, [rbx+mimelike_body_ofs]
lea rdi, [rsp+64]
mov [rdi+zlib_inbuf_ofs], rsp
mov [rdi+zlib_outbuf_ofs], rax
mov esi, zlib_no_flush
call zlib$inflate
; if that did _not_ succeed, well, something still may have ended up in the body
; we aren't too concerned with it inside here
lea rdi, [rsp+64]
call zlib$inflateEnd
add rsp, 128
mov eax, 1
pop r15 r14 r13 r12 rbx
epilog
calign
.notgzippedorchunked:
mov rdi, rbx
mov rsi, mimelike$contenttransferencoding
call mimelike$getheader
test rax, rax
jz .noparse
; make sure that gets lowercased
mov rdi, rax
call string$to_lower
mov r14, rax
; TODO: someday when I am bored, enforce the "short line limit" of 1000 here
; for now, we just pass them straight through if it is 7bit, 8bit, or binary
mov rdi, rax
mov rsi, mimelike$7bit
call string$indexof
cmp rax, 0
jge .noparse_free
mov rdi, r14
mov rsi, mimelike$8bit
call string$indexof
cmp rax, 0
jge .noparse_free
mov rdi, r14
mov rsi, mimelike$binary
call string$indexof
cmp rax, 0
jge .noparse_free
mov rdi, r14
mov rsi, mimelike$quotedprintable
call string$indexof
cmp rax, 0
jge .quotedprintable
mov rdi, r14
mov rsi, mimelike$base64
call string$indexof
cmp rax, 0
jge .base64
; otherwise, we don't recognize it, so pass the body striaght through
calign
.noparse_free:
mov rdi, r14
call heap$free
; fallthrough to noparse
calign
.noparse:
; so, not gzipped, not chunked, and no content transfer encoding, so just toss what we got straight in
mov rdi, [rbx+mimelike_body_ofs]
mov rsi, r12
mov rdx, r13
call buffer$append
mov eax, 1
pop r15 r14 r13 r12 rbx
epilog
calign
.quotedprintable:
; we have to undo the QP kak and make the actual body out of the decode
; free our lower case string first
mov rdi, r14
call heap$free
test r13, r13
jz .nothingtodo
mov rdi, [rbx+mimelike_body_ofs]
mov rsi, r13
call buffer$reserve
mov rdi, [rbx+mimelike_body_ofs]
mov r14, [rdi+buffer_itself_ofs]
sub rsp, 64
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
calign
.qploop:
movzx eax, byte [r12]
add r12, 1
sub r13, 1
cmp eax, '='
je .qploop_equalsign
mov byte [r14], al
add r14, 1
test r13, r13
jnz .qploop
calign
.qpdone:
mov rsi, r14
mov rdi, [rbx+mimelike_body_ofs]
sub rsi, [rdi+buffer_itself_ofs]
call buffer$append_nocopy
add rsp, 64
mov eax, 1
pop r15 r14 r13 r12 rbx
epilog
calign
.qploop_equalsign:
cmp r13, 2
jb .qpdone
; lots of in the wilds don't do CRLF, and just LF instead, so check for that too
movzx eax, word [r12]
cmp eax, 0xa0d
je .qploop_softlinebreak
cmp byte [r12], 0xa
je .qploop_softlinebreak_lf
cmp al, 32
je .qploop_paddedline
; otherwise, this must be 2 hex digits
add r12, 2
movzx ecx, ah
and eax, 0xff
if string_bits = 32
mov [rsp+16], eax
mov [rsp+20], ecx
else
mov [rsp+12], ax
mov [rsp+14], cx
end if
mov rdi, rsp
call string$to_unsigned
mov byte [r14], al
add r14, 1
sub r13, 2
jnz .qploop
jmp .qpdone
calign
.qploop_softlinebreak:
add r12, 2
sub r13, 2
jnz .qploop
jmp .qpdone
calign
.qploop_softlinebreak_lf:
add r12, 1
sub r13, 1
jnz .qploop
jmp .qpdone
calign
.qploop_paddedline:
add r12, 1
sub r13, 1
jz .qpdone
calign
.qploop_paddedline_findlf:
cmp r13, 2
jb .qpdone
cmp word [r12], 0xa0d
je .qploop_softlinebreak
cmp byte [r12], 0xa
je .qploop_softlinebreak_lf
add r12, 1
sub r13, 1
jnz .qploop_paddedline_findlf
jmp .qpdone
calign
.nothingtodo:
mov eax, 1
pop r15 r14 r13 r12 rbx
epilog
calign
.base64:
; free our lower case string first
mov rdi, r14
call heap$free
; base64 decode what we got passed
mov rdi, [rbx+mimelike_body_ofs]
mov rsi, r13
call buffer$reserve ; TODO: /4*3 instead lazy, wasting space in the heap here
mov rax, [rbx+mimelike_body_ofs]
mov rdi, r12
mov rsi, r13
mov rdx, [rax+buffer_itself_ofs]
xor ecx, ecx
call base64$decode_latin1
mov rdi, [rbx+mimelike_body_ofs]
mov rsi, rax
call buffer$append_nocopy
mov eax, 1
pop r15 r14 r13 r12 rbx
epilog
end if
if used mimelike$new_parse | defined include_everything
; four arguments: rdi == ptr to utf8 goods, rsi == length of same, edx == bool for headers only, ecx == bool for whether there is a preface or not
; returns a mimelike object or null on error
;
; NOTE: For duplicate headers, this adds a comma to the previous one, and appends the duplicate value, instead of overwriting it, or actually
; making a duplicate, all of my use scenarios thus far, this has been fine and dandy, but it might be restricting in certain [rare?] cases, haha
; NOTE 2: compose will go through a lot of extra trouble to deal with Set-Cookie, which for older browsers, seems to be the only header that doesn't
; really let me do the ', ' concat + unique headers.
falign
mimelike$new_parse:
prolog mimelike$new_parse
test rdi, rdi
jz .nullret ; sanity only
cmp rsi, 8
jb .nullret
test ecx, ecx
jz .nopreface
; walk forward til EOL
mov rcx, rdi
mov r9, rsi
calign
.find_preface_eol:
cmp word [rcx], 0xa0d
je .found_preface_crlf
cmp byte [rcx], 0xd
je .found_preface_lf
add rcx, 1
sub r9, 1
cmp r9, 2
ja .find_preface_eol
calign
.nullret:
xor eax, eax
epilog
calign
.found_preface_crlf:
mov rax, rcx
push rdi ; save the preface start pointer
sub rax, rdi
push rax ; and its length
add rcx, 2
sub rcx, rdi
sub rsi, rcx
add rdi, rcx
calign
.found_preface_doit:
add rsi, rdi
push rcx ; save the length of the preface, including the crlf/lf
call .parsemimepart
pop r8
test rax, rax
jz .preface_error_ret
add [rax+mimelike_hdrlen_ofs], r8
add [rax+mimelike_parselen_ofs], r8
; otherwise, set our preface
pop rsi
mov rdi, [rsp]
mov [rsp], rax
call string$from_utf8
mov rdi, [rsp]
mov rsi, rax
call mimelike$setpreface_nocopy
pop rax
epilog
calign
.found_preface_lf:
mov rax, rcx
push rdi ; save the preface start pointer
sub rax, rdi
push rax ; and its length
add rcx, 1
sub rcx, rdi
sub rsi, rcx
add rdi, rcx
jmp .found_preface_doit
calign
.preface_error_ret:
add rsp, 16
epilog
calign
.nopreface:
add rsi, rdi
mov ecx, r8d
call .parsemimepart
epilog
falign
.parsemimepart:
; rdi == start ptr, rsi == end ptr, edx == bool for headers only
push rbx r12 r13 r14 r15
mov r12, rdi
mov r13, rsi
mov r14, rdi
sub rsp, 64
mov [rsp], edx
mov dword [rsp+4], 0 ; number of header lines we have got
mov [rsp+8], rdi ; header start position
mov qword [rsp+32], 0 ; header key length
call mimelike$new
mov rbx, rax
mov [rax+mimelike_user_ofs], r12 ; save the starting pointer
add r14, 4
calign
.headers_outer:
cmp r14, r13
ja .headers_error
xor r15d, r15d ; how long the current line is
mov dword [rsp+24], 0 ; bool as to whether we are on a continuation line or not
cmp dword [rsp+4], 0 ; no header lines yet means it can't be a continuation
je .headers_normal
xor eax, eax
mov ecx, 1
xor edx, edx
cmp byte [r12], 32
cmove eax, ecx
cmp byte [r12], 9
cmove edx, ecx
or eax, edx
jz .headers_normal
; continuation line
calign
.headers_cont_skipspace:
cmp r14, r13
ja .headers_error
cmp word [r12], 0xa0d
je .headers_error
cmp byte [r12], 0xa
je .headers_error
xor eax, eax
cmp byte [r12], 32
cmove eax, ecx
cmp byte [r12], 9
cmove eax, ecx
test eax, eax
jz .headers_cont_spacedone
add r12, 1
add r14, 1
add r15d, 1
jmp .headers_cont_skipspace
calign
.headers_cont_spacedone:
mov [rsp+16], r12 ; value starting position
mov dword [rsp+24], 1 ; bool set for continuation line
mov ecx, 1
jmp .headers_valscan
calign
.headers_normal:
mov [rsp+8], r12
; hmmm, probably should redo how this little tidbit works
calign
.headers_normal_loop:
cmp r14, r13
ja .headers_error
movzx edx, byte [r12]
cmp word [r12], 0xa0d
je .headers_implicit ; this shouldn't happen in normal use, but if a boundary is followed by _exactly_ CRLF
; then we are supposed to assume Content-Type: text/plain as our header
cmp edx, 32
jb .headers_error
cmp edx, ':'
je .headers_normal_keydone
add r12, 1
add r14, 1
add r15d, 1
jmp .headers_normal_loop
calign
.headers_normal_keydone:
cmp r12, [rsp+8] ; keyless not okay
je .headers_error
cmp byte [r12+1], ' '
jne .headers_normal_keydone_checkemptyvalue
mov rax, r12
sub rax, [rsp+8]
mov [rsp+32], rax ; header key length
add r12, 2
add r14, 2
add r15d, 2
; cruise forward and skip spaces
mov ecx, 1
mov [rsp+16], r12 ; value starting position
calign
.headers_normal_skipspace:
cmp r14, r13
ja .headers_error
movzx edx, byte [r12]
xor eax, eax
cmp edx, 32
cmove eax, ecx
cmp edx, 9
cmove eax, ecx
test eax, eax
jz .headers_valscan
add r12, 1
add r14, 1
add r15d, 1
mov [rsp+16], r12 ; value starting position
jmp .headers_normal_skipspace
calign
.headers_normal_keydone_checkemptyvalue:
cmp word [r12+1], 0xa0d
je .headers_normal_emptyvalue
cmp byte [r12+1], 0xa
jne .headers_error
calign
.headers_normal_emptyvalue:
mov rax, r12
sub rax, [rsp+8]
mov [rsp+32], rax ; header key length
add r12, 1
add r14, 1
add r15d, 1
; cruise forward and skip spaces
mov ecx, 1
mov [rsp+16], r12 ; value starting position
calign
.headers_valscan:
; go til eol
cmp r14, r13
ja .headers_error
xor eax, eax
cmp word [r12], 0xa0d
cmove eax, ecx
cmp byte [r12], 0xa
cmove eax, ecx
test eax, eax
jnz .headers_valscan_done
add r12, 1
add r14, 1
add r15d, 1
jmp .headers_valscan
calign
.headers_valscan_done:
; the rfc says the max line length is 998, but apparently no one adheres to that
cmp r15d, 8192
ja .headers_error
cmp qword [rsp+32], 0 ; sanity only
je .headers_error
; otherwise, looks like a valid header line
mov rax, r12
sub rax, [rsp+16]
mov [rsp+40], rax ; value length
add dword [rsp+4], 1 ; number of headerlines we have
; create strings for both
mov rdi, [rsp+8] ; header starting position
mov rsi, [rsp+32] ; header key length
call string$from_utf8
mov [rsp+48], rax
mov rdi, [rsp+16] ; value starting position
mov rsi, [rsp+40] ; value length
call string$from_utf8
mov [rsp+56], rax ; save the parsed value for this line
cmp dword [rsp+24], 1
je .headers_setcontinuation
mov rdi, rbx
mov rsi, [rsp+48] ; the key we are working with
call mimelike$getheader
test rax, rax
jnz .headers_setduplicate
mov rdi, rbx
mov rsi, [rsp+48]
mov rdx, [rsp+56]
call mimelike$setheader_nocopy
cmp dword [r12], 0xa0d0a0d
je .headers_complete
cmp word [r12], 0xa0a
je .headers_complete
mov eax, 1
mov ecx, 2
cmp word [r12], 0xa0d
cmove eax, ecx
add r12, rax
add r14, rax
jmp .headers_outer
calign
.headers_setduplicate:
mov rdi, .commaspacestr
mov rsi, [rsp+56]
call string$concat
jmp .headers_append_doit
cleartext .commaspacestr, ', '
cleartext .spacestr, ' '
calign
.headers_setcontinuation:
; this is horrifically inefficient, probably should redo this, hehe
mov rdi, .spacestr
mov rsi, rax
call string$concat
calign
.headers_append_doit:
mov rdi, [rsp+56]
mov [rsp+56], rax
call heap$free
mov rdi, rbx
mov rsi, [rsp+48] ; the key we are working with
call mimelike$getheader
test rax, rax ; sanity only
jz .headers_continuation_error
mov rdi, rax
mov rsi, [rsp+56]
call string$concat
mov rdi, [rsp+56]
mov [rsp+56], rax
call heap$free
mov rdi, rbx
mov rsi, [rsp+48]
mov rdx, [rsp+56]
call mimelike$setheader_nocopy
cmp dword [r12], 0xa0d0a0d
je .headers_complete
cmp word [r12], 0xa0a
je .headers_complete
mov eax, 1
mov ecx, 2
cmp word [r12], 0xa0d
cmove eax, ecx
add r12, rax
add r14, rax
jmp .headers_outer
calign
.headers_continuation_error:
; free both strings and puke
mov rdi, [rsp+48]
call heap$free
mov rdi, [rsp+56]
call heap$free
jmp .headers_error
cleartext .mp1, 'multipart/'
cleartext .mp2, 'boundary='
cleartext .mp3, 'boundary="'
calign
.headers_implicit:
add r12, 2
add r14, 2
; set the implicit content type
mov rdi, rbx
mov rsi, mimelike$contenttype
mov rdx, mimelike$textplain
call mimelike$setheader
jmp .notmultipart
calign
.headers_complete:
mov eax, 2
mov ecx, 4
cmp dword [r12], 0xa0d0a0d
cmove eax, ecx
add r12, rax
add r14, rax
; set the hdrlen
mov rcx, r12
sub rcx, [rbx+mimelike_user_ofs]
mov [rbx+mimelike_hdrlen_ofs], rcx
mov [rbx+mimelike_parselen_ofs], rcx
; so, we skipped the header end, if headers only was requested, now is the time to return
cmp dword [rsp], 0
jne .headers_complete_doreturn
; if we have a content length header, make sure we have enough data, or return null
mov rdi, rbx
mov rsi, mimelike$contentlength
call mimelike$getheader
test rax, rax
jz .headers_complete_nocontentlength
mov rdi, rax
call string$to_unsigned
mov rcx, r13
sub rcx, r12
cmp rax, rcx
ja .headers_error
add [rbx+mimelike_parselen_ofs], rcx
; for cases where we do NOT have a "preamble" (default body type), _and_ we got presented
; a content-length (as in the case of POST w/ multipart/form-data et al)
; set the outermost body to the complete body
mov rdi, rbx
mov rsi, r12
mov rdx, r13
sub rdx, r12
call mimelike$setbody
calign
.headers_complete_nocontentlength:
; otherwise, check to see if it is a multipart content type, otherwise set our body
mov rdi, rbx
mov rsi, mimelike$contenttype
call mimelike$getheader
test rax, rax
jz .notmultipart
mov r15, rax
mov rdi, rax
mov rsi, .mp1
call string$indexof
cmp rax, 0
jl .notmultipart
mov rdi, r15
mov rsi, .mp2
call string$indexof
cmp rax, 0
jl .notmultipart
; since our multipart boundary preface includes the preceding CRLF (or just LF)
; we have to unwind our pointer in r12 by either 1 or 2
xor ecx, ecx
mov edx, 1
sub r12, 2
cmp word [r12], 0xa0a
cmove ecx, edx
add r12, rcx
; otherwise, it contains multipart/ and boundary=, so parts it is
; it is possible that the boundary is quoted
mov [rsp], rax
mov rdi, r15
mov rsi, .mp3
call string$indexof
cmp rax, 0
jl .multipart_notquoted
mov rdx, [rsp]
mov esi, '"'
mov rdi, r15
add rdx, 11
call string$indexof_charcode_ofs
mov rdi, r15
mov rsi, [rsp]
mov rdx, rax
add rsi, 10
call string$substring
mov [rsp+8], rax
; set the mimelike$boundary
mov rdi, r15
xor esi, esi
mov rdx, [rsp]
call string$substring
mov [rsp+16], rax
mov rdi, rbx
mov rsi, [rsp+8] ; setboundary assumes ownership of this
mov rdx, rax
call mimelike$setboundary
mov rdi, [rsp+16]
call heap$free
; \r\n--boundary\r\n, \n--boundary\n, \r\n--boundary--\r\n, \n--boundary--\n
; [rsp+8] is our boundary
; [rsp] is the indexof substring that we are done with now
jmp .multipart_boundarycheck
cleartext .crlfdashdash, 13,10,'--'
cleartext .crlf, 13,10
cleartext .lfdashdash, 10,'--'
cleartext .lf, 10
cleartext .dashdashcrlf, '--',13,10
cleartext .dashdashlf, '--',10
calign
.multipart_notquoted:
mov rsi, [rsp]
mov rdi, r15
mov rdx, -1
add rsi, 9
call string$substr ; boundary substring
mov [rsp+8], rax
; set the mimelike boundary
mov rdi, r15
xor esi, esi
mov rdx, [rsp]
call string$substring ; content type substring
mov [rsp+16], rax
mov rdi, rbx
mov rsi, [rsp+8] ; setboundary assumes ownership of this
mov rdx, rax
call mimelike$setboundary
mov rdi, [rsp+16]
call heap$free
calign
.multipart_boundarycheck:
; to be tolerant, e.g. cutting/pasting a mime encoded session, we may not necessarily
; have CRLF delimiters, despite the spec demanding it does... so if we can't find a CRLF
; we check for LF only based ones too
mov rdi, .crlfdashdash
mov rsi, [rsp+8]
call string$concat
mov r15, rax
mov rdi, rax
mov rsi, .crlf
call string$concat
mov rdi, r15
mov r15, rax
call heap$free
; turn that into a UTF8 encoding
mov rdi, r15
call string$utf8_length
mov rdi, rax
call heap$alloc
; make sure we can find it
mov [rsp], rax
mov rdi, r15
mov rsi, rax
call string$to_utf8
mov [rsp+16], rax
; we are done with r15
mov rdi, r15
call heap$free
xor edi, edi ; starting position for search
mov rsi, [rsp] ; pattern we are searching for
mov rdx, [rsp+16] ; pattern length
call .byte_indexof
; so at this point, [rsp] == our utf8 CRLF--boundaryCRLF
; [rsp+8] is the actual boundary
; [rsp+16] is the length of [rsp]
mov [rsp+24], rax ; save the starting marker
cmp rax, -1
je .multipart_boundary_checklf
; otherwise, it was valid, so create our ending boundary and its utf8 form as well
mov rdi, .crlfdashdash
mov rsi, [rsp+8]
call string$concat
mov r15, rax
mov rdi, rax
mov rsi, .dashdashcrlf
call string$concat
mov rdi, r15
mov r15, rax
call heap$free
; turn that into a UTF8 encoding
mov rdi, r15
call string$utf8_length
mov rdi, rax
call heap$alloc
mov [rsp+32], rax
mov rdi, r15
mov rsi, rax
call string$to_utf8
mov [rsp+40], rax
; we are done with r15
mov rdi, r15
call heap$free
jmp .multipart_boundary_doit
calign
.multipart_boundary_checklf:
; free our UTF8 buffer, and do it again with lf instead of crlf
mov rdi, [rsp]
call heap$free
mov rdi, .lfdashdash
mov rsi, [rsp+8]
call string$concat
mov r15, rax
mov rdi, rax
mov rsi, .lf
call string$concat
mov rdi, r15
mov r15, rax
call heap$free
; turn that into a UTF8 encoding
mov rdi, r15
call string$utf8_length
mov rdi, rax
call heap$alloc
; make sure we can find it
mov [rsp], rax
mov rdi, r15
mov rsi, rax
call string$to_utf8
mov [rsp+16], rax
; we are done with r15
mov rdi, r15
call heap$free
xor edi, edi
mov rsi, [rsp] ; pattern we are searching for
mov rdx, [rsp+16] ; pattern length
call .byte_indexof
; so at this point, [rsp] == our utf8 CRLF--boundaryCRLF
; [rsp+8] is the actual boundary
; [rsp+16] is the length of [rsp]
cmp rax, -1
je .multipart_boundary_error
mov [rsp+24], rax ; save the starting marker
; otherwise, we need the same again for the ending boundary
mov rdi, .lfdashdash
mov rsi, [rsp+8]
call string$concat
mov r15, rax
mov rdi, rax
mov rsi, .dashdashlf
call string$concat
mov rdi, r15
mov r15, rax
call heap$free
; turn that into a UTF8 encoding
mov rdi, r15
call string$utf8_length
mov rdi, rax
call heap$alloc
mov [rsp+32], rax
mov rdi, r15
mov rsi, rax
call string$to_utf8
mov [rsp+40], rax
; we are done with r15
mov rdi, r15
call heap$free
calign
.multipart_boundary_doit:
; so now:
; [rsp] is our utf non-last boundary
; [rsp+8] is the actual boundary string
; [rsp+16] is the length of the non-last boundary
; [rsp+24] is the starting marker position
; [rsp+32] is the utf8 of the last boundary
; [rsp+40] is the length of the last boundary
; so we may have a preamble if the starting marker is >0, in which case
; we need to set our body to it
cmp qword [rsp+24], 0
je .multipart_boundary_nopreamble
; we have to undo the temporary step backward for the prefix start
mov eax, 1
mov ecx, 2
cmp word [r12], 0xa0d
cmove eax, ecx
; hmmm, that may not always work exactly right... TODO: come back and redo this by storing the value previously used
mov rdi, rbx
mov rsi, r12
add rsi, rax
mov rdx, [rsp+24]
sub rdx, rax
call mimelike$setbody
calign
.multipart_boundary_nopreamble:
mov rdi, [rsp+16]
add rdi, [rsp+24]
mov rsi, [rsp]
mov rdx, [rsp+16]
call .byte_indexof
cmp rax, -1
je .multipart_boundary_checklast
calign
.multipart_boundary_addpart:
mov [rsp+48], rax ; save the ending marker
mov rdi, r12
add rdi, [rsp+24]
add rdi, [rsp+16]
mov rsi, r12
add rsi, rax
xor edx, edx
call .parsemimepart
mov rcx, [rsp+48]
mov [rsp+24], rcx ; starting marker == ending marker for next round
test rax, rax
jz .multipart_boundary_nopreamble
mov rdi, [rbx+mimelike_parts_ofs]
mov rsi, rax
mov [rax+mimelike_parent_ofs], rbx
call list$push_back
jmp .multipart_boundary_nopreamble
calign
.multipart_boundary_checklast:
mov rdi, [rsp+16]
add rdi, [rsp+24]
mov rsi, [rsp+32]
mov rdx, [rsp+40]
call .byte_indexof
cmp rax, -1
jne .multipart_boundary_addpart
calign
.multipart_boundary_doreturn:
mov rdi, [rsp]
call heap$free
; double-free fixup: 20160808: mimelike$setboundary assumed ownership of this:
; mov rdi, [rsp+8]
; call heap$free
; end fixup
mov rdi, [rsp+32]
call heap$free
jmp .normal_return
calign
.multipart_boundary_error:
; free our UTF8 first
mov rdi, [rsp]
call heap$free
; double-free fixup: 20160808: mimelike$setboundary assumed ownership of this
; free the boundary string itself
; mov rdi, [rsp+8]
; call heap$free
; end fixup
; destroy our mimelike object? if we didn't find a boundary
; this mime object is invalid, so hmmm, we could be more
; tolerant, and return it as-is without further parsing I spose
; hmm, yeah, that is what we'll do for now
jmp .normal_return
calign
.notmultipart:
; some interesting error condition possibilities here that we need to sort:
; if our transfer encoding is chunked, and setbody returns us with 0, fail
; if transfer encoding != chunked, then check for content-length, and if
; content length is set, and we don't have enough data, fail
mov rdi, rbx
mov rsi, mimelike$contentlength
call mimelike$getheader
test rax, rax
jz .notmultipart_nocl
mov rdi, rax
call string$to_unsigned
mov rdx, r13
sub rdx, r12
cmp rax, rdx
ja .headers_error
add [rbx+mimelike_parselen_ofs], rax
mov rdi, rbx
mov rsi, r12
mov rdx, rax
call mimelike$setbody
test eax, eax
jz .headers_error
jmp .normal_return
calign
.notmultipart_nocl:
mov rdi, rbx
mov rsi, r12
mov rdx, r13
sub rdx, r12
call mimelike$setbody
test eax, eax
jz .headers_error
calign
.headers_complete_doreturn:
.normal_return:
add rsp, 64
mov rax, rbx
pop r15 r14 r13 r12 rbx
ret
calign
.headers_error:
mov rdi, rbx
call mimelike$destroy
add rsp, 64
xor eax, eax
pop r15 r14 r13 r12 rbx
ret
falign
.byte_indexof:
; r12 is our input
; r13 is our input end
; rdi = starting position for our search
; rsi == bytes for search pattern
; rdx == length of search pattern
mov r8, r13
sub r8, r12
sub r8, rdx
add r8, r12 ; ending position
add rdi, r12 ; starting position
cmp rdi, r8
ja .byte_indexof_negone
calign
.byte_indexof_outer:
xor ecx, ecx
mov r9, rdx
calign
.byte_indexof_inner:
movzx eax, byte [rdi+rcx]
cmp al, byte [rsi+rcx]
jne .byte_indexof_next
add rcx, 1
sub r9, 1
jnz .byte_indexof_inner
mov rax, rdi
sub rax, r12
ret
calign
.byte_indexof_next:
add rdi, 1
cmp rdi, r8
jbe .byte_indexof_outer
calign
.byte_indexof_negone:
mov rax, -1
ret
end if
if used mimelike$new_parse_ext | defined include_everything
; four arguments: rdi == ptr to utf8 goods, rsi == length of same, edx == bool for headers only, ecx == bool for whether there is a preface or not
; returns a mimelike object or null on error
;
; this is the same thing as new_parse, only instead of setbody, setbody_external is used and no decoding is performed
; CAUTION: this means that the buffer that is passed in here is referenced by the returned mimelike object, and as a result must exist
; for the lifetime of this mimelike object!! (unlike the normal new_parse, which decodes/copies it)
falign
mimelike$new_parse_ext:
prolog mimelike$new_parse_ext
test rdi, rdi
jz .nullret ; sanity only
cmp rsi, 8
jb .nullret
test ecx, ecx
jz .nopreface
; walk forward til EOL
mov rcx, rdi
mov r9, rsi
calign
.find_preface_eol:
cmp word [rcx], 0xa0d
je .found_preface_crlf
cmp byte [rcx], 0xd
je .found_preface_lf
add rcx, 1
sub r9, 1
cmp r9, 2
ja .find_preface_eol
calign
.nullret:
xor eax, eax
epilog
calign
.found_preface_crlf:
mov rax, rcx
push rdi ; save the preface start pointer
sub rax, rdi
push rax ; and its length
add rcx, 2
sub rcx, rdi
sub rsi, rcx
add rdi, rcx
calign
.found_preface_doit:
add rsi, rdi
push rcx ; save the length of the preface, including the crlf/lf
call .parsemimepart
pop r8
test rax, rax
jz .preface_error_ret
add [rax+mimelike_hdrlen_ofs], r8
add [rax+mimelike_parselen_ofs], r8
; otherwise, set our preface
pop rsi
mov rdi, [rsp]
mov [rsp], rax
call string$from_utf8
mov rdi, [rsp]
mov rsi, rax
call mimelike$setpreface_nocopy
pop rax
epilog
calign
.found_preface_lf:
mov rax, rcx
push rdi ; save the preface start pointer
sub rax, rdi
push rax ; and its length
add rcx, 1
sub rcx, rdi
sub rsi, rcx
add rdi, rcx
jmp .found_preface_doit
calign
.preface_error_ret:
add rsp, 16
epilog
calign
.nopreface:
add rsi, rdi
mov ecx, r8d
call .parsemimepart
epilog
falign
.parsemimepart:
; rdi == start ptr, rsi == end ptr, edx == bool for headers only
push rbx r12 r13 r14 r15
mov r12, rdi
mov r13, rsi
mov r14, rdi
sub rsp, 64
mov [rsp], edx
mov dword [rsp+4], 0 ; number of header lines we have got
mov [rsp+8], rdi ; header start position
mov qword [rsp+32], 0 ; header key length
call mimelike$new
mov rbx, rax
mov [rax+mimelike_user_ofs], r12 ; save the starting pointer
add r14, 4
calign
.headers_outer:
cmp r14, r13
ja .headers_error
xor r15d, r15d ; how long the current line is
mov dword [rsp+24], 0 ; bool as to whether we are on a continuation line or not
cmp dword [rsp+4], 0 ; no header lines yet means it can't be a continuation
je .headers_normal
xor eax, eax
mov ecx, 1
xor edx, edx
cmp byte [r12], 32
cmove eax, ecx
cmp byte [r12], 9
cmove edx, ecx
or eax, edx
jz .headers_normal
; continuation line
calign
.headers_cont_skipspace:
cmp r14, r13
ja .headers_error
cmp word [r12], 0xa0d
je .headers_error
cmp byte [r12], 0xa
je .headers_error
xor eax, eax
cmp byte [r12], 32
cmove eax, ecx
cmp byte [r12], 9
cmove eax, ecx
test eax, eax
jz .headers_cont_spacedone
add r12, 1
add r14, 1
add r15d, 1
jmp .headers_cont_skipspace
calign
.headers_cont_spacedone:
mov [rsp+16], r12 ; value starting position
mov dword [rsp+24], 1 ; bool set for continuation line
mov ecx, 1
jmp .headers_valscan
calign
.headers_normal:
mov [rsp+8], r12
; hmmm, probably should redo how this little tidbit works
calign
.headers_normal_loop:
cmp r14, r13
ja .headers_error
movzx edx, byte [r12]
cmp word [r12], 0xa0d
je .headers_implicit ; this shouldn't happen in normal use, but if a boundary is followed by _exactly_ CRLF
; then we are supposed to assume Content-Type: text/plain as our header
cmp edx, 32
jb .headers_error
cmp edx, ':'
je .headers_normal_keydone
add r12, 1
add r14, 1
add r15d, 1
jmp .headers_normal_loop
calign
.headers_normal_keydone:
cmp r12, [rsp+8] ; keyless not okay
je .headers_error
cmp byte [r12+1], ' '
jne .headers_normal_keydone_checkemptyvalue
mov rax, r12
sub rax, [rsp+8]
mov [rsp+32], rax ; header key length
add r12, 2
add r14, 2
add r15d, 2
; cruise forward and skip spaces
mov ecx, 1
mov [rsp+16], r12 ; value starting position
calign
.headers_normal_skipspace:
cmp r14, r13
ja .headers_error
movzx edx, byte [r12]
xor eax, eax
cmp edx, 32
cmove eax, ecx
cmp edx, 9
cmove eax, ecx
test eax, eax
jz .headers_valscan
add r12, 1
add r14, 1
add r15d, 1
mov [rsp+16], r12 ; value starting position
jmp .headers_normal_skipspace
calign
.headers_normal_keydone_checkemptyvalue:
cmp word [r12+1], 0xa0d
je .headers_normal_emptyvalue
cmp byte [r12+1], 0xa
jne .headers_error
calign
.headers_normal_emptyvalue:
mov rax, r12
sub rax, [rsp+8]
mov [rsp+32], rax ; header key length
add r12, 1
add r14, 1
add r15d, 1
; cruise forward and skip spaces
mov ecx, 1
mov [rsp+16], r12 ; value starting position
calign
.headers_valscan:
; go til eol
cmp r14, r13
ja .headers_error
xor eax, eax
cmp word [r12], 0xa0d
cmove eax, ecx
cmp byte [r12], 0xa
cmove eax, ecx
test eax, eax
jnz .headers_valscan_done
add r12, 1
add r14, 1
add r15d, 1
jmp .headers_valscan
calign
.headers_valscan_done:
; the rfc says the max line length is 998, but apparently no one adheres to that
cmp r15d, 8192
ja .headers_error
cmp qword [rsp+32], 0 ; sanity only
je .headers_error
; otherwise, looks like a valid header line
mov rax, r12
sub rax, [rsp+16]
mov [rsp+40], rax ; value length
add dword [rsp+4], 1 ; number of headerlines we have
; create strings for both
mov rdi, [rsp+8] ; header starting position
mov rsi, [rsp+32] ; header key length
call string$from_utf8
mov [rsp+48], rax
mov rdi, [rsp+16] ; value starting position
mov rsi, [rsp+40] ; value length
call string$from_utf8
mov [rsp+56], rax ; save the parsed value for this line
cmp dword [rsp+24], 1
je .headers_setcontinuation
mov rdi, rbx
mov rsi, [rsp+48] ; the key we are working with
call mimelike$getheader
test rax, rax
jnz .headers_setduplicate
mov rdi, rbx
mov rsi, [rsp+48]
mov rdx, [rsp+56]
call mimelike$setheader_nocopy
cmp dword [r12], 0xa0d0a0d
je .headers_complete
cmp word [r12], 0xa0a
je .headers_complete
mov eax, 1
mov ecx, 2
cmp word [r12], 0xa0d
cmove eax, ecx
add r12, rax
add r14, rax
jmp .headers_outer
calign
.headers_setduplicate:
mov rdi, .commaspacestr
mov rsi, [rsp+56]
call string$concat
jmp .headers_append_doit
cleartext .commaspacestr, ', '
cleartext .spacestr, ' '
calign
.headers_setcontinuation:
; this is horrifically inefficient, probably should redo this, hehe
mov rdi, .spacestr
mov rsi, rax
call string$concat
calign
.headers_append_doit:
mov rdi, [rsp+56]
mov [rsp+56], rax
call heap$free
mov rdi, rbx
mov rsi, [rsp+48] ; the key we are working with
call mimelike$getheader
test rax, rax ; sanity only
jz .headers_continuation_error
mov rdi, rax
mov rsi, [rsp+56]
call string$concat
mov rdi, [rsp+56]
mov [rsp+56], rax
call heap$free
mov rdi, rbx
mov rsi, [rsp+48]
mov rdx, [rsp+56]
call mimelike$setheader_nocopy
cmp dword [r12], 0xa0d0a0d
je .headers_complete
cmp word [r12], 0xa0a
je .headers_complete
mov eax, 1
mov ecx, 2
cmp word [r12], 0xa0d
cmove eax, ecx
add r12, rax
add r14, rax
jmp .headers_outer
calign
.headers_continuation_error:
; free both strings and puke
mov rdi, [rsp+48]
call heap$free
mov rdi, [rsp+56]
call heap$free
jmp .headers_error
cleartext .mp1, 'multipart/'
cleartext .mp2, 'boundary='
cleartext .mp3, 'boundary="'
calign
.headers_implicit:
add r12, 2
add r14, 2
; set the implicit content type
mov rdi, rbx
mov rsi, mimelike$contenttype
mov rdx, mimelike$textplain
call mimelike$setheader
jmp .notmultipart
calign
.headers_complete:
mov eax, 2
mov ecx, 4
cmp dword [r12], 0xa0d0a0d
cmove eax, ecx
add r12, rax
add r14, rax
; set the hdrlen
mov rcx, r12
sub rcx, [rbx+mimelike_user_ofs]
mov [rbx+mimelike_hdrlen_ofs], rcx
mov [rbx+mimelike_parselen_ofs], rcx
; so, we skipped the header end, if headers only was requested, now is the time to return
cmp dword [rsp], 0
jne .headers_complete_doreturn
; if we have a content length header, make sure we have enough data, or return null
mov rdi, rbx
mov rsi, mimelike$contentlength
call mimelike$getheader
test rax, rax
jz .headers_complete_nocontentlength
mov rdi, rax
call string$to_unsigned
mov rcx, r13
sub rcx, r12
cmp rax, rcx
ja .headers_error
add [rbx+mimelike_parselen_ofs], rcx
; for cases where we do NOT have a "preamble" (default body type), _and_ we got presented
; a content-length (as in the case of POST w/ multipart/form-data et al)
; set the outermost body to the complete body
mov rdi, rbx
mov rsi, r12
mov rdx, r13
sub rdx, r12
call mimelike$setbody_external
calign
.headers_complete_nocontentlength:
; otherwise, check to see if it is a multipart content type, otherwise set our body
mov rdi, rbx
mov rsi, mimelike$contenttype
call mimelike$getheader
test rax, rax
jz .notmultipart
mov r15, rax
mov rdi, rax
mov rsi, .mp1
call string$indexof
cmp rax, 0
jl .notmultipart
mov rdi, r15
mov rsi, .mp2
call string$indexof
cmp rax, 0
jl .notmultipart
; since our multipart boundary preface includes the preceding CRLF (or just LF)
; we have to unwind our pointer in r12 by either 1 or 2
xor ecx, ecx
mov edx, 1
sub r12, 2
cmp word [r12], 0xa0a
cmove ecx, edx
add r12, rcx
; otherwise, it contains multipart/ and boundary=, so parts it is
; it is possible that the boundary is quoted
mov [rsp], rax
mov rdi, r15
mov rsi, .mp3
call string$indexof
cmp rax, 0
jl .multipart_notquoted
mov rdx, [rsp]
mov esi, '"'
mov rdi, r15
add rdx, 11
call string$indexof_charcode_ofs
mov rdi, r15
mov rsi, [rsp]
mov rdx, rax
add rsi, 10
call string$substring
mov [rsp+8], rax
; set the mimelike$boundary
mov rdi, r15
xor esi, esi
mov rdx, [rsp]
call string$substring
mov [rsp+16], rax
mov rdi, rbx
mov rsi, [rsp+8] ; setboundary assumes ownership of this
mov rdx, rax
call mimelike$setboundary
mov rdi, [rsp+16]
call heap$free
; \r\n--boundary\r\n, \n--boundary\n, \r\n--boundary--\r\n, \n--boundary--\n
; [rsp+8] is our boundary
; [rsp] is the indexof substring that we are done with now
jmp .multipart_boundarycheck
cleartext .crlfdashdash, 13,10,'--'
cleartext .crlf, 13,10
cleartext .lfdashdash, 10,'--'
cleartext .lf, 10
cleartext .dashdashcrlf, '--',13,10
cleartext .dashdashlf, '--',10
calign
.multipart_notquoted:
mov rsi, [rsp]
mov rdi, r15
mov rdx, -1
add rsi, 9
call string$substr ; boundary substring
mov [rsp+8], rax
; set the mimelike boundary
mov rdi, r15
xor esi, esi
mov rdx, [rsp]
call string$substring ; content type substring
mov [rsp+16], rax
mov rdi, rbx
mov rsi, [rsp+8] ; setboundary assumes ownership of this
mov rdx, rax
call mimelike$setboundary
mov rdi, [rsp+16]
call heap$free
calign
.multipart_boundarycheck:
; to be tolerant, e.g. cutting/pasting a mime encoded session, we may not necessarily
; have CRLF delimiters, despite the spec demanding it does... so if we can't find a CRLF
; we check for LF only based ones too
mov rdi, .crlfdashdash
mov rsi, [rsp+8]
call string$concat
mov r15, rax
mov rdi, rax
mov rsi, .crlf
call string$concat
mov rdi, r15
mov r15, rax
call heap$free
; turn that into a UTF8 encoding
mov rdi, r15
call string$utf8_length
mov rdi, rax
call heap$alloc
; make sure we can find it
mov [rsp], rax
mov rdi, r15
mov rsi, rax
call string$to_utf8
mov [rsp+16], rax
; we are done with r15
mov rdi, r15
call heap$free
xor edi, edi ; starting position for search
mov rsi, [rsp] ; pattern we are searching for
mov rdx, [rsp+16] ; pattern length
call .byte_indexof
; so at this point, [rsp] == our utf8 CRLF--boundaryCRLF
; [rsp+8] is the actual boundary
; [rsp+16] is the length of [rsp]
mov [rsp+24], rax ; save the starting marker
cmp rax, -1
je .multipart_boundary_checklf
; otherwise, it was valid, so create our ending boundary and its utf8 form as well
mov rdi, .crlfdashdash
mov rsi, [rsp+8]
call string$concat
mov r15, rax
mov rdi, rax
mov rsi, .dashdashcrlf
call string$concat
mov rdi, r15
mov r15, rax
call heap$free
; turn that into a UTF8 encoding
mov rdi, r15
call string$utf8_length
mov rdi, rax
call heap$alloc
mov [rsp+32], rax
mov rdi, r15
mov rsi, rax
call string$to_utf8
mov [rsp+40], rax
; we are done with r15
mov rdi, r15
call heap$free
jmp .multipart_boundary_doit
calign
.multipart_boundary_checklf:
; free our UTF8 buffer, and do it again with lf instead of crlf
mov rdi, [rsp]
call heap$free
mov rdi, .lfdashdash
mov rsi, [rsp+8]
call string$concat
mov r15, rax
mov rdi, rax
mov rsi, .lf
call string$concat
mov rdi, r15
mov r15, rax
call heap$free
; turn that into a UTF8 encoding
mov rdi, r15
call string$utf8_length
mov rdi, rax
call heap$alloc
; make sure we can find it
mov [rsp], rax
mov rdi, r15
mov rsi, rax
call string$to_utf8
mov [rsp+16], rax
; we are done with r15
mov rdi, r15
call heap$free
xor edi, edi
mov rsi, [rsp] ; pattern we are searching for
mov rdx, [rsp+16] ; pattern length
call .byte_indexof
; so at this point, [rsp] == our utf8 CRLF--boundaryCRLF
; [rsp+8] is the actual boundary
; [rsp+16] is the length of [rsp]
cmp rax, -1
je .multipart_boundary_error
mov [rsp+24], rax ; save the starting marker
; otherwise, we need the same again for the ending boundary
mov rdi, .lfdashdash
mov rsi, [rsp+8]
call string$concat
mov r15, rax
mov rdi, rax
mov rsi, .dashdashlf
call string$concat
mov rdi, r15
mov r15, rax
call heap$free
; turn that into a UTF8 encoding
mov rdi, r15
call string$utf8_length
mov rdi, rax
call heap$alloc
mov [rsp+32], rax
mov rdi, r15
mov rsi, rax
call string$to_utf8
mov [rsp+40], rax
; we are done with r15
mov rdi, r15
call heap$free
calign
.multipart_boundary_doit:
; so now:
; [rsp] is our utf non-last boundary
; [rsp+8] is the actual boundary string
; [rsp+16] is the length of the non-last boundary
; [rsp+24] is the starting marker position
; [rsp+32] is the utf8 of the last boundary
; [rsp+40] is the length of the last boundary
; so we may have a preamble if the starting marker is >0, in which case
; we need to set our body to it
cmp qword [rsp+24], 0
je .multipart_boundary_nopreamble
; we have to undo the temporary step backward for the prefix start
mov eax, 1
mov ecx, 2
cmp word [r12], 0xa0d
cmove eax, ecx
; hmmm, that may not always work exactly right... TODO: come back and redo this by storing the value previously used
mov rdi, rbx
mov rsi, r12
add rsi, rax
mov rdx, [rsp+24]
sub rdx, rax
call mimelike$setbody_external
calign
.multipart_boundary_nopreamble:
mov rdi, [rsp+16]
add rdi, [rsp+24]
mov rsi, [rsp]
mov rdx, [rsp+16]
call .byte_indexof
cmp rax, -1
je .multipart_boundary_checklast
calign
.multipart_boundary_addpart:
mov [rsp+48], rax ; save the ending marker
mov rdi, r12
add rdi, [rsp+24]
add rdi, [rsp+16]
mov rsi, r12
add rsi, rax
xor edx, edx
call .parsemimepart
mov rcx, [rsp+48]
mov [rsp+24], rcx ; starting marker == ending marker for next round
test rax, rax
jz .multipart_boundary_nopreamble
mov rdi, [rbx+mimelike_parts_ofs]
mov rsi, rax
mov [rax+mimelike_parent_ofs], rbx
call list$push_back
jmp .multipart_boundary_nopreamble
calign
.multipart_boundary_checklast:
mov rdi, [rsp+16]
add rdi, [rsp+24]
mov rsi, [rsp+32]
mov rdx, [rsp+40]
call .byte_indexof
cmp rax, -1
jne .multipart_boundary_addpart
calign
.multipart_boundary_doreturn:
mov rdi, [rsp]
call heap$free
; double-free fixup: 20160808: mimelike$setboundary assumed ownership of this
; mov rdi, [rsp+8]
; call heap$free
; end fixup
mov rdi, [rsp+32]
call heap$free
jmp .normal_return
calign
.multipart_boundary_error:
; free our UTF8 first
mov rdi, [rsp]
call heap$free
; double-free fixup: 20160808: mimelike$setboundary assumed ownership of this
; free the boundary string itself
; mov rdi, [rsp+8]
; call heap$free
; end fixup
; destroy our mimelike object? if we didn't find a boundary
; this mime object is invalid, so hmmm, we could be more
; tolerant, and return it as-is without further parsing I spose
; hmm, yeah, that is what we'll do for now
jmp .normal_return
calign
.notmultipart:
; some interesting error condition possibilities here that we need to sort:
; if our transfer encoding is chunked, and setbody returns us with 0, fail
; if transfer encoding != chunked, then check for content-length, and if
; content length is set, and we don't have enough data, fail
mov rdi, rbx
mov rsi, mimelike$contentlength
call mimelike$getheader
test rax, rax
jz .notmultipart_nocl
mov rdi, rax
call string$to_unsigned
mov rdx, r13
sub rdx, r12
cmp rax, rdx
ja .headers_error
add [rbx+mimelike_parselen_ofs], rax
mov rdi, rbx
mov rsi, r12
mov rdx, rax
call mimelike$setbody_external
test eax, eax
jz .headers_error
jmp .normal_return
calign
.notmultipart_nocl:
mov rdi, rbx
mov rsi, r12
mov rdx, r13
sub rdx, r12
call mimelike$setbody_external
test eax, eax
jz .headers_error
calign
.headers_complete_doreturn:
.normal_return:
add rsp, 64
mov rax, rbx
pop r15 r14 r13 r12 rbx
ret
calign
.headers_error:
mov rdi, rbx
call mimelike$destroy
add rsp, 64
xor eax, eax
pop r15 r14 r13 r12 rbx
ret
falign
.byte_indexof:
; r12 is our input
; r13 is our input end
; rdi = starting position for our search
; rsi == bytes for search pattern
; rdx == length of search pattern
mov r8, r13
sub r8, r12
sub r8, rdx
add r8, r12 ; ending position
add rdi, r12 ; starting position
cmp rdi, r8
ja .byte_indexof_negone
calign
.byte_indexof_outer:
xor ecx, ecx
mov r9, rdx
calign
.byte_indexof_inner:
movzx eax, byte [rdi+rcx]
cmp al, byte [rsi+rcx]
jne .byte_indexof_next
add rcx, 1
sub r9, 1
jnz .byte_indexof_inner
mov rax, rdi
sub rax, r12
ret
calign
.byte_indexof_next:
add rdi, 1
cmp rdi, r8
jbe .byte_indexof_outer
calign
.byte_indexof_negone:
mov rax, -1
ret
end if