%%% BEGIN openflax/http/request.erl %%% %%% %%% openflax - Open Source web server for Erlang/OTP %%% Copyright (c)2004 Cat's Eye Technologies. All rights reserved. %%% %%% Redistribution and use in source and binary forms, with or without %%% modification, are permitted provided that the following conditions %%% are met: %%% %%% Redistributions of source code must retain the above copyright %%% notice, this list of conditions and the following disclaimer. %%% %%% 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 with the %%% distribution. %%% %%% Neither the name of Cat's Eye Technologies nor the names of its %%% contributors may be used to endorse or promote products derived %%% from this software without specific prior written permission. %%% %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND %%% CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, %%% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF %%% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE %%% DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE %%% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, %%% OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, %%% PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, %%% OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON %%% ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, %%% OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY %%% OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE %%% POSSIBILITY OF SUCH DAMAGE. %% @doc HTTP request collecting and validating for OpenFlax. %% %%
This module provides a thin interface for collecting and %% validating incoming HTTP requests in OpenFlax.
%% %%openflax.http.interface is used to retrieve
%% the request and perform the basic parsing of the request line,
%% the headers, and the body. The results of this retrieval
%% are collected into a conf(), and validated for
%% compliance to RFC 2616.
In the case of non-trivial request bodies, such as a
%% multipart/form-data Content-Type for the request, a
%% custom parsing callback, if present, will be used to parse the form.
%% This callback is specified by the
%% cfg_openflax_http_body_parser
%% setting in the global configuration.
Key=Value pairs given after the first ?
%% in the URI are extracted and added to the conf()
%% with an arg_ prefix added to each key.
%% Subsequent lines until a blank line are parsed as HTTP headers,
%% and each is added to the returned conf().
%% Finally, POSTed form data is parsed. Fields in it are extracted
%% and placed in the conf() with form_ prefixed
%% to each field name.
collect(Http, Conf, MasterDict) ->
case catch collect0(Http, Conf, MasterDict) of
{'EXIT', Reason} ->
openflax.app:debug(collect_error, Reason),
{error, {server_error, Conf}};
{error, {Reason, Conf0}} ->
{error, {Reason, Conf0}};
Else ->
validate(Else)
end.
collect0(Http, Conf, MasterDict) ->
receive
{Http, request_line,
{Host, Port, Request, Major, Minor, Method, URI, Root, Args, ArgList}} ->
% openflax.app:debug(collected, URI),
Conf0 = openflax.conf:new([
{sreq_host, Host},
{sreq_port, Port},
{sreq_request_line, Request},
{sreq_major_version, Major},
{sreq_minor_version, Minor},
{sreq_method, Method},
{sreq_uri, URI},
{sreq_basic_resource, "/" ++ Root},
{sreq_basic_resource_container, filename:dirname("/" ++ Root)},
{req_basic_resource_leaf, filename:basename("/" ++ Root)},
{sreq_basic_arguments, Args}] ++
lists:map(fun({Key, Val}) ->
{openflax.string:to_atom("arg_" ++ openflax.string:from_header(Key)),
Val}
end, ArgList)),
Conf1 = openflax.conf:merge(Conf, Conf0),
collect0(Http, Conf1, MasterDict);
{Http, request_header, {req_host, HostPort}} ->
% openflax.app:debug(collected, HostPort),
% if this was already set by absoluteURI, ignore
Conf2 = case openflax.conf:get_string(sreq_host, Conf) of
"" ->
{Host, Port} = parse:hostport(HostPort),
Conf0 = openflax.conf:put_value(sreq_host, Host, Conf),
Conf1 = openflax.conf:put_value(sreq_port, Port, Conf0),
Conf1;
_ ->
Conf
end,
collect0(Http, Conf2, MasterDict);
{Http, request_header, {Key, Value}} ->
% openflax.app:debug(collected, {Key, Value}),
Conf0 = openflax.conf:put_value(Key, Value, Conf),
collect0(Http, Conf0, MasterDict);
{Http, request_header_end, _} ->
% end of headers, so find which virtual host we are,
% and get their settings.
% openflax.app:debug(end_of_req_hdrs, 0),
Host = openflax.conf:get_string(sreq_host, Conf),
HostConf = dict:fetch(list_to_atom(Host), MasterDict),
openflax.conf:merge(HostConf, Conf);
{Http, {error, {Error, _Reason}}} ->
{error, {Error, Conf}}
end.
%% @spec validate(conf()) ->
%% {ok, conf()} | {error, {response(), conf()}}
%% @doc Checks the HTTP request for validity.
validate(Conf) ->
% check expectations
case openflax.conf:get_string(req_expect, Conf) of
"" ->
validate0(Conf);
_ ->
{error, {expectation_failed, Conf}}
end.
validate0(Conf) ->
% check transfer-encoding
case openflax.conf:get_string(req_transfer_encoding, Conf) of
TE when TE == ""; TE == "identity"; TE == "chunked" ->
{ok, Conf};
_ ->
{error, {not_implemented, Conf}}
end.
%% @spec collect_body(conf()) -> conf()
%% @doc Collect the body of the HTTP request.
%% This function may be called by handlers to retrieve the HTTP request body
%% as as form_* settings in a conf().
collect_body(Conf) ->
{ok, Http} = openflax.conf:get_value(cfg_openflax_http, Conf),
case openflax.conf:get_value(cfg_openflax_body_collected, Conf) of
{ok, true} ->
Conf;
_ ->
collect_body0(Http, Conf)
end.
collect_body0(Http, Conf) ->
receive
{Http, request_body, ArgList} ->
% openflax.app:debug(collected, {body, ArgList}),
Conf0 = lists:foldl(fun({Key, Value}, Acc) ->
openflax.conf:put_value(
openflax.string:to_atom("form_" ++ openflax.string:from_header(Key)),
Value, Acc)
end, Conf, ArgList),
openflax.conf:put_value(cfg_openflax_body_collected, true, Conf0);
{Http, multipart_header, Header} ->
% openflax.app:debug(multipart_header, Header),
%% TODO: stick header in conf
collect_body0(Http, Conf);
{Http, multipart_chunk, Chunk} ->
% openflax.app:debug(multipart_chunk, length(Chunk)),
%% TODO: stick chunk in conf
collect_body0(Http, Conf);
{Http, multipart_end, _} ->
% openflax.app:debug(multipart_end, ok),
openflax.conf:put_value(cfg_openflax_body_collected, true, Conf);
Else ->
openflax.app:debug(unknown_http_request_body_part, Else),
Conf
end.
%%% END of openflax/http/request.erl %%%