mirror of
https://github.com/processone/ejabberd.git
synced 2024-12-22 17:28:25 +01:00
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:
parent
09c75af15c
commit
5f82846732
@ -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)}.
|
||||
|
Loading…
Reference in New Issue
Block a user