/src/middleware/ewgi_post/ewgi_post.erl
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}.