diff --git a/lib/hpax/huffman.ex b/lib/hpax/huffman.ex index 574112f..2f9864f 100644 --- a/lib/hpax/huffman.ex +++ b/lib/hpax/huffman.ex @@ -85,6 +85,14 @@ defmodule HPAX.Huffman do end end + # Anything left over here is 8 or more bits that don't match any Huffman code (the clauses + # above only match a complete code, the empty binary, or up to 7 bits of valid EOS padding). + # This can only happen with a malformed/malicious encoding, since a real encoder never + # produces output with more than 7 trailing bits that aren't a complete code. + def decode(<<_rest::bitstring>>) do + throw({:hpax, {:protocol_error, :invalid_huffman_encoding}}) + end + ## Helpers @compile {:inline, take_significant_bits: 3} diff --git a/test/hpax/huffman_test.exs b/test/hpax/huffman_test.exs index 05cab8c..317c14e 100644 --- a/test/hpax/huffman_test.exs +++ b/test/hpax/huffman_test.exs @@ -19,4 +19,17 @@ defmodule HPAX.HuffmanTest do assert Huffman.decode(encoded) == :huffman.decode(encoded) end end + + describe "decode/1 with malformed input" do + test "raises a catchable :hpax error instead of a FunctionClauseError when the trailing bits don't correspond to any complete code" do + # 8+ bits of all 1s is a valid prefix of the 30-bit EOS code but is neither a complete + # code nor <= 7 bits of valid EOS padding, so it can't come from a real encoder + for n_bits <- [8, 9, 15, 16, 23, 29] do + invalid = for _ <- 1..n_bits, into: <<>>, do: <<1::1>> + + assert catch_throw(Huffman.decode(invalid)) == + {:hpax, {:protocol_error, :invalid_huffman_encoding}} + end + end + end end