123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358(*----------------------------------------------------------------------------
* Copyright (c) 2017 Inhabited Type LLC.
* Copyright (c) 2019 Antonio N. Monteiro.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the author nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*---------------------------------------------------------------------------*)typename=stringtypevalue=stringtypeheader=Hpack.header={name:name;value:value;sensitive:bool}typet=headerlistletempty:t=[]letof_rev_lisths=List.map(fun(name,value)->{name;value;sensitive=false})hsletof_listt=of_rev_list(List.revt)letto_rev_listt=List.map(fun{name;value;_}->name,value)tletto_listt=List.rev(to_rev_listt)letto_hpack_listt=List.revtexceptionLocalmoduleCI=structletchar_is_upperc=c>=0x41&&c<=0x5aletlowerc=ifchar_is_uppercthenc+32elsecletequalxy=letlen=String.lengthxinlen=String.lengthy&&matchfori=0tolen-1doletc1=Char.code(String.unsafe_getxi)inletc2=Char.code(String.unsafe_getyi)inifc1=c2then()elseiflowerc1<>lowerc2thenraiseLocaldonewith|()->true|exceptionLocal->falseletis_lowercasex=letlen=String.lengthxinmatchfori=0tolen-1doletc1=Char.code(String.unsafe_getxi)inifchar_is_upperc1thenraiseLocalelse()donewith|()->true|exceptionLocal->falseendletrecmemtname=matchtwith|{name=name';_}::t'->CI.equalnamename'||memt'name|_->false(* TODO: do we need to keep a list of never indexed fields? *)letaddt?(sensitive=false)namevalue={name;value;sensitive}::tletadd_listtls=of_rev_listls@t(* XXX(seliopou): do better here *)letadd_multi=letrecloop_outertlss=matchlsswith[]->t|(n,vs)::lss'->loop_innertnvslss'andloop_innertnvslss=matchvswith|[]->loop_outertlss|v::vs'->loop_inner({name=n;value=v;sensitive=false}::t)nvs'lssinloop_outerletadd_unless_existst?(sensitive=false)namevalue=ifmemtnamethentelse{name;value;sensitive}::tletreplacet?(sensitive=false)namevalue=letreclooptnnvseen=matchtwith|[]->ifnotseenthenraiseLocalelse[]|({name=n';_}asnv')::t->ifCI.equalnn'thenifseenthenlooptnnvtrueelsenv::looptnnvtrueelsenv'::looptnnvseenintrylooptname{name;value;sensitive}falsewithLocal->tletremovetname=letrecloopsnseen=matchswith|[]->ifnotseenthenraiseLocalelse[]|({name=n';_}asnv')::s'->ifCI.equalnn'thenloops'ntrueelsenv'::loops'nseenintrylooptnamefalsewithLocal->tletgettname=letreclooptn=matchtwith|[]->None|{name=n';value;_}::t'->ifCI.equalnn'thenSomevalueelseloopt'ninlooptnameletget_exntname=letrecloopt=matchtwith|[]->failwith(Printf.sprintf"Headers.get_exn: %S not found"name)|{name=n;value;_}::t'->ifCI.equalnamenthenvalueelseloopt'inlooptletget_pseudotname=gett(":"^name)letget_pseudo_exntname=get_exnt(":"^name)letget_multitname=letreclooptacc=matchtwith|[]->acc|{name=n;value;_}::t'->ifCI.equalnamenthenloopt'(value::acc)elseloopt'accinloopt[]letget_multi_pseudotname=get_multit(":"^name)modulePseudo=structletreserved_request=[":method";":scheme";":authority";":path"]letreserved_response=[":status"](* 0x3A is the char code for `:` *)letis_pseudoname=Char.code(String.unsafe_getname0)==0x3Aendletiter~ft=List.iter(fun{name;value;_}->fnamevalue)tletfold~f~initt=List.fold_left(funacc{name;value;_}->fnamevalueacc)inittletexists~ft=List.exists(fun{name;value;_}->fnamevalue)tletto_stringt=letb=Buffer.create128inList.iter(fun(name,value)->Buffer.add_stringbname;Buffer.add_stringb": ";Buffer.add_stringbvalue;Buffer.add_stringb"\r\n")(to_listt);Buffer.add_stringb"\r\n";Buffer.contentsbletvalid_headers?(is_request=true)t=matchgett"connection",gett"TE"with|Some_,_->(* From RFC7540§8.1.2.2:
* HTTP/2 does not use the Connection header field to indicate
* connection-specific header fields; in this protocol,
* connection-specific metadata is conveyed by other means. An endpoint
* MUST NOT generate an HTTP/2 message containing connection-specific
* header fields; any message containing connection-specific header
* fields MUST be treated as malformed (Section 8.1.2.6). *)false|_,Somevaluewhenvalue<>"trailers"->(* From RFC7540§8.1.2.2:
* The only exception to this is the TE header field, which MAY be
* present in an HTTP/2 request; when it is, it MUST NOT contain any
* value other than "trailers". *)false|_->letpseudo_ended=reffalseinletinvalid=exists~f:(funname_->letis_pseudo=Pseudo.is_pseudonameinletpseudo_did_end=!pseudo_endedinif(notis_pseudo)&¬pseudo_did_endthenpseudo_ended:=true;(* From RFC7540§8.1.2:
* [...] header field names MUST be converted to lowercase
* prior to their encoding in HTTP/2. A request or response
* containing uppercase header field names MUST be treated as
* malformed (Section 8.1.2.6). *)(notCI.(is_lowercasename))(* From RFC7540§8.1.2.1:
* Pseudo-header fields are only valid in the context in
* which they are defined. [...] pseudo-header fields defined
* for responses MUST NOT appear in requests. [...] Endpoints
* MUST treat a request or response that contains undefined
* or invalid pseudo-header fields as malformed (Section
* 8.1.2.6). *)||(is_pseudo&¬(List.memname(ifis_requestthenPseudo.reserved_requestelsePseudo.reserved_response)))||(* From RFC7540§8.1.2.1:
* All pseudo-header fields MUST appear in the header block
* before regular header fields. Any request or response that
* contains a pseudo-header field that appears in a header block
* after a regular header field MUST be treated as malformed
* (Section 8.1.2.6). *)(is_pseudo&&pseudo_did_end))(to_hpack_listt)innotinvalidletvalid_request_headerst=valid_headerstletvalid_response_headerst=valid_headers~is_request:falsetletmethod_path_and_scheme_or_malformedt=match(get_multi_pseudot"method",get_multi_pseudot"scheme",get_multi_pseudot"path")with|_,[("http"|"https")],[path]whenString.lengthpath==0->(* From RFC7540§8.1.2.6:
* This pseudo-header field MUST NOT be empty for http or https URIs;
* http or https URIs that do not contain a path component MUST include a
* value of '/'. *)`Malformed(* From RFC7540§8.1.2.3:
* All HTTP/2 requests MUST include exactly one valid value for the
* :method, :scheme, and :path pseudo-header fields, unless it is a
* CONNECT request (Section 8.3). *)|[("CONNECT"asmeth)],[],[]->(* From RFC7540§8.3:
* The HTTP header field mapping works as defined in Section 8.1.2.3
* ("Request Pseudo-Header Fields"), with a few differences.
* Specifically:
*
* - The :method pseudo-header field is set to CONNECT.
* - The :scheme and :path pseudo-header fields MUST be omitted.
* - The :authority pseudo-header field contains the host and port to
* connect to (equivalent to the authority-form of the request-target
* of CONNECT requests (see [RFC7230], Section 5.3)).
*
* A CONNECT request that does not conform to these restrictions is
* malformed (Section 8.1.2.6). *)ifmemt":authority"then`Valid(meth,"","")else`Malformed|["CONNECT"],_,_->`Malformed|[meth],[scheme],[path]->ifvalid_request_headerstthen`Valid(meth,path,scheme)else`Malformed|_->`Malformedlettrailers_validt=letinvalid=exists~f:(funname_->(* From RFC7540§8.1.2:
* [...] header field names MUST be converted to lowercase prior to
* their encoding in HTTP/2. A request or response containing
* uppercase header field names MUST be treated as malformed
* (Section 8.1.2.6). *)(not(CI.is_lowercasename))||(* From RFC7540§8.1.2.1:
* Pseudo-header fields MUST NOT appear in trailers. Endpoints
* MUST treat a request or response that contains undefined or
* invalid pseudo-header fields as malformed (Section 8.1.2.6). *)Pseudo.is_pseudoname)tinnotinvalidletpp_humfmtt=letpp_elemfmt(name,value)=Format.fprintffmt"@[(%S %S)@]"namevalueinFormat.fprintffmt"@[(";Format.pp_print_listpp_elemfmt(to_listt);Format.fprintffmt")@]"