diff --git a/src/ibrowse.erl b/src/ibrowse.erl index fc90dc6..85bb75c 100644 --- a/src/ibrowse.erl +++ b/src/ibrowse.erl @@ -150,7 +150,7 @@ stop() -> %% The Status return value indicates the HTTP status code returned by the webserver %% @spec send_req(Url::string(), Headers::headerList(), Method::method()) -> response() %% headerList() = [{header(), value()}] -%% header() = atom() | string() +%% header() = atom() | string() | binary() %% value() = term() %% method() = get | post | head | options | put | delete | trace | mkcol | propfind | proppatch | lock | unlock | move | copy %% Status = string() diff --git a/src/ibrowse_http_client.erl b/src/ibrowse_http_client.erl index 64fc443..6ee806b 100644 --- a/src/ibrowse_http_client.erl +++ b/src/ibrowse_http_client.erl @@ -924,7 +924,7 @@ make_request(Method, Headers, AbsPath, RelPath, Body, Options, HttpVsn = http_vsn_string(get_value(http_vsn, Options, {1,1})), Fun1 = fun({X, Y}) when is_atom(X) -> {to_lower(atom_to_list(X)), X, Y}; - ({X, Y}) when is_list(X) -> + ({X, Y}) when is_list(X); is_binary(X) -> {to_lower(X), X, Y} end, Headers_0 = [Fun1(X) || X <- Headers], @@ -1010,7 +1010,7 @@ encode_headers(L) -> encode_headers(L, []). encode_headers([{http_vsn, _Val} | T], Acc) -> encode_headers(T, Acc); -encode_headers([{Name,Val} | T], Acc) when is_list(Name) -> +encode_headers([{Name,Val} | T], Acc) when is_list(Name); is_binary(Name) -> encode_headers(T, [[Name, ": ", fmt_val(Val), crnl()] | Acc]); encode_headers([{Name,Val} | T], Acc) when is_atom(Name) -> encode_headers(T, [[atom_to_list(Name), ": ", fmt_val(Val), crnl()] | Acc]); @@ -1072,7 +1072,7 @@ parse_response(Data, #state{reply_buffer = Acc, reqs = Reqs, {HttpVsn, StatCode, Headers_1, Status_line, Raw_headers} = parse_headers(Headers), do_trace("HttpVsn: ~p StatusCode: ~p Headers_1 -> ~1000.p~n", [HttpVsn, StatCode, Headers_1]), LCHeaders = [{to_lower(X), Y} || {X,Y} <- Headers_1], - ConnClose = to_lower(get_value("connection", LCHeaders, "false")), + ConnClose = to_lower(get_header_value("connection", LCHeaders, "false")), IsClosing = is_connection_closing(HttpVsn, ConnClose), State_0 = case IsClosing of true -> @@ -1096,11 +1096,11 @@ parse_response(Data, #state{reply_buffer = Acc, reqs = Reqs, http_status_code=StatCode} end, put(conn_close, ConnClose), - TransferEncodings = to_lower(get_value("transfer-encoding", LCHeaders, "false")), + TransferEncodings = to_lower(get_header_value("transfer-encoding", LCHeaders, "false")), IsChunked = lists:any(fun(Enc) -> string:strip(Enc) =:= "chunked" end, string:tokens(TransferEncodings, ",")), Head_response_with_body = lists:member({workaround, head_response_with_body}, Options), - case get_value("content-length", LCHeaders, undefined) of + case get_header_value("content-length", LCHeaders, undefined) of _ when Method == connect, hd(StatCode) == $2 -> {_, Reqs_1} = queue:out(Reqs), @@ -1906,6 +1906,8 @@ cancel_timer(Ref, {eat_message, Msg}) -> make_req_id() -> now(). +to_lower(Str) when is_binary(Str) -> + to_lower(binary_to_list(Str)); to_lower(Str) -> to_lower(Str, []). to_lower([H|T], Acc) when H >= $A, H =< $Z -> @@ -2021,3 +2023,13 @@ to_binary({X, _}) when is_function(X) -> to_binary(X); to_binary(X) when is_function(X) -> <<"body generated by function">>; to_binary(X) when is_list(X) -> list_to_binary(X); to_binary(X) when is_binary(X) -> X. + +get_header_value(Name, Headers, Default_val) -> + case lists:keysearch(Name, 1, Headers) of + false -> + Default_val; + {value, {_, Val}} when is_binary(Val) -> + binary_to_list(Val); + {value, {_, Val}} -> + Val + end. diff --git a/test/ibrowse_test.erl b/test/ibrowse_test.erl index 4d759eb..407ffcb 100644 --- a/test/ibrowse_test.erl +++ b/test/ibrowse_test.erl @@ -32,6 +32,8 @@ test_303_response_with_no_body/1, test_303_response_with_a_body/0, test_303_response_with_a_body/1, + test_binary_headers/0, + test_binary_headers/1, test_generate_body_0/0 ]). @@ -239,8 +241,8 @@ dump_errors(Key, Iod) -> {local_test_fun, test_pipeline_head_timeout, []}, {local_test_fun, test_head_transfer_encoding, []}, {local_test_fun, test_head_response_with_body, []}, - {local_test_fun, test_303_response_with_a_body, []} - + {local_test_fun, test_303_response_with_a_body, []}, + {local_test_fun, test_binary_headers, []} ]). unit_tests() -> @@ -466,6 +468,28 @@ test_head_transfer_encoding(Url) -> {test_failed, Res} end. +%%------------------------------------------------------------------------------ +%% Test what happens when the response to a HEAD request is a +%% Chunked-Encoding response with a non-empty body. Issue #67 on +%% Github +%% ------------------------------------------------------------------------------ +test_binary_headers() -> + clear_msg_q(), + test_binary_headers("http://localhost:8181/ibrowse_echo_header"). + +test_binary_headers(Url) -> + case ibrowse:send_req(Url, [{<<"x-binary">>, <<"x-header">>}], get) of + {ok, "200", Headers, _} -> + case proplists:get_value("x-binary", Headers) of + "x-header" -> + success; + V -> + {fail, V} + end; + Res -> + {test_failed, Res} + end. + %%------------------------------------------------------------------------------ %% Test what happens when the response to a HEAD request is a %% Chunked-Encoding response with a non-empty body. Issue #67 on diff --git a/test/ibrowse_test_server.erl b/test/ibrowse_test_server.erl index 8aed065..703227d 100644 --- a/test/ibrowse_test_server.erl +++ b/test/ibrowse_test_server.erl @@ -155,6 +155,25 @@ process_request(Sock, Sock_type, uri = {abs_path, "/ibrowse_head_transfer_enc"}}) -> Resp = <<"HTTP/1.1 400 Bad Request\r\nServer: Apache-Coyote/1.1\r\nContent-Length:5\r\nDate: Wed, 04 Apr 2012 16:53:49 GMT\r\n\r\nabcde">>, do_send(Sock, Sock_type, Resp); + +process_request(Sock, Sock_type, + #request{method='GET', + headers = Headers, + uri = {abs_path, "/ibrowse_echo_header"}}) -> + Tag = "x-binary", + Headers_1 = [{to_lower(X), to_lower(Y)} || {http_header, _, X, _, Y} <- Headers], + X_binary_header_val = case lists:keysearch(Tag, 1, Headers_1) of + false -> + "not_found"; + {value, {_, V}} -> + V + end, + Resp = [<<"HTTP/1.1 200 OK\r\n">>, + <<"Server: ibrowse_test\r\n">>, + Tag, ": ", X_binary_header_val, "\r\n", + <<"Content-Length: 0\r\n\r\n">>], + do_send(Sock, Sock_type, Resp); + process_request(Sock, Sock_type, #request{method='HEAD', headers = _Headers, @@ -231,3 +250,7 @@ split_list_at(List2, 0, List1) -> split_list_at([H | List2], N, List1) -> split_list_at(List2, N-1, [H | List1]). +to_lower(X) when is_atom(X) -> + list_to_atom(to_lower(atom_to_list(X))); +to_lower(X) when is_list(X) -> + string:to_lower(X).