123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340(*----------------------------------------------------------------------------
* Copyright (c) 2019 António Nuno 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 copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 COPYRIGHT HOLDER 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.
*---------------------------------------------------------------------------*)openTypesmoduleHeaderFieldsTbl=Hashtbl.Make(structtypet=stringletequal=String.equallethashs=Hashtbl.hashsend)moduleValueMap=Map.Make(String)typet={table:Dynamic_table.t(* We maintain a lookup table of header fields to their indexes in the
* dynamic table. The format is name -> (value -> index) *);lookup_table:intValueMap.tHeaderFieldsTbl.t;mutablenext_seq:int}moduleBinaryFormat=struct(* From RFC7541§6.2.3. Literal Header Field Never Indexed
* A literal header field never-indexed representation starts with the
* '0001' 4-bit pattern. *)letnever_indexed=0b0001_0000,4(* From RFC7541§6.2.2: Literal Header Field without Indexing
* A literal header field without indexing representation starts with the
* '0000' 4-bit pattern. *)letwithout_indexing=0b0000_0000,4(* From RFC7541§6.2.1: Literal Header Field with Incremental Indexing
* A literal header field with incremental indexing representation starts
* with the '01' 2-bit pattern. *)letincremental_indexing=0b0100_0000,6(* From RFC7541§6.1: Indexed Header Field Representation
* An indexed header field starts with the '1' 1-bit pattern, followed by
* the index of the matching header field, represented as an integer with
* a 7-bit prefix (see Section 5.1). *)letindexed=0b1000_0000,7let[@inline]is_indexed=function128->true|_->falseendletcreate=leton_evictlookup_table(name,value)=letmap=HeaderFieldsTbl.findlookup_tablenameinifValueMap.cardinalmap=1thenHeaderFieldsTbl.removelookup_tablenameelseletmap=ValueMap.removevaluemapinHeaderFieldsTbl.replacelookup_tablenamemapinfuncapacity->letlookup_table=HeaderFieldsTbl.createcapacityin{table=Dynamic_table.create~on_evict:(on_evictlookup_table)capacity;lookup_table;next_seq=0}letadd({table;lookup_table;next_seq}asencoder)entry=letname,value=entryinDynamic_table.addtableentry;letmap=matchHeaderFieldsTbl.find_optlookup_tablenamewith|Somemap->ValueMap.addvaluenext_seqmap|None->ValueMap.singletonvaluenext_seqinencoder.next_seq<-next_seq+1;HeaderFieldsTbl.replacelookup_tablenamemapletencode=letfind_token=letencode_missing_valueencoderwithout_indexingtokennamevalue=(* This is a header field whose value we didn't find in the static
* table after looping. We ended up here (name <> name') because we
* looped to check whether the value was indexed in the static table.
* We can still use the token index to encode the header name. *)letindex=token+1inifwithout_indexingthen(* From RFC7541§6.2.2: Literal Header Field without Indexing
* If the header field name matches the header field name of an entry
* stored in the static table or the dynamic table, the header field
* name can be represented using the index of that entry. *)BinaryFormat.without_indexing,indexelse((* From RFC7541§6.2.1: Literal Header Field with Incremental Indexing
* A literal header field with incremental indexing representation
* results in appending a header field to the decoded header list and
* inserting it as a new entry into the dynamic table. *)addencoder(name,value);BinaryFormat.incremental_indexing,index)infunencoderwithout_indexingtokennamevalue->letrecloopi=ifi>=Static_table.table_sizethenencode_missing_valueencoderwithout_indexingtokennamevalueelseletname',value'=Static_table.table.(i)inifname=name'thenifvalue'=valuethen(* From RFC7541§6.1: Indexed Header Field Representation
* An indexed header field starts with the '1' 1-bit pattern,
* followed by the index of the matching header field. *)BinaryFormat.indexed,i+1else(* Advance one token in the static table, as the next entry might have
* a value that can fall into the above branch. We're guaranteed to
* always get the first token (index) in the static table for `name`,
* because that's what `Static_table.lookup_token` returns. *)loop(i+1)elseencode_missing_valueencoderwithout_indexingtokennamevalueinlooptokeninlet[@inline]seq_to_indexnext_seqseq=Static_table.table_size+next_seq-seqinletis_without_indexing=letmoduleIntSet=Set.Make(Int)inlettokens_without_indexing=(* From RFC7541§6.2.2: Never-Indexed Literals
* Either form of header field name representation is followed by the
* header field value represented as a string literal (see Section 5.2).
*
* Note: we choose not to index the values of these fields as they would
* vary immensely. This way, we save some additions / evictions from the
* dynamic table. *)IntSet.of_listStatic_table.TokenIndices.[path;age;content_length;etag;if_modified_since;if_none_match;location;set_cookie]infun[@inline]token->token<>-1&&IntSet.memtokentokens_without_indexinginlet[@inline]is_sensitivetokenvalue=token<>-1&&(* From RFC7541§7.1.3: Never-Indexed Literals
* An encoder might also choose not to index values for header fields
* that are considered to be highly valuable or sensitive to recovery,
* such as the Cookie or Authorization header fields. *)Static_table.TokenIndices.(token==authorization||(token==cookie&&String.lengthvalue<20))infun({lookup_table;next_seq;_}asencoder){name;value;sensitive}->lettoken=Static_table.lookup_token_indexnameinlettoken_found_in_static_table=token<>-1inifsensitive||is_sensitivetokenvaluethen(* never indexed literal header field, find the index *)letindex=iftoken_found_in_static_tablethen(* From RFC7541§6.2.2: Literal Header Field without Indexing
* If the header field name matches the header field name of an entry
* stored in the static table or the dynamic table, the header field
* name can be represented using the index of that entry. *)token+1elsematchHeaderFieldsTbl.find_optlookup_tablenamewith|Somemap->let_,any_entry=ValueMap.choosemapinseq_to_indexnext_seqany_entry|None->(* From RFC7541§6.2.2: Literal Header Field without Indexing
* Otherwise, the header field name is represented as a string
* literal (see Section 5.2). A value 0 is used in place of the
* 4-bit index, followed by the header field name. *)0inBinaryFormat.never_indexed,indexelseiftoken_found_in_static_tablethen(* Header name is represented in the static table. *)matchHeaderFieldsTbl.find_optlookup_tablenamewith|Somemap->(* Header value is indexed in the dynamic table. *)(matchValueMap.find_optvaluemapwith|Someseq->(* From RFC7541§6.1: Indexed Header Field Representation
* An indexed header field representation identifies an entry in
* either the static table or the dynamic table (see Section 2.3). *)BinaryFormat.indexed,seq_to_indexnext_seqseq|None->(* Header value is not indexed in the dynamic table. Check if it's an
* entry in the static table or if we need to encode its value, (and
* potentially name if the field is requested to be encoded without
* indexing). *)letwithout_indexing=is_without_indexingtokeninfind_tokenencoderwithout_indexingtokennamevalue)|None->letwithout_indexing=is_without_indexingtokeninfind_tokenencoderwithout_indexingtokennamevalueelsematchHeaderFieldsTbl.find_optlookup_tablenamewith|Somemap->(matchValueMap.find_optvaluemapwith|Someseq->BinaryFormat.indexed,seq_to_indexnext_seqseq|None->letindex=seq_to_indexnext_seq(snd(ValueMap.choosemap))inifis_without_indexingtokenthenBinaryFormat.without_indexing,indexelse((* From RFC7541§6.2.1
* A literal header field with incremental indexing representation
* results in appending a header field to the decoded header list
* and inserting it as a new entry into the dynamic table. *)addencoder(name,value);BinaryFormat.incremental_indexing,index))|None->ifis_without_indexingtokenthenBinaryFormat.without_indexing,0else((* From RFC7541§6.2.1
* A literal header field with incremental indexing representation
* results in appending a header field to the decoded header list and
* inserting it as a new entry into the dynamic table. *)addencoder(name,value);BinaryFormat.incremental_indexing,0)letencode_inttprefixni=letmax_prefix=(1lsln)-1inifi<max_prefixthen(* From RFC7541§5.1:
* If the integer value is small enough, i.e., strictly less than 2^N-1,
* it is encoded within the N-bit prefix. *)Faraday.write_uint8t(prefixlori)else(* From RFC7541§5.1:
* Otherwise, all the bits of the prefix are set to 1, and the value,
* decreased by 2^N-1, is encoded using a list of one or more octets. The
* most significant bit of each octet is used as a continuation flag: its
* value is set to 1 except for the last octet in the list. The remaining
* bits of the octets are used to encode the decreased value. *)leti=i-max_prefixinFaraday.write_uint8t(prefixlormax_prefix);letrecloopi=ifi>=128then(Faraday.write_uint8t(iland127lor128);loop(ilsr7))elseFaraday.write_uint8tiinloopiletencode_header=letencode_stringts=letstring_length=String.lengthsinlethuffman_length=Huffman.encoded_lengthsinifhuffman_length>string_lengththen((* From RFC7541§5.2:
* The number of octets used to encode the string literal, encoded as an
* integer with a 7-bit prefix (see Section 5.1). *)encode_intt07string_length;(* From RFC7541§5.2:
* The encoded data of the string literal. If H is '0', then the encoded
* data is the raw octets of the string literal. If H is '1', then the
* encoded data is the Huffman encoding of the string literal. *)Faraday.write_stringts)else((* From RFC7541§5.2:
* The number of octets used to encode the string literal, encoded as an
* integer with a 7-bit prefix (see Section 5.1). *)encode_intt1287huffman_length;(* From RFC7541§5.2:
* The encoded data of the string literal. If H is '0', then the encoded
* data is the raw octets of the string literal. If H is '1', then the
* encoded data is the Huffman encoding of the string literal. *)Huffman.encodets)infunencodert({name;value;_}asheader)->let(prefix,prefix_length),index=encodeencoderheaderinencode_inttprefixprefix_lengthindex;matchBinaryFormat.is_indexedprefixwith|true->()|false->ifindex==0then(* From RFC7541§6.2.2: Literal Header Field without Indexing * If the
header field name matches the header field name of an entry * stored
in the static table or the dynamic table, the header field * name can
be represented using the index of that entry. In this case, * the
index of the entry is represented as an integer with a 4-bit * prefix
(see Section 5.1). This value is always non-zero. * * Otherwise, the
header field name is represented as a string literal * (see Section
5.2). A value 0 is used in place of the 4-bit index, * followed by
the header field name. *)encode_stringtname;(* From RFC7541§6.2.2: Literal Header Field without Indexing
* Either form of header field name representation is followed by the
* header field value represented as a string literal (see
* Section 5.2). *)encode_stringtvalueletset_capacity{table;_}new_capacity=Dynamic_table.set_capacitytablenew_capacity