Commits

Anonymous committed bcb8e81

Add support for chunked encoding

Comments (0)

Files changed (1)

src/lhttpc_client.erl

     case lhttpc_lib:header_value("content-length", Hdrs) of
         undefined ->
             case lhttpc_lib:header_value("transfer-encoding", Hdrs) of
-                "chunked" -> read_chunked_body(Socket, Ssl);
+                "chunked" -> read_chunked_body(Socket, Ssl, Hdrs, []);
                 undefined -> read_infinite_body(Socket, Vsn, Hdrs, Ssl)
             end;
         ContentLength ->
             throw(Reason)
     end.
 
-read_chunked_body(_, _) ->
-    % TODO: Implement chunked reading
-    erlang:error(not_imlemented).
+read_chunked_body(Socket, Ssl, Hdrs, Chunks) ->
+    lhttpc_sock:setopts(Socket, [{packet, line}], Ssl),
+    case lhttpc_sock:recv(Socket, Ssl) of
+        {ok, ChunkSizeExt} ->
+            case chunk_size(ChunkSizeExt) of
+                0 ->
+                    Body = list_to_binary(lists:reverse(Chunks)),
+                    {Body, read_trailers(Socket, Ssl, Hdrs, <<>>), Socket};
+                Size ->
+                    Chunk = read_chunk(Socket, Ssl, Size),
+                    read_chunked_body(Socket, Ssl, Hdrs, [Chunk | Chunks])
+            end;
+        {error, Reason} ->
+            throw(Reason)
+    end.
+
+chunk_size(Bin) ->
+    erlang:list_to_integer(lists:reverse(chunk_size(Bin, [])), 16).
+
+chunk_size(<<$;, _/binary>>, Chars) ->
+    Chars;
+chunk_size(<<"\r\n", _/binary>>, Chars) ->
+    Chars;
+chunk_size(<<Char, Binary/binary>>, Chars) ->
+    chunk_size(Binary, [Char | Chars]).
+
+read_chunk(Socket, Ssl, Size) ->
+    lhttpc_sock:setopts(Socket, [{packet, raw}], Ssl),
+    case lhttpc_sock:recv(Socket, Size + 2, Ssl) of
+        {ok, <<Chunk:Size/binary, "\r\n">>} ->
+            Chunk;
+        {ok, Data} ->
+            erlang:error({invalid_chunk, Data});
+        {error, Reason} ->
+            throw(Reason)
+    end.
+
+%% XXX: This seems very much too complicated, right?
+%% Oh well, it seems there is a problem with erlang:decode_packet/3, which I
+%% haven't had time to look at:
+%% erlang:decode_packet(httph,<<"Trailer-1: 1\r\n">>,[]) ->
+%%     {more,undefined}
+%% erlang:decode_packet(httph,<<"Trailer-1: 1\r\nX">>,[]) ->
+%%     {ok,{http_header,0,"Trailer-1",undefined,"1"},<<"X">>}
+%% Luckily this works:
+%% erlang:decode_packet(httph,<<"\r\n">>,[]) ->
+%%     {ok, http_eoh}
+read_trailers(Socket, Ssl, Hdrs, <<>>) ->
+    case lhttpc_sock:recv(Socket, Ssl) of
+        {ok, Line} ->
+            case erlang:decode_packet(httph, Line, []) of
+                {ok, {http_header, _, Name, _, Value}, Rest} ->
+                    Header = {lhttpc_lib:maybe_atom_to_list(Name), Value},
+                    read_trailers(Socket, Ssl, [Header | Hdrs], Rest);
+                {ok, http_eoh, _} ->
+                    Hdrs;
+                {more, _} ->
+                    read_trailers(Socket, Ssl, Hdrs, Line)
+            end;
+        {error, Reason} ->
+            throw(Reason)
+    end;
+read_trailers(Socket, Ssl, Hdrs, Acc) ->
+    case erlang:decode_packet(httph, Acc, []) of
+        {ok, {http_header, _, Name, _, Value}, Rest} ->
+            Header = {lhttpc_lib:maybe_atom_to_list(Name), Value},
+            read_trailers(Socket, Ssl, [Header | Hdrs], Rest);
+        {ok, http_eoh, _} ->
+            Hdrs;
+        {more, _} ->
+            case lhttpc_sock:recv(Socket, Ssl) of
+                {ok, Line} ->
+                    NewAcc = <<Acc/binary, Line/binary>>,
+                    read_trailers(Socket, Ssl, Hdrs, NewAcc);
+                {error, Reason} ->
+                    throw(Reason)
+            end
+    end.
 
 read_infinite_body(Socket, {1, 1}, Hdrs, Ssl) ->
     case lhttpc_lib:header_value("connection", Hdrs) of