]> granicus.if.org Git - ejabberd/commitdiff
Parse correctly https request split into multiple packets
authorPaweł Chmielowski <pchmielowski@process-one.net>
Fri, 6 Apr 2012 14:22:08 +0000 (16:22 +0200)
committerPaweł Chmielowski <pchmielowski@process-one.net>
Fri, 6 Apr 2012 14:22:08 +0000 (16:22 +0200)
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.

src/web/ejabberd_http.erl

index eae1e771cf33081f6323cab84e97fecc69071454..f9d390dbef11f64f920a97c900e2eba5817d5557 100644 (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)}.