src/inets_bridge_modules/inets_simple_bridge.erl

% vim: ts=4 sw=4 et
% Simple Bridge
% Copyright (c) 2008-2010 Rusty Klophaus
% See MIT-LICENSE for licensing information.

-module (inets_simple_bridge).
-behaviour (simple_bridge).
-include("simple_bridge.hrl").
-include_lib("inets/include/httpd.hrl").
-export ([
        init/1,
        protocol/1,
        host/1,
        request_method/1,
        path/1,
        uri/1,
        peer_ip/1,
        peer_port/1,
        headers/1,
        cookies/1,
        query_params/1,
        post_params/1,
        request_body/1,
        socket/1,
        recv_from_socket/3,
        protocol_version/1,
        native_header_type/0
    ]).

-export([
        build_response/2
    ]).


init(Req) -> 
    Req.

protocol(Req) ->
    case Req#mod.socket of
        S when is_tuple(S), element(1, S) =:= sslsocket -> https;
        _ -> http
    end.

host(Req) ->
    Headers = headers(Req),
    Host = proplists:get_value("host", Headers),
    XForwardedFor = proplists:get_value("x-forwarded-for", Headers),
    error_logger:info_msg("~p, ~p, ~p~n",[Req#mod.absolute_uri, Host, XForwardedFor]),
    simple_bridge_util:infer_host(Req#mod.absolute_uri, Host, XForwardedFor).

request_method(Req) ->
    list_to_atom(Req#mod.method).

path(Req) -> 
    {Path, _QueryString} = split_request_uri(Req#mod.request_uri, []),
    Path.

uri(Req) ->
    Req#mod.request_uri.

peer_ip(Req) -> 
    Socket = Req#mod.socket,
    {ok, {IP, _Port}} =
        case Socket of
            S when is_tuple(S), 
                   element(1, S) =:= sslsocket -> 
                ssl:peername(Socket);
            _ -> 
                inet:peername(Socket)
        end,
    IP.

peer_port(Req) -> 
    Socket = Req#mod.socket,
    {ok, {_IP, Port}} =
        case Socket of
            S when is_tuple(S), 
                   element(1, S) =:= sslsocket ->
                ssl:peername(Socket);
            _ -> 
                inet:peername(Socket)
        end,
    Port.

native_header_type() ->
    list.

headers(Req) ->
    Req#mod.parsed_header.

cookies(Req) ->
    Headers = Req#mod.parsed_header,
    CookieData = proplists:get_value("cookie", Headers, ""),
    simple_bridge_util:parse_cookie_header(CookieData).

query_params(Req) ->
    {_Path, QueryString} = split_request_uri(Req#mod.request_uri, []),
    Query = handle_parse_qs(QueryString, ?PARSE_QS(QueryString)),
    [{Key, Value} || {Key, Value} <- Query, Key /= []].

post_params(Req) ->
    Body = request_body(Req),
    Query = handle_parse_qs(Body, ?PARSE_QS(Body)),
    [{Key, Value} || {Key, Value} <- Query, Key /= []].

handle_parse_qs(_QS, Err={error, _, _}) ->
    error_logger:warning_msg("Unable to parse QueryString for Inets. ~nReason: ~p~n",[Err]),
    [];
handle_parse_qs(_QS, Res) ->
    Res.

request_body(Req) ->
    Req#mod.entity_body.

socket(Req) -> 
    Req#mod.socket.

recv_from_socket(Length, Timeout, Req) -> 
    Socket = socket(Req),
    case gen_tcp:recv(Socket, Length, Timeout) of
        {ok, Data} -> Data;
        _ -> exit(normal)
    end.

protocol_version(Req) ->
  case Req#mod.http_version of
    "HTTP/0.9" -> {0, 9};
    "HTTP/1.0" -> {1, 0};
    "HTTP/1.1" -> {1, 1}
  end.

%%% PRIVATE FUNCTIONS %%%
split_request_uri([], Path) -> {lists:reverse(Path), ""};
split_request_uri([$?|QueryString], Path) -> {lists:reverse(Path), QueryString};
split_request_uri([H|T], Path) -> split_request_uri(T,[H|Path]).

build_response(Req, Res) -> 
    ResponseCode = Res#response.status_code,
    case Res#response.data of
        {data, Data} ->
            Size = integer_to_list(erlang:iolist_size(Data)),

            % Assemble headers...
            Headers = lists:flatten([
                {code, ResponseCode},
                {content_length, Size},
                [{massage(X#header.name), X#header.value} || X <- Res#response.headers],
                [create_cookie_header(X) || X <- Res#response.cookies]
            ]),     

            % Send the inets response...
            {break,[
                {response, {response, Headers, Data}}
            ]};

        {file, _Path} ->
            mod_get:do(Req)
    end.

create_cookie_header(Cookie = #cookie{}) ->
    {K, V} = simple_bridge_util:create_cookie_header(Cookie),
    {binary_to_list(K), V}.


% Inets wants some headers as lowercase atoms, and the rest as lists. So let's fix these up.
massage(Header) when is_binary(Header) ->
    massage(binary_to_list(Header));
massage(Header) ->
    X = simple_bridge_util:atomize_header(Header),
    case lists:member(X, special_headers()) of
        true  -> X;
        false -> Header
    end.

special_headers() ->
    [accept_ranges , allow , cache_control , content_MD5 , content_encoding , 
     content_language , content_length , content_location , content_range , 
     content_type , date , etag , expires , last_modified , location , 
     pragma , retry_after , server , trailer , transfer_encoding].