PageRenderTime 184ms CodeModel.GetById 90ms app.highlight 18ms RepoModel.GetById 73ms app.codeStats 0ms

/src/middleware/ewgi_post/ewgi_post.erl

http://github.com/skarab/ewgi
Erlang | 171 lines | 130 code | 15 blank | 26 comment | 2 complexity | 07b73cdc4a10b79e0e06c4e608290493 MD5 | raw file
  1%% @author Filippo Pacini <filippo.pacini@gmail.com>
  2%% @copyright 2009 S.G. Consulting.
  3
  4%% @doc Post example
  5
  6-module(ewgi_post).
  7-author('Filippo Pacini <filippo.pacini@gmail.com>').
  8
  9-export([run/2]).
 10-export([post_app_example/1]).
 11
 12-define(DEFAULT_MAX_LENGTH, 2097152). %% 2 MB maximum
 13
 14run(Ctx, [NoPostApp, FailedPostApp, SuccessPostApp]) ->
 15    run(Ctx, [NoPostApp, FailedPostApp, SuccessPostApp, ?DEFAULT_MAX_LENGTH]);
 16run(Ctx, [NoPostApp, FailedPostApp, SuccessPostApp, MaxLength]) ->
 17    Parser = post_parse_middleware(MaxLength, SuccessPostApp, FailedPostApp),
 18    case ewgi_api:request_method(Ctx) of
 19	'GET' ->
 20	    NoPostApp(Ctx);
 21	'POST' ->
 22	    Parser(Ctx)
 23    end.
 24
 25%% MaxLength is the maximum size (in bytes) that the server will
 26%% receive from the client.  App should be the application called when
 27%% the parse is successful (or unnecessary).  ErrApp should be an
 28%% error application when the content length exceeds the maximum
 29%% specified limit.
 30post_parse_middleware(MaxLength, App, ErrApp)
 31  when is_integer(MaxLength), MaxLength > 0, is_function(App, 1) ->
 32    fun(Ctx) ->
 33            case ewgi_api:request_method(Ctx) of
 34                Method when Method =:= 'POST';
 35                            Method =:= 'PUT' ->
 36                    case ewgi_api:remote_user_data(Ctx) of
 37                        undefined ->
 38                            %% Check content-type first
 39                            Ct = ewgi_api:content_type(Ctx),
 40                            parse_post(Ctx, App, ErrApp, parse_ct(Ct), MaxLength);
 41                        _ ->
 42                            App(Ctx)
 43                    end;
 44                _ ->
 45                    App(Ctx)
 46            end
 47    end.
 48
 49%% Parse content-type (ignoring additional vars for now)
 50%% Should look like "major/minor; var=val"
 51parse_ct(L) when is_list(L) ->
 52    case string:tokens(L, ";") of
 53        [CT|Vars] ->
 54	    Vars1 = [string:tokens(VarStr, "=") || VarStr <- Vars],
 55	    Vars2 = [{string:strip(Name), Value} || [Name, Value] <- Vars1],
 56            {CT, Vars2};
 57        _ ->
 58            undefined
 59    end.
 60
 61parse_post(Ctx, App, ErrApp, {"application/x-www-form-urlencoded", Vars}, Max) ->
 62    case ewgi_api:content_length(Ctx) of
 63        L when is_integer(L), L > Max ->
 64	    %% shouldn't we set an error message here?
 65            ErrApp(Ctx);
 66        L when is_integer(L), L > 0 ->
 67            Input = read_input_string(Ctx, L),
 68	    %% http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html
 69	    %% When no explicit charset parameter is provided by the sender,
 70	    %% media subtypes of the "text" type are defined to have a default
 71	    %% charset value of "ISO-8859-1" when received via HTTP.
 72	    case proplists:get_value("charset", Vars) of
 73		undefined -> InCharset = "iso-8859-1"
 74				 ;Charset -> InCharset = string:to_lower(Charset)
 75	    end,
 76	    UnicodeInput = to_unicode(Input, InCharset),
 77            Vals = ewgi_api:parse_post(UnicodeInput),
 78            Ctx1 = ewgi_api:remote_user_data(Vals, Ctx),
 79            App(Ctx1);
 80        _ ->
 81            ErrApp(Ctx)
 82    end;
 83parse_post(Ctx, App, ErrApp, {"application/json", _Vars}, Max) ->
 84    case ewgi_api:content_length(Ctx) of
 85        L when is_integer(L), L > Max ->
 86	    %% shouldn't we set an error message here?
 87            ErrApp(Ctx);
 88        L when is_integer(L), L > 0 ->
 89	    Input = read_input_string(Ctx, L),
 90	    %% http://www.ietf.org/rfc/rfc4627.txt
 91	    %% JSON text SHALL be encoded in Unicode.
 92	    %% The default encoding is UTF-8.
 93	    case unicode:bom_to_encoding(Input) of
 94		{latin1,0} -> InEncoding = utf8
 95				  ;{InEncoding, _Length} -> ok
 96	    end,
 97	    UnicodeInput = unicode:characters_to_list(Input, InEncoding),
 98            {Json, [], _} = ktj_decode:decode(UnicodeInput),
 99	    Vals = [{"json", Json}],
100            Ctx1 = ewgi_api:remote_user_data(Vals, Ctx),
101            App(Ctx1);
102        _ ->
103            ErrApp(Ctx)
104    end;
105parse_post(Ctx, App, _, _, _) ->
106    %% Silently ignore other content-types
107    App(Ctx).
108
109read_input_string(Ctx, L) when is_integer(L), L > 0 ->
110    R = ewgi_api:read_input(Ctx),
111    iolist_to_binary(R(read_input_string_cb([]), L)).
112
113read_input_string_cb(Acc) ->
114    fun(eof) ->
115            lists:reverse(Acc);
116       ({data, B}) ->
117            read_input_string_cb([B|Acc])
118    end.
119
120%% Transforms the data from the given charset to unicode
121%% Todo: add support for other charset as needed.
122to_unicode(Data, "iso-8859-1") ->
123    unicode:characters_to_list(Data, latin1);
124to_unicode(Data, "utf8") ->
125    unicode:characters_to_list(Data, utf8);
126to_unicode(Data, "utf-8") ->
127    unicode:characters_to_list(Data, utf8).
128
129%% 
130%% example functions on how to use the post handling middleware
131%%
132post_app_example(Ctx) ->
133    run(Ctx, [fun display_form/1,
134	      fun post_app_error/1,
135	      fun display_form_data/1]).
136
137post_app_error({ewgi_context, Request, _}) ->
138    Response = {ewgi_response, {400, "BAD REQUEST"}, [],
139                [<<"Maximum content-length exceeded.">>],
140                undefined},
141    {ewgi_context, Request, Response}.
142
143display_form_data({ewgi_context, Request, _Response}=Ctx) ->
144    Body = 
145	case ewgi_api:remote_user_data(Ctx) of
146	    undefined ->
147		"undefined";
148	    Body1 ->
149		io_lib:format("~p", [Body1])
150	end,
151    ResponseHeaders = [{"Content-type", "text/html; charset=utf8"}],
152    Response = {ewgi_response, 
153                {200, "OK"}, 
154                ResponseHeaders,
155                [Body], undefined},
156    {ewgi_context, Request, Response}.
157
158display_form({ewgi_context, Request, _Response}) ->
159    Body = <<"<form action=\"/postex\" method=\"post\">
160Un: <input type=\"text\" name=\"un\" value=\"\"/>
161<br/>
162	    Pw: <input type=\"text\" name=\"pw\" value=\"\"/>
163<br/><br/>
164	    <input type=\"submit\" name=\"submit\" value=\"Login\"/>
165</form>">>,
166    ResponseHeaders = [{"Content-type", "text/html"}],
167	    Response = {ewgi_response, 
168			{200, "OK"}, 
169			ResponseHeaders,
170			[Body], undefined},
171	    {ewgi_context, Request, Response}.