%%% 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.

%% %% @end -module(openflax.http.request). -vsn('$Id: request.erl 31 2004-04-23 07:00:11Z catseye $'). -author('catseye@catseye.mb.ca'). -copyright('Copyright (c)2004 Cat`s Eye Technologies. All rights reserved.'). -export([collect/3]). -export([collect_body/1]). -import(lists). -import(filename). -import(dict). -import(io_lib). %% @spec collect(http(), conf(), master_dict()) -> %% {ok, conf()} | {error, {response(), conf()}} %% @doc Collects the incoming HTTP request as it comes in, then validates it. %% 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 %%%