A new user interface for you! Read more...

File 0432-inets-Fix-handling-of-Content-Length-httpc.patch of Package erlang

From bb5ba03ac8ada0b05c95c7cde295e61fe3faca5c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A9ter=20Dimitrov?= <peterdmv@erlang.org>
Date: Tue, 2 Oct 2018 16:26:59 +0200
Subject: [PATCH] inets: Fix handling of 'Content-Length' (httpc)

RFC7230 3.3.2
A sender MUST NOT send a 'Content-Length' header field in any message
that contains a 'Transfer-Encoding' header field.

Change-Id: I568fc65cafe3ab30baad56c002459ff74e4dbb28
---
 lib/inets/src/http_client/httpc_request.erl | 11 ++++-
 lib/inets/test/httpc_SUITE.erl              | 75 ++++++++++++++++++++++++-----
 2 files changed, 73 insertions(+), 13 deletions(-)

diff --git a/lib/inets/src/http_client/httpc_request.erl b/lib/inets/src/http_client/httpc_request.erl
index 9b81bd7a80..9db961cf9e 100644
--- a/lib/inets/src/http_client/httpc_request.erl
+++ b/lib/inets/src/http_client/httpc_request.erl
@@ -221,7 +221,8 @@ update_headers(Headers, ContentType, Body, []) ->
             %% that does not include a message body. This implies that either the
             %% Content-Length or the Transfer-Encoding header MUST be present.
             %% DO NOT send content-type when Body is empty.
-            Headers#http_request_h{'content-type' = ContentType};
+            Headers1 = Headers#http_request_h{'content-type' = ContentType},
+            handle_transfer_encoding(Headers1);
         _ ->
             Headers#http_request_h{
               'content-length' = body_length(Body),
@@ -230,6 +231,14 @@ update_headers(Headers, ContentType, Body, []) ->
 update_headers(_, _, _, HeadersAsIs) ->
     HeadersAsIs.
 
+handle_transfer_encoding(Headers = #http_request_h{'transfer-encoding' = undefined}) ->
+    Headers;
+handle_transfer_encoding(Headers) ->
+    %% RFC7230 3.3.2
+    %% A sender MUST NOT send a 'Content-Length' header field in any message
+    %% that contains a 'Transfer-Encoding' header field.
+    Headers#http_request_h{'content-length' = undefined}.
+
 body_length(Body) when is_binary(Body) ->
    integer_to_list(size(Body));
 
diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl
index 3d375222b5..41f8b60363 100644
--- a/lib/inets/test/httpc_SUITE.erl
+++ b/lib/inets/test/httpc_SUITE.erl
@@ -165,7 +165,8 @@ misc() ->
      server_does_not_exist,
      timeout_memory_leak,
      wait_for_whole_response,
-     post_204_chunked
+     post_204_chunked,
+     chunkify_fun
     ].
 
 %%--------------------------------------------------------------------
@@ -1285,7 +1286,8 @@ post_204_chunked(_Config) ->
 
     {ok, ListenSocket} = gen_tcp:listen(0, [{active,once}, binary]),
     {ok,{_,Port}} = inet:sockname(ListenSocket),
-    spawn(fun () -> custom_server(Msg, Chunk, ListenSocket) end),
+    spawn(fun () -> custom_server(Msg, Chunk, ListenSocket,
+                                  fun post_204_receive/0) end),
 
     {ok,Host} = inet:gethostname(),
     End = "/cgi-bin/erl/httpd_example:post_204",
@@ -1295,16 +1297,26 @@ post_204_chunked(_Config) ->
     %% Second request times out in the faulty case.
     {ok, _} = httpc:request(post, {URL, [], "text/html", []}, [], []).
 
-custom_server(Msg, Chunk, ListenSocket) ->
+post_204_receive() ->
+    receive
+        {tcp, _, Msg} ->
+            ct:log("Message received: ~p", [Msg])
+    after
+        1000 ->
+            ct:fail("Timeout: did not recive packet")
+    end.
+
+%% Custom server is used to test special cases when using chunked encoding
+custom_server(Msg, Chunk, ListenSocket, ReceiveFun) ->
     {ok, Accept} = gen_tcp:accept(ListenSocket),
-    receive_packet(),
+    ReceiveFun(),
     send_response(Msg, Chunk, Accept),
-    custom_server_loop(Msg, Chunk, Accept).
+    custom_server_loop(Msg, Chunk, Accept, ReceiveFun).
 
-custom_server_loop(Msg, Chunk, Accept) ->
-    receive_packet(),
+custom_server_loop(Msg, Chunk, Accept, ReceiveFun) ->
+    ReceiveFun(),
     send_response(Msg, Chunk, Accept),
-    custom_server_loop(Msg, Chunk, Accept).
+    custom_server_loop(Msg, Chunk, Accept, ReceiveFun).
 
 send_response(Msg, Chunk, Socket) ->
     inet:setopts(Socket, [{active, once}]),
@@ -1312,15 +1324,54 @@ send_response(Msg, Chunk, Socket) ->
     timer:sleep(250),
     gen_tcp:send(Socket, Chunk).
 
-receive_packet() ->
+%%--------------------------------------------------------------------
+chunkify_fun() ->
+    [{doc,"Test that a chunked encoded request does not include the 'Content-Length header'"}].
+chunkify_fun(_Config) ->
+    Msg = "HTTP/1.1 204 No Content\r\n" ++
+        "Date: Thu, 23 Aug 2018 13:36:29 GMT\r\n" ++
+        "Content-Type: text/html\r\n" ++
+        "Server: inets/6.5.2.3\r\n" ++
+        "Cache-Control: no-cache\r\n" ++
+        "Pragma: no-cache\r\n" ++
+        "Expires: Fri, 24 Aug 2018 07:49:35 GMT\r\n" ++
+        "Transfer-Encoding: chunked\r\n" ++
+        "\r\n",
+    Chunk = "0\r\n\r\n",
+
+    {ok, ListenSocket} = gen_tcp:listen(0, [{active,once}, binary]),
+    {ok,{_,Port}} = inet:sockname(ListenSocket),
+    spawn(fun () -> custom_server(Msg, Chunk, ListenSocket,
+                                  fun chunkify_receive/0) end),
+
+    {ok,Host} = inet:gethostname(),
+    End = "/cgi-bin/erl/httpd_example",
+    URL = ?URL_START ++ Host ++ ":" ++ integer_to_list(Port) ++ End,
+    Fun = fun(_) -> {ok,<<1>>,eof_body} end,
+    Acc = start,
+
+    {ok, {{_,204,_}, _, _}} =
+        httpc:request(put, {URL, [], "text/html", {chunkify, Fun, Acc}}, [], []).
+
+chunkify_receive() ->
+    Error = "HTTP/1.1 500 Internal Server Error\r\n" ++
+        "Content-Length: 0\r\n\r\n",
     receive
-        {tcp, _, Msg} ->
-            ct:log("Message received: ~p", [Msg])
+        {tcp, Port, Msg} ->
+            case binary:match(Msg, <<"content-length">>) of
+                nomatch ->
+                    ct:log("Message received: ~s", [binary_to_list(Msg)]);
+                {_, _} ->
+                    ct:log("Message received (negative): ~s", [binary_to_list(Msg)]),
+                    %% Signal a testcase failure when the received HTTP request
+                    %% contains a 'Content-Length' header.
+                    gen_tcp:send(Port, Error),
+                    ct:fail("Content-Length present in received headers.")
+            end
     after
         1000 ->
             ct:fail("Timeout: did not recive packet")
     end.
-
 %%--------------------------------------------------------------------
 stream_fun_server_close() ->
     [{doc, "Test that an error msg is received when using a receiver fun as stream target"}].
-- 
2.16.4