aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorViktor Söderqvist <[email protected]>2020-09-16 00:23:05 +0200
committerLoïc Hoguin <[email protected]>2020-10-07 13:33:37 +0200
commitbc3f1cf9afbd5c92de5f7347afc64fc42ae035d3 (patch)
treeb3045df5a1cd51e8bd5221508ccad2caea330afc /src
parentbb26112da43feef7fcd34a1647b86aa15e9bbeee (diff)
downloadcowlib-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.erl65
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};