Permalink
Browse files

add ability to handle combined content-length header.

  • Loading branch information...
1 parent 9d205a8 commit 888ceb58d6c6cc24d5c6ad375249b5f636570bbf @kmwang kmwang committed Oct 9, 2012
Showing with 66 additions and 6 deletions.
  1. +58 −1 src/mochiweb_headers.erl
  2. +1 −1 src/mochiweb_multipart.erl
  3. +7 −4 src/mochiweb_request.erl
@@ -6,7 +6,7 @@
-module(mochiweb_headers).
-author('bob@mochimedia.com').
-export([empty/0, from_list/1, insert/3, enter/3, get_value/2, lookup/2]).
--export([delete_any/2, get_primary_value/2]).
+-export([delete_any/2, get_primary_value/2, get_combined_value/2]).
-export([default/3, enter_from_list/2, default_from_list/2]).
-export([to_list/1, make/1]).
-export([from_binary/1]).
@@ -112,6 +112,32 @@ get_primary_value(K, T) ->
lists:takewhile(fun (C) -> C =/= $; end, V)
end.
+%% @spec get_combined_value(key(), headers()) -> string() | undefined
+%% @doc Return the value from the given header using a case insensitive search.
+%% If the value of the header is a comma-separated list where holds values
+%% are all identical, the identical value will be returned.
+%% undefined will be returned for keys that are not present or the
+%% values in the list are not the same.
+%%
+%% Section 4.2 of the RFC 2616 (HTTP 1.1) describes multiple message-header
+%% fields with the same field-name may be present in a message if and only
+%% if the entire field-value for that header field is defined as a
+%% comma-separated list [i.e., #(values)].
+get_combined_value(K, T) ->
+ case get_value(K, T) of
+ undefined ->
+ undefined;
+ V ->
+ UniqueValuesList = sets:to_list(sets:from_list(string:tokens(V, " ,"))),
+ case length(UniqueValuesList) =:= 1 of
+ true ->
+ [Val|_Ignore] = UniqueValuesList,
+ Val;
+ false ->
+ undefined
+ end
+ end.
+
%% @spec lookup(key(), headers()) -> {value, {key(), string()}} | none
%% @doc Return the case preserved key and value for the given header using
%% a case insensitive search. none will be returned for keys that are
@@ -237,6 +263,37 @@ get_primary_value_test() ->
get_primary_value(<<"baz">>, H)),
ok.
+get_combined_value_test() ->
+ H = make([{hdr, foo}, {baz, <<"wibble,taco">>}, {content_length, "123, 123"},
+ {test, " 123, 123, 123 , 123,123 "},
+ {test2, "456, 123, 123 , 123"},
+ {test3, "123"}, {test4, " 123, "}]),
+ ?assertEqual(
+ "foo",
+ get_combined_value(hdr, H)),
+ ?assertEqual(
+ undefined,
+ get_combined_value(bar, H)),
+ ?assertEqual(
+ undefined,
+ get_combined_value(<<"baz">>, H)),
+ ?assertEqual(
+ "123",
+ get_combined_value(<<"content_length">>, H)),
+ ?assertEqual(
+ "123",
+ get_combined_value(<<"test">>, H)),
+ ?assertEqual(
+ undefined,
+ get_combined_value(<<"test2">>, H)),
+ ?assertEqual(
+ "123",
+ get_combined_value(<<"test3">>, H)),
+ ?assertEqual(
+ "123",
+ get_combined_value(<<"test4">>, H)),
+ ok.
+
set_cookie_test() ->
H = make([{"set-cookie", foo}, {"set-cookie", bar}, {"set-cookie", baz}]),
?assertEqual(
@@ -128,7 +128,7 @@ default_file_handler_1(Filename, ContentType, Acc) ->
parse_multipart_request(Req, Callback) ->
%% TODO: Support chunked?
- Length = list_to_integer(Req:get_header_value("content-length")),
+ Length = list_to_integer(Req:get_combined_header_value("content-length")),
Boundary = iolist_to_binary(
get_boundary(Req:get_header_value("content-type"))),
Prefix = <<"\r\n--", Boundary/binary>>,
@@ -11,7 +11,7 @@
-define(QUIP, "Any of you quaids got a smint?").
--export([get_header_value/1, get_primary_header_value/1, get/1, dump/0]).
+-export([get_header_value/1, get_primary_header_value/1, get_combined_header_value/1, get/1, dump/0]).
-export([send/1, recv/1, recv/2, recv_body/0, recv_body/1, stream_body/3]).
-export([start_response/1, start_response_length/1, start_raw_response/1]).
-export([respond/1, ok/1]).
@@ -52,6 +52,9 @@ get_header_value(K) ->
get_primary_header_value(K) ->
mochiweb_headers:get_primary_value(K, Headers).
+get_combined_header_value(K) ->
+ mochiweb_headers:get_combined_value(K, Headers).
+
%% @type field() = socket | scheme | method | raw_path | version | headers | peer | path | body_length | range
%% @spec get(field()) -> term()
@@ -167,7 +170,7 @@ recv(Length, Timeout) ->
body_length() ->
case get_header_value("transfer-encoding") of
undefined ->
- case get_header_value("content-length") of
+ case get_combined_header_value("content-length") of
undefined ->
undefined;
Length ->
@@ -389,8 +392,8 @@ should_close() ->
andalso get_header_value("connection") =/= "Keep-Alive")
%% unread data left on the socket, can't safely continue
orelse (DidNotRecv
- andalso get_header_value("content-length") =/= undefined
- andalso list_to_integer(get_header_value("content-length")) > 0)
+ andalso get_combined_header_value("content-length") =/= undefined
+ andalso list_to_integer(get_combined_header_value("content-length")) > 0)
orelse (DidNotRecv
andalso get_header_value("transfer-encoding") =:= "chunked").

0 comments on commit 888ceb5

Please sign in to comment.