diff options
Diffstat (limited to 'src/xmlrpc_http.erl')
-rw-r--r-- | src/xmlrpc_http.erl | 290 |
1 files changed, 148 insertions, 142 deletions
diff --git a/src/xmlrpc_http.erl b/src/xmlrpc_http.erl index b2970b3e0..d3933e08f 100644 --- a/src/xmlrpc_http.erl +++ b/src/xmlrpc_http.erl @@ -3,10 +3,10 @@ %% %% Redistribution and use in source and binary forms, with or without %% modification, are permitted provided that the following conditions -%% are met: +%% are met: %% %% 1. Redistributions of source code must retain the above copyright -%% notice, this list of conditions and the following disclaimer. +%% notice, this list of conditions and the following disclaimer. %% 2. Redistributions in binary form must reproduce the above %% copyright notice, this list of conditions and the following %% disclaimer in the documentation and/or other materials provided @@ -25,186 +25,192 @@ %% SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -module(xmlrpc_http). + -author('jocke@gleipnir.com'). + -export([handler/4]). -include("log.hrl"). --record(header, { - %% int() - content_length, - %% string() - content_type, - %% string() - user_agent, - %% close | undefined - connection - }). +-record(header, + {content_length :: non_neg_integer(), + content_type :: binary(), + user_agent :: binary(), + connection :: close}). %% Exported: handler/3 handler(Socket, Timeout, Handler, State) -> case parse_request(Socket, Timeout) of - {ok, Header} -> - ?DEBUG_LOG({header, Header}), - handle_payload(Socket, Timeout, Handler, State, Header); - {status, StatusCode} -> - send(Socket, StatusCode), - handler(Socket, Timeout, Handler, State); - {error, Reason} -> {error, Reason} + {ok, Header} -> + ?DEBUG_LOG({header, Header}), + handle_payload(Socket, Timeout, Handler, State, Header); + {status, StatusCode} -> + send(Socket, StatusCode), + handler(Socket, Timeout, Handler, State); + {error, Reason} -> {error, Reason} end. parse_request(Socket, Timeout) -> inet:setopts(Socket, [{packet, line}]), case gen_tcp:recv(Socket, 0, Timeout) of - {ok, RequestLine} -> - case string:tokens(RequestLine, " \r\n") of - ["POST", _, "HTTP/1.0"] -> - ?DEBUG_LOG({http_version, "1.0"}), - parse_header(Socket, Timeout, #header{connection = close}); - ["POST", _, "HTTP/1.1"] -> - ?DEBUG_LOG({http_version, "1.1"}), - parse_header(Socket, Timeout); - [Method, _, "HTTP/1.1"] -> {status, 501}; - ["POST", _, HTTPVersion] -> {status, 505}; - _ -> {status, 400} - end; - {error, Reason} -> {error, Reason} + {ok, RequestLine} -> + case str:tokens(RequestLine, <<" \r\n">>) of + [<<"POST">>, _, <<"HTTP/1.0">>] -> + ?DEBUG_LOG({http_version, <<"1.0">>}), + parse_header(Socket, Timeout, + #header{connection = close}); + [<<"POST">>, _, <<"HTTP/1.1">>] -> + ?DEBUG_LOG({http_version, <<"1.1">>}), + parse_header(Socket, Timeout); + [_Method, _, <<"HTTP/1.1">>] -> {status, 501}; + [<<"POST">>, _, _HTTPVersion] -> {status, 505}; + _ -> {status, 400} + end; + {error, Reason} -> {error, Reason} end. -parse_header(Socket, Timeout) -> parse_header(Socket, Timeout, #header{}). +parse_header(Socket, Timeout) -> + parse_header(Socket, Timeout, #header{}). parse_header(Socket, Timeout, Header) -> case gen_tcp:recv(Socket, 0, Timeout) of - {ok, "\r\n"} when Header#header.content_length == undefined -> - {status, 411}; - {ok, "\r\n"} when Header#header.content_type == undefined -> - {status, 400}; - {ok, "\r\n"} when Header#header.user_agent == undefined -> - {status, 400}; - {ok, "\r\n"} -> {ok, Header}; - {ok, HeaderField} -> - case split_header_field(HeaderField) of - {[$C,$o,$n,$t,$e,$n,$t,$-,_,$e,$n,$g,$t,$h,$:], - ContentLength} -> - case catch list_to_integer(ContentLength) of - N -> - parse_header(Socket, Timeout, - Header#header{content_length = N}); - _ -> {status, 400} - end; - {"Content-Type:", "text/xml"} -> - parse_header(Socket, Timeout, - Header#header{content_type = "text/xml"}); - {"Content-Type:", "text/xml; charset=utf-8"} -> - parse_header(Socket, Timeout, - Header#header{content_type = "text/xml; charset=utf-8"}); - {"Content-Type:", ContentType} -> {status, 415}; - {"User-Agent:", UserAgent} -> - parse_header(Socket, Timeout, - Header#header{user_agent = UserAgent}); - {"Connection:", "close"} -> - parse_header(Socket, Timeout, - Header#header{connection = close}); - {"Connection:", [_,$e,$e,$p,$-,_,$l,$i,$v,$e]} -> - parse_header(Socket, Timeout, - Header#header{connection = undefined}); - _ -> - ?DEBUG_LOG({skipped_header, HeaderField}), - parse_header(Socket, Timeout, Header) - end; - {error, Reason} -> {error, Reason} + {ok, <<"\r\n">>} + when Header#header.content_length == undefined -> + {status, 411}; + {ok, <<"\r\n">>} + when Header#header.content_type == undefined -> + {status, 400}; + {ok, <<"\r\n">>} + when Header#header.user_agent == undefined -> + {status, 400}; + {ok, <<"\r\n">>} -> {ok, Header}; + {ok, HeaderField} -> + case split_header_field(HeaderField) of + {<<"Content-", _, "ength:">>, ContentLength} -> + case catch jlib:binary_to_integer(ContentLength) of + N when is_integer(N), N>=0 -> + parse_header(Socket, Timeout, + Header#header{content_length = N}); + _ -> {status, 400} + end; + {<<"Content-Type:">>, <<"text/xml">>} -> + parse_header(Socket, Timeout, + Header#header{content_type = <<"text/xml">>}); + {<<"Content-Type:">>, <<"text/xml; charset=utf-8">>} -> + parse_header(Socket, Timeout, + Header#header{content_type = + <<"text/xml; charset=utf-8">>}); + {<<"Content-Type:">>, _ContentType} -> {status, 415}; + {<<"User-Agent:">>, UserAgent} -> + parse_header(Socket, Timeout, + Header#header{user_agent = UserAgent}); + {<<"Connection:">>, <<"close">>} -> + parse_header(Socket, Timeout, + Header#header{connection = close}); + {<<"Connection:">>, <<_, "eep-", _, "live">>} -> + parse_header(Socket, Timeout, + Header#header{connection = undefined}); + _ -> + ?DEBUG_LOG({skipped_header, HeaderField}), + parse_header(Socket, Timeout, Header) + end; + {error, Reason} -> {error, Reason} end. -split_header_field(HeaderField) -> split_header_field(HeaderField, []). +split_header_field(HeaderField) -> + split_header_field(binary_to_list(HeaderField), []). -split_header_field([], Name) -> {Name, ""}; -split_header_field([$ |Rest], Name) -> {lists:reverse(Name), Rest -- "\r\n"}; -split_header_field([C|Rest], Name) -> split_header_field(Rest, [C|Name]). +split_header_field([], Name) -> {list_to_binary(Name), <<"">>}; +split_header_field([$\s | Rest], Name) -> + {list_to_binary(lists:reverse(Name)), + list_to_binary(Rest -- "\r\n")}; +split_header_field([C | Rest], Name) -> + split_header_field(Rest, [C | Name]). handle_payload(Socket, Timeout, Handler, State, #header{connection = Connection} = Header) -> - case get_payload(Socket, Timeout, Header#header.content_length) of - {ok, Payload} -> - ?DEBUG_LOG({encoded_call, Payload}), - case xmlrpc_decode:payload(Payload) of - {ok, DecodedPayload} -> - ?DEBUG_LOG({decoded_call, DecodedPayload}), - eval_payload(Socket, Timeout, Handler, State, Connection, - DecodedPayload); - {error, Reason} when Connection == close -> - ?ERROR_LOG({xmlrpc_decode, payload, Payload, Reason}), - send(Socket, 400); - {error, Reason} -> - ?ERROR_LOG({xmlrpc_decode, payload, Payload, Reason}), - send(Socket, 400), - handler(Socket, Timeout, Handler, State) - end; - {error, Reason} -> {error, Reason} + case get_payload(Socket, Timeout, + Header#header.content_length) + of + {ok, Payload} -> + ?DEBUG_LOG({encoded_call, Payload}), + case xmlrpc_decode:payload(Payload) of + {ok, DecodedPayload} -> + ?DEBUG_LOG({decoded_call, DecodedPayload}), + eval_payload(Socket, Timeout, Handler, State, + Connection, DecodedPayload); + {error, Reason} when Connection == close -> + ?ERROR_LOG({xmlrpc_decode, payload, Payload, Reason}), + send(Socket, 400); + {error, Reason} -> + ?ERROR_LOG({xmlrpc_decode, payload, Payload, Reason}), + send(Socket, 400), + handler(Socket, Timeout, Handler, State) + end; + {error, Reason} -> {error, Reason} end. get_payload(Socket, Timeout, ContentLength) -> - inet:setopts(Socket, [{packet, raw}]), + inet:setopts(Socket, [binary, {packet, raw}]), gen_tcp:recv(Socket, ContentLength, Timeout). -eval_payload(Socket, Timeout, {M, F} = Handler, State, Connection, Payload) -> +eval_payload(Socket, Timeout, {M, F} = Handler, State, + Connection, Payload) -> case catch M:F(State, Payload) of - {'EXIT', Reason} when Connection == close -> - ?ERROR_LOG({M, F, {'EXIT', Reason}}), - send(Socket, 500, "Connection: close\r\n"); - {'EXIT', Reason} -> - ?ERROR_LOG({M, F, {'EXIT', Reason}}), - send(Socket, 500), - handler(Socket, Timeout, Handler, State); - {error, Reason} when Connection == close -> - ?ERROR_LOG({M, F, Reason}), - send(Socket, 500, "Connection: close\r\n"); - {error, Reason} -> - ?ERROR_LOG({M, F, Reason}), - send(Socket, 500), - handler(Socket, Timeout, Handler, State); - {false, ResponsePayload} -> - encode_send(Socket, 200, "Connection: close\r\n", ResponsePayload); - {true, NewTimeout, NewState, ResponsePayload} when - Connection == close -> - encode_send(Socket, 200, "Connection: close\r\n", ResponsePayload); - {true, NewTimeout, NewState, ResponsePayload} -> - encode_send(Socket, 200, "", ResponsePayload), - handler(Socket, NewTimeout, Handler, NewState) + {'EXIT', Reason} when Connection == close -> + ?ERROR_LOG({M, F, {'EXIT', Reason}}), + send(Socket, 500, <<"Connection: close\r\n">>); + {'EXIT', Reason} -> + ?ERROR_LOG({M, F, {'EXIT', Reason}}), + send(Socket, 500), + handler(Socket, Timeout, Handler, State); + {error, Reason} when Connection == close -> + ?ERROR_LOG({M, F, Reason}), + send(Socket, 500, <<"Connection: close\r\n">>); + {error, Reason} -> + ?ERROR_LOG({M, F, Reason}), + send(Socket, 500), + handler(Socket, Timeout, Handler, State); + {false, ResponsePayload} -> + encode_send(Socket, 200, <<"Connection: close\r\n">>, + ResponsePayload); + {true, _NewTimeout, _NewState, ResponsePayload} + when Connection == close -> + encode_send(Socket, 200, <<"Connection: close\r\n">>, + ResponsePayload); + {true, NewTimeout, NewState, ResponsePayload} -> + encode_send(Socket, 200, <<"">>, ResponsePayload), + handler(Socket, NewTimeout, Handler, NewState) end. encode_send(Socket, StatusCode, ExtraHeader, Payload) -> ?DEBUG_LOG({decoded_response, Payload}), - case xmlrpc_encode:payload(Payload) of - {ok, EncodedPayload} -> - ?DEBUG_LOG({encoded_response, lists:flatten(EncodedPayload)}), - send(Socket, StatusCode, ExtraHeader, EncodedPayload); - {error, Reason} -> - ?ERROR_LOG({xmlrpc_encode, payload, Payload, Reason}), - send(Socket, 500) - end. + EncodedPayload = xmlrpc_encode:payload(Payload), + ?DEBUG_LOG({encoded_response, EncodedPayload}), + send(Socket, StatusCode, ExtraHeader, EncodedPayload). -send(Socket, StatusCode) -> send(Socket, StatusCode, "", ""). +send(Socket, StatusCode) -> + send(Socket, StatusCode, <<"">>, <<"">>). send(Socket, StatusCode, ExtraHeader) -> - send(Socket, StatusCode, ExtraHeader, ""). + send(Socket, StatusCode, ExtraHeader, <<"">>). send(Socket, StatusCode, ExtraHeader, Payload) -> - Response = - ["HTTP/1.1 ", integer_to_list(StatusCode), " ", - reason_phrase(StatusCode), "\r\n", - "Content-Length: ", integer_to_list(lists:flatlength(Payload)), - "\r\n", - "Server: Erlang/1.13\r\n", - "Content-Type: text/xml\r\n", - ExtraHeader, "\r\n", - Payload], + Response = [<<"HTTP/1.1 ">>, + jlib:integer_to_binary(StatusCode), <<" ">>, + reason_phrase(StatusCode), <<"\r\n">>, + <<"Content-Length: ">>, + jlib:integer_to_binary(byte_size(Payload)), + <<"\r\n">>, <<"Server: Erlang/1.13\r\n">>, + <<"Content-Type: text/xml\r\n">>, ExtraHeader, + <<"\r\n">>, Payload], gen_tcp:send(Socket, Response). -reason_phrase(200) -> "OK"; -reason_phrase(400) -> "Bad Request"; -reason_phrase(411) -> "Length required"; -reason_phrase(415) -> "Unsupported Media Type"; -reason_phrase(500) -> "Internal Server Error"; -reason_phrase(501) -> "Not Implemented"; -reason_phrase(505) -> "HTTP Version not supported". +reason_phrase(200) -> <<"OK">>; +reason_phrase(400) -> <<"Bad Request">>; +reason_phrase(411) -> <<"Length required">>; +reason_phrase(415) -> <<"Unsupported Media Type">>; +reason_phrase(500) -> <<"Internal Server Error">>; +reason_phrase(501) -> <<"Not Implemented">>; +reason_phrase(505) -> <<"HTTP Version not supported">>. |