diff options
author | Viktor Söderqvist <[email protected]> | 2020-09-16 00:23:05 +0200 |
---|---|---|
committer | Loïc Hoguin <[email protected]> | 2020-10-07 13:33:37 +0200 |
commit | bc3f1cf9afbd5c92de5f7347afc64fc42ae035d3 (patch) | |
tree | b3045df5a1cd51e8bd5221508ccad2caea330afc /src | |
parent | bb26112da43feef7fcd34a1647b86aa15e9bbeee (diff) | |
download | cowlib-bc3f1cf9afbd5c92de5f7347afc64fc42ae035d3.tar.gz cowlib-bc3f1cf9afbd5c92de5f7347afc64fc42ae035d3.tar.bz2 cowlib-bc3f1cf9afbd5c92de5f7347afc64fc42ae035d3.zip |
Make HPACK encode respect new configured max size
Correction to cow_hpack:encode/2,3 according to RFC 7541, 4.3. Entry
Eviction When Dynamic Table Size Changes.
This change also corrects the handling of inserting entries larger than
the max size, which shall result in an empty table, according to 4.4.
in the same RFC.
Fixes #101, #103.
Diffstat (limited to 'src')
-rw-r--r-- | src/cow_hpack.erl | 65 |
1 files changed, 56 insertions, 9 deletions
diff --git a/src/cow_hpack.erl b/src/cow_hpack.erl index 3089d16..0ea4bf8 100644 --- a/src/cow_hpack.erl +++ b/src/cow_hpack.erl @@ -526,14 +526,16 @@ encode(Headers) -> encode(Headers, State=#state{max_size=MaxSize, configured_max_size=MaxSize}) -> encode(Headers, State, huffman, []); encode(Headers, State0=#state{configured_max_size=MaxSize}) -> - {Data, State} = encode(Headers, State0#state{max_size=MaxSize}, huffman, []), + State1 = table_update_size(MaxSize, State0), + {Data, State} = encode(Headers, State1, huffman, []), {[enc_int5(MaxSize, 2#001)|Data], State}. -spec encode(cow_http:headers(), State, opts()) -> {iodata(), State} when State::state(). encode(Headers, State=#state{max_size=MaxSize, configured_max_size=MaxSize}, Opts) -> encode(Headers, State, huffman_opt(Opts), []); encode(Headers, State0=#state{configured_max_size=MaxSize}, Opts) -> - {Data, State} = encode(Headers, State0#state{max_size=MaxSize}, huffman_opt(Opts), []), + State1 = table_update_size(MaxSize, State0), + {Data, State} = encode(Headers, State1, huffman_opt(Opts), []), {[enc_int5(MaxSize, 2#001)|Data], State}. huffman_opt(#{huffman := false}) -> no_huffman; @@ -1016,6 +1018,43 @@ table_update_encode_test() -> {42,{<<":status">>, <<"302">>}}]} = EncState3, ok. +%% Check that encode/2 is using the new table size after calling +%% set_max_size/1 and that adding entries larger than the max size +%% results in an empty table. +table_update_encode_max_size_0_test() -> + %% Encoding starts with default max size + EncState0 = init(), + %% Decoding starts with max size of 0 + DecState0 = init(0), + %% First request. + Headers1 = [ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"http">>}, + {<<":path">>, <<"/">>}, + {<<":authority">>, <<"www.example.com">>} + ], + {Encoded1, EncState1} = encode(Headers1, EncState0), + {Headers1, DecState1} = decode(iolist_to_binary(Encoded1), DecState0), + #state{size=57, dyn_table=[{57,{<<":authority">>, <<"www.example.com">>}}]} = EncState1, + #state{size=0, dyn_table=[]} = DecState1, + %% Settings received after the first request. + EncState2 = set_max_size(0, EncState1), + #state{configured_max_size=0, max_size=4096, + size=57, dyn_table=[{57,{<<":authority">>, <<"www.example.com">>}}]} = EncState2, + %% Second request. + Headers2 = [ + {<<":method">>, <<"GET">>}, + {<<":scheme">>, <<"http">>}, + {<<":path">>, <<"/">>}, + {<<":authority">>, <<"www.example.com">>}, + {<<"cache-control">>, <<"no-cache">>} + ], + {Encoded2, EncState3} = encode(Headers2, EncState2), + {Headers2, DecState2} = decode(iolist_to_binary(Encoded2), DecState1), + #state{configured_max_size=0, max_size=0, size=0, dyn_table=[]} = EncState3, + #state{size=0, dyn_table=[]} = DecState2, + ok. + encode_iolist_test() -> Headers = [ {<<":method">>, <<"GET">>}, @@ -1365,13 +1404,21 @@ table_get_name(Index, #state{dyn_table=DynamicTable}) -> table_insert(Entry = {Name, Value}, State=#state{size=Size, max_size=MaxSize, dyn_table=DynamicTable}) -> EntrySize = byte_size(Name) + byte_size(Value) + 32, - {DynamicTable2, Size2} = if - Size + EntrySize > MaxSize -> - table_resize(DynamicTable, MaxSize - EntrySize, 0, []); - true -> - {DynamicTable, Size} - end, - State#state{size=Size2 + EntrySize, dyn_table=[{EntrySize, Entry}|DynamicTable2]}. + if + EntrySize + Size =< MaxSize -> + %% Add entry without eviction + State#state{size=Size + EntrySize, dyn_table=[{EntrySize, Entry}|DynamicTable]}; + EntrySize =< MaxSize -> + %% Evict, then add entry + {DynamicTable2, Size2} = table_resize(DynamicTable, MaxSize - EntrySize, 0, []), + State#state{size=Size2 + EntrySize, dyn_table=[{EntrySize, Entry}|DynamicTable2]}; + EntrySize > MaxSize -> + %% "an attempt to add an entry larger than the + %% maximum size causes the table to be emptied + %% of all existing entries and results in an + %% empty table" (RFC 7541, 4.4) + State#state{size=0, dyn_table=[]} + end. table_resize([], _, Size, Acc) -> {lists:reverse(Acc), Size}; |