Parse correctly https request split into multiple packets

This fixes case when SockMod:recv() calls returns only part of first line
of http request (GET/POST/OPTION/HEAD line). Before that change request
like that (and if keep-alive was active, all further request) were dropped.

This fixes EJAB-1537.
This commit is contained in:
Paweł Chmielowski 2012-04-06 16:22:08 +02:00
parent 09c75af15c
commit 5f82846732
1 changed files with 80 additions and 130 deletions

View File

@ -66,7 +66,7 @@
request_headers = [],
end_of_request = false,
default_host,
trail = ""
trail = <<>>
}).
@ -168,12 +168,12 @@ send_text(State, Text) ->
exit(normal)
end.
receive_headers(State) ->
receive_headers(#state{trail=Trail} = State) ->
SockMod = State#state.sockmod,
Socket = State#state.socket,
Data = SockMod:recv(Socket, 0, 300000),
case State#state.sockmod of
gen_tcp ->
gen_tcp ->
NewState = process_header(State, Data),
case NewState#state.end_of_request of
true ->
@ -181,31 +181,35 @@ receive_headers(State) ->
_ ->
receive_headers(NewState)
end;
_ ->
case Data of
{ok, Binary} ->
{Request, Trail} = parse_request(
State,
State#state.trail ++ binary_to_list(Binary)),
State1 = State#state{trail = Trail},
NewState = lists:foldl(
fun(D, S) ->
case S#state.end_of_request of
true ->
S;
_ ->
process_header(S, D)
end
end, State1, Request),
case NewState#state.end_of_request of
true ->
ok;
_ ->
receive_headers(NewState)
end;
_ ->
case Data of
{ok, D} ->
parse_headers(State#state{trail = <<Trail/binary, D/binary>>});
{error, _} ->
ok
end
end.
parse_headers(#state{trail = <<>>} = State) ->
receive_headers(State);
parse_headers(#state{request_method = Method, trail = Data} = State) ->
PktType = case Method of
undefined -> http;
_ -> httph
end,
case decode_packet(PktType, Data) of
{ok, Pkt, Rest} ->
NewState = process_header(State#state{trail = Rest}, {ok, Pkt}),
case NewState#state.end_of_request of
true ->
ok;
_ ->
ok
end
parse_headers(NewState)
end;
{more, _} ->
receive_headers(State#state{trail = Data});
_ ->
ok
end.
process_header(State, Data) ->
@ -519,16 +523,16 @@ recv_data(_State, 0, Acc) ->
binary_to_list(list_to_binary(Acc));
recv_data(State, Len, Acc) ->
case State#state.trail of
[] ->
case (State#state.sockmod):recv(State#state.socket, Len, 300000) of
<<>> ->
case (State#state.sockmod):recv(State#state.socket, Len, 300000) of
{ok, Data} ->
recv_data(State, Len - size(Data), [Acc | [Data]]);
_ ->
""
end;
_ ->
Trail = State#state.trail,
recv_data(State#state{trail = ""}, Len - length(Trail), [Acc | Trail])
Trail = binary_to_list(State#state.trail),
recv_data(State#state{trail = <<>>}, Len - length(Trail), [Acc | Trail])
end.
@ -809,7 +813,7 @@ parse_auth(_) ->
decode_base64([]) ->
[];
decode_base64([Sextet1,Sextet2,$=,$=|Rest]) ->
Bits2x6=
Bits2x6=
(d(Sextet1) bsl 18) bor
(d(Sextet2) bsl 12),
Octet1=Bits2x6 bsr 16,
@ -919,42 +923,29 @@ old_integer_to_hex(I) when I>=16 ->
% The following code is mostly taken from yaws_ssl.erl
parse_request(State, Data) ->
case Data of
[] ->
{[], []};
_ ->
?DEBUG("GOT ssl data ~p~n", [Data]),
{R, Trail} = case State#state.request_method of
undefined ->
{R1, Trail1} = get_req(Data),
?DEBUG("Parsed request ~p~n", [R1]),
{[R1], Trail1};
_ ->
{[], Data}
end,
{H, Trail2} = get_headers(Trail),
{R ++ H, Trail2}
decode_packet(_, <<"\r\n", Rest/binary>>) ->
{ok, http_eoh, Rest};
decode_packet(Type, Data) ->
case binary:match(Data, <<"\r\n">>) of
{Start, _Len} ->
<<LineB:Start/binary, _:2/binary, Rest/binary>> = Data,
Line = binary_to_list(LineB),
Result = case Type of
http ->
parse_req(Line);
httph ->
parse_line(Line)
end,
case Result of
{ok, H} ->
{ok, H, Rest};
Err ->
{error, Err}
end;
_ ->
{more, undefined}
end.
get_req("\r\n\r\n" ++ _) ->
bad_request;
get_req("\r\n" ++ Data) ->
get_req(Data);
get_req(Data) ->
{FirstLine, Trail} = lists:splitwith(fun not_eol/1, Data),
R = parse_req(FirstLine),
{R, Trail}.
not_eol($\r)->
false;
not_eol($\n) ->
false;
not_eol(_) ->
true.
get_word(Line)->
{Word, T} = lists:splitwith(fun(X)-> X /= $\ end, Line),
{Word, lists:dropwhile(fun(X) -> X == $\ end, T)}.
@ -1025,68 +1016,54 @@ parse_req(Line) ->
end.
get_headers(Tail) ->
get_headers([], Tail).
get_headers(H, Tail) ->
case get_line(Tail) of
{incomplete, Tail2} ->
{H, Tail2};
{line, Line, Tail2} ->
get_headers(H ++ parse_line(Line), Tail2);
{lastline, Line, Tail2} ->
{H ++ parse_line(Line) ++ [{ok, http_eoh}], Tail2}
end.
parse_line("Connection:" ++ Con) ->
[{ok, {http_header, undefined, 'Connection', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'Connection', undefined, strip_spaces(Con)}};
parse_line("Host:" ++ Con) ->
[{ok, {http_header, undefined, 'Host', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'Host', undefined, strip_spaces(Con)}};
parse_line("Accept:" ++ Con) ->
[{ok, {http_header, undefined, 'Accept', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'Accept', undefined, strip_spaces(Con)}};
parse_line("If-Modified-Since:" ++ Con) ->
[{ok, {http_header, undefined, 'If-Modified-Since', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'If-Modified-Since', undefined, strip_spaces(Con)}};
parse_line("If-Match:" ++ Con) ->
[{ok, {http_header, undefined, 'If-Match', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'If-Match', undefined, strip_spaces(Con)}};
parse_line("If-None-Match:" ++ Con) ->
[{ok, {http_header, undefined, 'If-None-Match', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'If-None-Match', undefined, strip_spaces(Con)}};
parse_line("If-Range:" ++ Con) ->
[{ok, {http_header, undefined, 'If-Range', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'If-Range', undefined, strip_spaces(Con)}};
parse_line("If-Unmodified-Since:" ++ Con) ->
[{ok, {http_header, undefined, 'If-Unmodified-Since', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'If-Unmodified-Since', undefined, strip_spaces(Con)}};
parse_line("Range:" ++ Con) ->
[{ok, {http_header, undefined, 'Range', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'Range', undefined, strip_spaces(Con)}};
parse_line("User-Agent:" ++ Con) ->
[{ok, {http_header, undefined, 'User-Agent', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'User-Agent', undefined, strip_spaces(Con)}};
parse_line("Accept-Ranges:" ++ Con) ->
[{ok, {http_header, undefined, 'Accept-Ranges', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'Accept-Ranges', undefined, strip_spaces(Con)}};
parse_line("Authorization:" ++ Con) ->
[{ok, {http_header, undefined, 'Authorization', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'Authorization', undefined, strip_spaces(Con)}};
parse_line("Keep-Alive:" ++ Con) ->
[{ok, {http_header, undefined, 'Keep-Alive', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'Keep-Alive', undefined, strip_spaces(Con)}};
parse_line("Referer:" ++ Con) ->
[{ok, {http_header, undefined, 'Referer', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'Referer', undefined, strip_spaces(Con)}};
parse_line("Content-type:"++Con) ->
[{ok, {http_header, undefined, 'Content-Type', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'Content-Type', undefined, strip_spaces(Con)}};
parse_line("Content-Type:"++Con) ->
[{ok, {http_header, undefined, 'Content-Type', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'Content-Type', undefined, strip_spaces(Con)}};
parse_line("Content-Length:"++Con) ->
[{ok, {http_header, undefined, 'Content-Length', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'Content-Length', undefined, strip_spaces(Con)}};
parse_line("Content-length:"++Con) ->
[{ok, {http_header, undefined, 'Content-Length', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'Content-Length', undefined, strip_spaces(Con)}};
parse_line("Cookie:"++Con) ->
[{ok, {http_header, undefined, 'Cookie', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'Cookie', undefined, strip_spaces(Con)}};
parse_line("Accept-Language:"++Con) ->
[{ok, {http_header, undefined, 'Accept-Language', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'Accept-Language', undefined, strip_spaces(Con)}};
parse_line("Accept-Encoding:"++Con) ->
[{ok, {http_header, undefined, 'Accept-Encoding', undefined, strip_spaces(Con)}}];
{ok, {http_header, undefined, 'Accept-Encoding', undefined, strip_spaces(Con)}};
parse_line(S) ->
case lists:splitwith(fun(C)->C /= $: end, S) of
{Name, [$:|Val]} ->
[{ok, {http_header, undefined, Name, undefined, strip_spaces(Val)}}];
{ok, {http_header, undefined, Name, undefined, strip_spaces(Val)}};
_ ->
[]
bad_request
end.
@ -1121,30 +1098,3 @@ drop_spaces(YS=[X|XS]) ->
false ->
YS
end.
is_nb_space(X) ->
lists:member(X, [$\s, $\t]).
% ret: {line, Line, Trail} | {lastline, Line, Trail}
get_line(L) ->
get_line(L, []).
get_line("\r\n\r\n" ++ Tail, Cur) ->
{lastline, lists:reverse(Cur), Tail};
get_line("\r\n" ++ Tail, Cur) ->
case Tail of
[] ->
{incomplete, lists:reverse(Cur) ++ "\r\n"};
_ ->
case is_nb_space(hd(Tail)) of
true -> %% multiline ... continue
get_line(Tail, [$\n, $\r | Cur]);
false ->
{line, lists:reverse(Cur), Tail}
end
end;
get_line([H|T], Cur) ->
get_line(T, [H|Cur]);
get_line([], Cur) ->
{incomplete, lists:reverse(Cur)}.