/src/ewgi_api.erl
Erlang | 918 lines | 611 code | 142 blank | 165 comment | 4 complexity | 2b384630705aef0436e47e78fd7cb050 MD5 | raw file
1%%%------------------------------------------------------------------- 2%%% File : ewgi_api.erl 3%%% Authors : Filippo Pacini <filippo.pacini@gmail.com> 4%%% Hunter Morris <huntermorris@gmail.com> 5%%% License : 6%%% The contents of this file are subject to the Mozilla Public 7%%% License Version 1.1 (the "License"); you may not use this file 8%%% except in compliance with the License. You may obtain a copy of 9%%% the License at http://www.mozilla.org/MPL/ 10%%% 11%%% Software distributed under the License is distributed on an "AS IS" 12%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 13%%% the License for the specific language governing rights and 14%%% limitations under the License. 15%%% The Initial Developer of the Original Code is S.G. Consulting 16%%% srl. Portions created by S.G. Consulting s.r.l. are Copyright (C) 17%%% 2007 S.G. Consulting srl. All Rights Reserved. 18%%% 19%%% @doc 20%%% <p>ewgi API. Defines a low level CGI like API.</p> 21%%% 22%%% @end 23%%% 24%%% Created : 10 Oct 2007 by Filippo Pacini <filippo.pacini@gmail.com> 25%%%------------------------------------------------------------------- 26-module(ewgi_api). 27 28-include_lib("ewgi.hrl"). 29 30%% Record helpers 31-export([empty_request/0, empty_response/0, context/2, request/2, response/2, 32 request/1, response/1]). 33 34-export([response_headers/1, response_message_body/1, response_status/1, 35 response_error/1]). 36 37-export([response_headers/2, response_message_body/2, response_status/2, 38 response_error/2]). 39 40%% Request 'get' methods 41-export([auth_type/1, content_length/1, content_type/1, gateway_interface/1, 42 path_info/1, path_translated/1, query_string/1, remote_addr/1, 43 remote_host/1, remote_ident/1, remote_user/1, remote_user_data/1, 44 request_method/1, script_name/1, server_name/1, server_port/1, 45 server_protocol/1, server_software/1]). 46 47%% Request 'set' methods 48-export([auth_type/2, content_length/2, content_type/2, gateway_interface/2, 49 path_info/2, path_translated/2, query_string/2, remote_addr/2, 50 remote_host/2, remote_ident/2, remote_user/2, remote_user_data/2, 51 request_method/2, script_name/2, server_name/2, server_port/2, 52 server_protocol/2, server_software/2]). 53 54%% Additional request methods 55-export([get_header_value/2, set_header/3, insert_header/3, 56 get_all_headers/1, read_input/1, read_input/3, read_input_string/2, 57 write_error/2, url_scheme/1, version/1, get_all_data/1, find_data/2, 58 find_data/3, store_data/3]). 59 60%% Server methods 61-export([server_request_foldl/4]). 62 63%% Utility methods 64-export([parse_qs/1, parse_post/1, urlencode/1, quote/1, normalize_header/1, 65 unquote_path/1, path_components/3, urlsplit/1]). 66 67%% Stream methods 68-export([ 69 stream_process_init/2, 70 stream_process_init/3, 71 stream_process_deliver/2, 72 stream_process_deliver_chunk/2, 73 stream_process_deliver_final_chunk/2, 74 stream_process_end/1 75 ]). 76 77%%==================================================================== 78%% API 79%%==================================================================== 80-spec empty_request() -> ewgi_request(). 81empty_request() -> 82 {'ewgi_request', undefined, undefined, undefined, 83 empty_ewgi_spec(), undefined, empty_http_headers(), undefined, 84 undefined, undefined, undefined, undefined, undefined, undefined, 85 undefined, undefined, undefined, undefined, undefined, undefined, 86 undefined}. 87 88-spec empty_response() -> ewgi_response(). 89empty_response() -> 90 {'ewgi_response', undefined, [], [], undefined}. 91 92-spec context(ewgi_request(), ewgi_response()) -> ewgi_context(). 93context(Request, Response) when ?IS_EWGI_REQUEST(Request), 94 ?IS_EWGI_RESPONSE(Response) -> 95 {'ewgi_context', Request, Response}. 96 97-spec request(ewgi_request(), ewgi_context()) -> ewgi_context(). 98request(Req, Ctx) when ?IS_EWGI_REQUEST(Req), ?IS_EWGI_CONTEXT(Ctx) -> 99 ?SET_EWGI_REQUEST(Req, Ctx). 100 101-spec response(ewgi_response(), ewgi_context()) -> ewgi_context(). 102response(Rsp, Ctx) when ?IS_EWGI_RESPONSE(Rsp), 103 ?IS_EWGI_CONTEXT(Ctx) -> 104 ?SET_EWGI_RESPONSE(Rsp, Ctx). 105 106-spec response(ewgi_context()) -> ewgi_response(). 107response(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 108 ?GET_EWGI_RESPONSE(Ctx). 109 110-spec request(ewgi_context()) -> ewgi_request(). 111request(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 112 ?GET_EWGI_REQUEST(Ctx). 113 114-spec response_headers(ewgi_context()) -> ewgi_header_list(). 115response_headers(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 116 ?GET_RESPONSE_HEADERS(response(Ctx)). 117 118-spec response_status(ewgi_context()) -> ewgi_status(). 119response_status(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 120 ?GET_RESPONSE_STATUS(response(Ctx)). 121 122-spec response_message_body(ewgi_context()) -> ewgi_message_body(). 123response_message_body(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 124 ?GET_RESPONSE_MESSAGE_BODY(response(Ctx)). 125 126-spec response_error(ewgi_context()) -> any(). 127response_error(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 128 ?GET_RESPONSE_ERROR(response(Ctx)). 129 130-spec response_headers(ewgi_header_list(), ewgi_context()) -> ewgi_context(). 131response_headers(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 132 response(?SET_RESPONSE_HEADERS(V, response(Ctx)), Ctx). 133 134-spec response_status(ewgi_status(), ewgi_context()) -> ewgi_context(). 135response_status(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 136 response(?SET_RESPONSE_STATUS(V, response(Ctx)), Ctx). 137 138-spec response_message_body(ewgi_message_body(), ewgi_context()) -> ewgi_context(). 139response_message_body(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 140 response(?SET_RESPONSE_MESSAGE_BODY(V, response(Ctx)), Ctx). 141 142-spec response_error(any(), ewgi_context()) -> ewgi_context(). 143response_error(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 144 response(?SET_RESPONSE_ERROR(V, response(Ctx)), Ctx). 145 146-spec auth_type(ewgi_context()) -> ewgi_val(). 147auth_type(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 148 ?GET_AUTH_TYPE(request(Ctx)). 149 150-spec content_length(ewgi_context()) -> non_neg_integer(). 151content_length(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 152 ?GET_CONTENT_LENGTH(request(Ctx)). 153 154-spec content_type(ewgi_context()) -> ewgi_val(). 155content_type(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 156 ?GET_CONTENT_TYPE(request(Ctx)). 157 158-spec gateway_interface(ewgi_context()) -> ewgi_val(). 159gateway_interface(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 160 ?GET_GATEWAY_INTERFACE(request(Ctx)). 161 162-spec path_info(ewgi_context()) -> ewgi_val(). 163path_info(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 164 ?GET_PATH_INFO(request(Ctx)). 165 166-spec path_translated(ewgi_context()) -> ewgi_val(). 167path_translated(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 168 ?GET_PATH_TRANSLATED(request(Ctx)). 169 170-spec query_string(ewgi_context()) -> ewgi_val(). 171query_string(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 172 ?GET_QUERY_STRING(request(Ctx)). 173 174-spec remote_addr(ewgi_context()) -> ewgi_val(). 175remote_addr(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 176 ?GET_REMOTE_ADDR(request(Ctx)). 177 178-spec remote_host(ewgi_context()) -> ewgi_val(). 179remote_host(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 180 ?GET_REMOTE_HOST(request(Ctx)). 181 182-spec remote_ident(ewgi_context()) -> ewgi_val(). 183remote_ident(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 184 ?GET_REMOTE_IDENT(request(Ctx)). 185 186-spec remote_user(ewgi_context()) -> ewgi_val(). 187remote_user(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 188 ?GET_REMOTE_USER(request(Ctx)). 189 190-spec remote_user_data(ewgi_context()) -> ewgi_val(). 191remote_user_data(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 192 ?GET_REMOTE_USER_DATA(request(Ctx)). 193 194-spec request_method(ewgi_context()) -> ewgi_request_method(). 195request_method(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 196 ?GET_REQUEST_METHOD(request(Ctx)). 197 198-spec script_name(ewgi_context()) -> ewgi_val(). 199script_name(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 200 ?GET_SCRIPT_NAME(request(Ctx)). 201 202-spec server_name(ewgi_context()) -> ewgi_val(). 203server_name(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 204 ?GET_SERVER_NAME(request(Ctx)). 205 206-spec server_port(ewgi_context()) -> ewgi_val(). 207server_port(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 208 ?GET_SERVER_PORT(request(Ctx)). 209 210-spec server_protocol(ewgi_context()) -> ewgi_val(). 211server_protocol(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 212 ?GET_SERVER_PROTOCOL(request(Ctx)). 213 214-spec server_software(ewgi_context()) -> ewgi_val(). 215server_software(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 216 ?GET_SERVER_SOFTWARE(request(Ctx)). 217 218-spec headers(ewgi_context()) -> ewgi_http_headers(). 219headers(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 220 ?GET_HTTP_HEADERS(request(Ctx)). 221 222-spec headers(ewgi_http_headers(), ewgi_context()) -> ewgi_context(). 223headers(H, Ctx) when ?IS_HTTP_HEADERS(H), ?IS_EWGI_CONTEXT(Ctx) -> 224 request(?SET_HTTP_HEADERS(H, request(Ctx)), Ctx). 225 226-spec get_header_value(string(), ewgi_context()) -> ewgi_header_val(). 227get_header_value(Hdr0, Ctx) when is_list(Hdr0), ?IS_EWGI_CONTEXT(Ctx) -> 228 Hdr = string:to_lower(Hdr0), 229 get_header1(Hdr, Ctx). 230 231get_header1("accept", Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 232 ?GET_HTTP_ACCEPT(headers(Ctx)); 233get_header1("cookie", Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 234 ?GET_HTTP_COOKIE(headers(Ctx)); 235get_header1("host", Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 236 ?GET_HTTP_HOST(headers(Ctx)); 237get_header1("if-modified-since", Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 238 ?GET_HTTP_IF_MODIFIED_SINCE(headers(Ctx)); 239get_header1("user-agent", Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 240 ?GET_HTTP_USER_AGENT(headers(Ctx)); 241get_header1("x-http-method-override", Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 242 ?GET_HTTP_X_HTTP_METHOD_OVERRIDE(headers(Ctx)); 243get_header1(Hdr, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 244 case gb_trees:lookup(Hdr, ?GET_HTTP_OTHER(headers(Ctx))) of 245 {value, V} -> 246 unzip_header_value(V); 247 none -> 248 undefined 249 end. 250 251unzip_header_value([{_,_}|_]=V) -> 252 {_, V1} = lists:unzip(V), 253 string:join(V1, ", "); 254unzip_header_value(V) -> 255 V. 256 257insert_header(K0, V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 258 K = string:to_lower(K0), 259 insert_header1(K, K0, V, Ctx). 260 261combine_headers(K, V, Ctx) -> 262 case get_header1(K, Ctx) of 263 undefined -> 264 V; 265 S when is_list(S) -> 266 string:join([S, V], ", ") 267 end. 268 269insert_header1("accept"=K, _, V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 270 V1 = combine_headers(K, V, Ctx), 271 headers(?SET_HTTP_ACCEPT(V1, headers(Ctx)), Ctx); 272insert_header1("cookie"=K, _, V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 273 V1 = combine_headers(K, V, Ctx), 274 headers(?SET_HTTP_COOKIE(V1, headers(Ctx)), Ctx); 275insert_header1("host"=K, _, V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 276 V1 = combine_headers(K, V, Ctx), 277 headers(?SET_HTTP_HOST(V1, headers(Ctx)), Ctx); 278insert_header1("if-modified-since"=K, _, V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 279 V1 = combine_headers(K, V, Ctx), 280 headers(?SET_HTTP_IF_MODIFIED_SINCE(V1, headers(Ctx)), Ctx); 281insert_header1("user-agent"=K, _, V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 282 V1 = combine_headers(K, V, Ctx), 283 headers(?SET_HTTP_USER_AGENT(V1, headers(Ctx)), Ctx); 284insert_header1("x-http-method-override"=K, _, V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 285 V1 = combine_headers(K, V, Ctx), 286 headers(?SET_HTTP_X_HTTP_METHOD_OVERRIDE(V1, headers(Ctx)), Ctx); 287insert_header1(K, K0, V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 288 D = t_insert_header(K, {K0, V}, ?GET_HTTP_OTHER(headers(Ctx))), 289 headers(?SET_HTTP_OTHER(D, headers(Ctx)), Ctx). 290 291set_header(K0, V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 292 K = string:to_lower(K0), 293 set_header1(K, K0, V, Ctx). 294 295set_header1("accept", _, V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 296 headers(?SET_HTTP_ACCEPT(V, headers(Ctx)), Ctx); 297set_header1("cookie", _, V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 298 headers(?SET_HTTP_COOKIE(V, headers(Ctx)), Ctx); 299set_header1("host", _, V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 300 headers(?SET_HTTP_HOST(V, headers(Ctx)), Ctx); 301set_header1("if-modified-since", _, V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 302 headers(?SET_HTTP_IF_MODIFIED_SINCE(V, headers(Ctx)), Ctx); 303set_header1("user-agent", _, V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 304 headers(?SET_HTTP_USER_AGENT(V, headers(Ctx)), Ctx); 305set_header1("x-http-method-override", _, V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 306 headers(?SET_HTTP_X_HTTP_METHOD_OVERRIDE(V, headers(Ctx)), Ctx); 307set_header1(K, K0, V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 308 D = t_enter_header(K, {K0, V}, ?GET_HTTP_OTHER(headers(Ctx))), 309 headers(?SET_HTTP_OTHER(D, headers(Ctx)), Ctx). 310 311auth_type(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 312 request(?SET_AUTH_TYPE(V, request(Ctx)), Ctx). 313 314content_length(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 315 request(?SET_CONTENT_LENGTH(V, request(Ctx)), Ctx). 316 317content_type(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 318 request(?SET_CONTENT_TYPE(V, request(Ctx)), Ctx). 319 320gateway_interface(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 321 request(?SET_GATEWAY_INTERFACE(V, request(Ctx)), Ctx). 322 323path_info(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 324 request(?SET_PATH_INFO(V, request(Ctx)), Ctx). 325 326path_translated(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 327 request(?SET_PATH_TRANSLATED(V, request(Ctx)), Ctx). 328 329query_string(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 330 request(?SET_QUERY_STRING(V, request(Ctx)), Ctx). 331 332remote_addr(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 333 request(?SET_REMOTE_ADDR(V, request(Ctx)), Ctx). 334 335remote_host(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 336 request(?SET_REMOTE_HOST(V, request(Ctx)), Ctx). 337 338remote_ident(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 339 request(?SET_REMOTE_IDENT(V, request(Ctx)), Ctx). 340 341remote_user(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 342 request(?SET_REMOTE_USER(V, request(Ctx)), Ctx). 343 344remote_user_data(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 345 request(?SET_REMOTE_USER_DATA(V, request(Ctx)), Ctx). 346 347request_method(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 348 request(?SET_REQUEST_METHOD(V, request(Ctx)), Ctx). 349 350script_name(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 351 request(?SET_SCRIPT_NAME(V, request(Ctx)), Ctx). 352 353server_name(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 354 request(?SET_SERVER_NAME(V, request(Ctx)), Ctx). 355 356server_port(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 357 request(?SET_SERVER_PORT(V, request(Ctx)), Ctx). 358 359server_protocol(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 360 request(?SET_SERVER_PROTOCOL(V, request(Ctx)), Ctx). 361 362server_software(V, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 363 request(?SET_SERVER_SOFTWARE(V, request(Ctx)), Ctx). 364 365get_all_headers(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 366 H = headers(Ctx), 367 Other = gb_trees:to_list(?GET_HTTP_OTHER(H)), 368 Acc = [{K, unzip_header_value(V)} || {K, V} <- Other], 369 L = [{"accept", get_header_value("accept", Ctx)}, 370 {"cookie", get_header_value("cookie", Ctx)}, 371 {"host", get_header_value("host", Ctx)}, 372 {"if-modified-since", get_header_value("if-modified-since", Ctx)}, 373 {"user-agent", get_header_value("user-agent", Ctx)}, 374 {"x-http-method-override", get_header_value("x-http-method-override", Ctx)}|Acc], 375 lists:filter(fun({_, undefined}) -> false; (_) -> true end, L). 376 377-spec ewgi_spec(ewgi_context()) -> ewgi_spec(). 378ewgi_spec(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 379 ?GET_EWGI(request(Ctx)). 380 381-spec ewgi_spec(ewgi_spec(), ewgi_context()) -> ewgi_context(). 382ewgi_spec(E, Ctx) when ?IS_EWGI_SPEC(E), ?IS_EWGI_CONTEXT(Ctx) -> 383 request(?SET_EWGI(E, request(Ctx)), Ctx). 384 385-spec read_input(ewgi_context()) -> ewgi_ri_callback() | 'undefined'. 386read_input(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 387 ?GET_EWGI_READ_INPUT(ewgi_spec(Ctx)). 388 389-spec read_input(ewgi_ri_callback(), non_neg_integer(), ewgi_context()) -> ewgi_ri_callback() | 'undefined'. 390read_input(Callback, Length, Ctx) when ?IS_EWGI_CONTEXT(Ctx), 391 is_function(Callback, 1), 392 is_integer(Length), 393 Length >= 0 -> 394 case read_input(Ctx) of 395 F when is_function(F, 2) -> 396 F(Callback, Length); 397 undefined -> 398 undefined 399 end. 400 401%% @spec read_input_string(non_neg_integer(), ewgi_context()) -> string() | {error, no_input} 402%% @doc Reads the client message body into a string from the EWGI context. 403-spec read_input_string(non_neg_integer(), ewgi_context()) -> [byte()] | {'error', 'no_input'}. 404read_input_string(L, Ctx) when is_integer(L), L >= 0, ?IS_EWGI_CONTEXT(Ctx) -> 405 case read_input(read_input_string_cb([]), L, Ctx) of 406 undefined -> 407 {error, no_input}; 408 Iol -> 409 Bin = iolist_to_binary(Iol), 410 binary_to_list(Bin) 411 end. 412 413-spec read_input_string_cb(list()) -> ewgi_ri_callback(). 414read_input_string_cb(Acc) -> 415 F = fun(eof) -> 416 lists:reverse(Acc); 417 ({data, B}) -> 418 read_input_string_cb([B|Acc]) 419 end, 420 F. 421 422write_error(Msg, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 423 F = ?GET_EWGI_WRITE_ERROR(ewgi_spec(Ctx)), 424 F(Msg). 425 426url_scheme(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 427 ?GET_EWGI_URL_SCHEME(ewgi_spec(Ctx)). 428 429version(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 430 ?GET_EWGI_VERSION(ewgi_spec(Ctx)). 431 432get_all_data(Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 433 ?GET_EWGI_DATA(ewgi_spec(Ctx)). 434 435find_data(Key, Ctx) -> 436 find_data(Key, Ctx, undefined). 437 438find_data(Key, Ctx, Default) when ?IS_EWGI_CONTEXT(Ctx) -> 439 case gb_trees:lookup(Key, get_all_data(Ctx)) of 440 {value, V} -> 441 V; 442 none -> 443 Default 444 end. 445 446store_data(Key, Val, Ctx) when ?IS_EWGI_CONTEXT(Ctx) -> 447 D = gb_trees:enter(Key, Val, get_all_data(Ctx)), 448 ewgi_spec(?SET_EWGI_DATA(D, ewgi_spec(Ctx)), Ctx). 449 450%%-------------------------------------------------------------------- 451%% @spec parse_qs(string()|binary()) -> [proplist()] 452%% 453%% @doc Parse a query string. Calls parse_data to do the job. 454%% @end 455%%-------------------------------------------------------------------- 456parse_qs(ToParse) -> 457 parse_data(ToParse, "ISO-8859-1"). 458 459%%-------------------------------------------------------------------- 460%% @spec parse_post(string()|binary()) -> [proplist()] 461%% 462%% @doc Parse application/x-www-form-urlencoded data. 463%% Calls parse_data to do the job. 464%% @end 465%%-------------------------------------------------------------------- 466parse_post(ToParse) -> 467 parse_data(ToParse). 468 469%%-------------------------------------------------------------------- 470%% @spec parse_data(string()|binary()) -> [proplist()] 471%% 472%% @doc Parse a query string or application/x-www-form-urlencoded data. 473%% @end 474%%-------------------------------------------------------------------- 475parse_data(undefined) -> 476 []; 477parse_data(Binary) when is_binary(Binary) -> 478 parse_data(binary_to_list(Binary), []); 479parse_data(String) -> 480 parse_data(String, []). 481 482parse_data([], Acc) -> 483 lists:reverse(Acc); 484parse_data(String, Acc) -> 485 {{Key, Val}, Rest} = parse_kv(String), 486 parse_data(Rest, [{Key, Val} | Acc]). 487 488 489%%-------------------------------------------------------------------- 490%% @spec urlencode(proplist()) -> string() 491%% 492%% @doc URL encodes a proplist of parameters. 493%% @end 494%%-------------------------------------------------------------------- 495-spec urlencode(ewgi_proplist()) -> string(). 496urlencode(Props) -> 497 QuotedL = [[quote(K), $=, quote(V)] || {K, V} <- Props], 498 lists:flatten(join(QuotedL, $&)). 499 500%%-------------------------------------------------------------------- 501%% @spec quote(term()) -> string() 502%% 503%% @doc URL encodes the given term. 504%% @end 505%%-------------------------------------------------------------------- 506-spec quote(ewgi_propval()) -> string(). 507quote(Term) when is_atom(Term) -> 508 quote(atom_to_list(Term)); 509quote(Term) when is_integer(Term) -> 510 quote(integer_to_list(Term)); 511quote(Term) when is_binary(Term) -> 512 quote(binary_to_list(Term)); 513quote(Term) when is_list(Term) -> 514 quote(Term, []). 515 516-spec quote(string(), string()) -> string(). 517quote([], Acc) -> 518 lists:reverse(Acc); 519%% low alpha chars 520quote([H|Rest], Acc) when H >= $a, H =< $z -> 521 quote(Rest, [H|Acc]); 522%% hialpha chars 523quote([H|Rest], Acc) when H >= $A, H =< $Z -> 524 quote(Rest, [H|Acc]); 525%% digit chars 526quote([H|Rest], Acc) when H >= $0, H =< $9 -> 527 quote(Rest, [H|Acc]); 528%% safe chars 529quote([H|Rest], Acc) when H =:= $-; H=:=$.; H=:=$_; H=:=$~ -> 530 quote(Rest, [H|Acc]); 531%% space 532quote([$\s|Rest], Acc) -> 533 quote(Rest, [$+ | Acc]); 534%% other characters (convert to hex) 535quote([H|Rest], Acc) -> 536 <<Hi:4, Lo:4>> = <<H>>, 537 quote(Rest, [to_hex(Lo), to_hex(Hi), $\% | Acc]). 538 539 540%%==================================================================== 541%% Internal functions 542%%==================================================================== 543%%-------------------------------------------------------------------- 544%% @spec parse_kv(String::string()) -> parsed()|{error, Reason} 545%% 546%% @type parsed() = {ok, proplist(), Rest::string()} 547%% 548%% @doc Parser for kv pairs found in query strings and body data. 549%% returns the first proplist parsed from String or an error. 550%% @end 551%%-------------------------------------------------------------------- 552-spec parse_kv(string()) -> {{string(), string()}, string()}. 553parse_kv(String) -> 554 P = and_parser([until(fun is_equal/1), until(fun is_amp/1)]), 555 {ok, [K, V], Rest} = P(String), 556 {{unquote(K), unquote(V)}, Rest}. 557 558%%-------------------------------------------------------------------- 559%% @spec and_parser(Rules::rules()) -> parsed()|{error, Reason} 560%% 561%% @type rules() = [rule()] 562%% rule() = function(template()). 563%% 564%% @doc and_parser of Rules. 565%% Applies each Rule in sequence to the Template passed. 566%% If a rule fails returns an error. 567%% @end 568%%-------------------------------------------------------------------- 569-type parser() :: fun((list()) -> {'ok', list(), list()}). 570-spec and_parser([parser()]) -> parser(). 571and_parser(Rules) -> 572 fun(Tmpl) -> 573 and_parser(Rules, Tmpl, []) 574 end. 575 576-spec and_parser(list(), list(), list()) -> {'ok', list(), list()}. 577and_parser([], Tmpl, SoFar) -> 578 {ok, lists:reverse(SoFar), Tmpl}; 579and_parser([Rule|T], Tmpl, SoFar) -> 580 {ok, Tok, Rest} = Rule(Tmpl), 581 and_parser(T, Rest, [Tok|SoFar]). 582 583%%-------------------------------------------------------------------- 584%% @spec until(predicate()) -> parsed()|{error, Reason} 585%% 586%% @type predicate() = function(template()). 587%% 588%% @doc until predicate P: 589%% output what it gets until P(H) is true. 590%% @end 591%%-------------------------------------------------------------------- 592-type predicate() :: fun((list()) -> {'true', list()} | 'false'). 593-spec until(predicate()) -> parser(). 594until(P) -> 595 fun (Tmpl) -> until(P, Tmpl, []) end. 596 597-spec until(predicate(), list(), list()) -> {'ok', list(), list()}. 598until(_P, [], Parsed) -> %% end of string so end parsing 599 {ok, lists:reverse(Parsed), []}; 600until(P, String, Parsed) -> 601 case P(String) of 602 {true, Rest} -> 603 {ok, lists:reverse(Parsed), Rest}; 604 _ -> 605 [H|Rest] = String, 606 until(P, Rest, [H|Parsed]) 607 end. 608 609%%-------------------------------------------------------------------- 610%% @spec is_equal(string()) -> boolean() 611%% 612%% @doc Match = character at the head of string. 613%% @end 614%%-------------------------------------------------------------------- 615-spec is_equal(string()) -> boolean(). 616is_equal([$=|Rest]) -> 617 {true, Rest}; 618is_equal(_) -> 619 false. 620 621%%-------------------------------------------------------------------- 622%% @spec is_amp(string()) -> boolean() 623%% 624%% @doc Match & character or &amp; entity at the beginning of string. 625%% @end 626%%-------------------------------------------------------------------- 627-spec is_amp(string()) -> boolean(). 628is_amp("&"++Rest) -> 629 {true, Rest}; 630is_amp([$&|Rest]) -> 631 {true, Rest}; 632is_amp(_) -> 633 false. 634 635 636%%-------------------------------------------------------------------- 637%% @spec unquote(string()) -> string() 638%% 639%% @doc URL decodes the given term. 640%% Used to parse query strings and application/x-www-form-urlencoded data. 641%% @end 642%%-------------------------------------------------------------------- 643unquote(Val) when is_binary(Val) -> 644 unquote(binary_to_list(Val), []); 645unquote(Val) -> 646 unquote(Val, []). 647 648unquote([], Acc) -> 649 lists:reverse(Acc); 650unquote([37, Hi, Lo|Rest], Acc) -> % match %Hex 651 unquote(Rest, [(from_hex(Lo) bor (from_hex(Hi) bsl 4))|Acc]); 652unquote([$+|Rest], Acc) -> 653 unquote(Rest, [$\s|Acc]); 654unquote([H|Rest], Acc) -> 655 unquote(Rest, [H|Acc]). 656 657%%-------------------------------------------------------------------- 658%% @spec to_hex(char()) -> hex() 659%% 660%% @doc convert char to hex code. 661%% @end 662%%-------------------------------------------------------------------- 663-spec to_hex(0..16) -> byte(). 664to_hex(C) when C >= 0, C < 10 -> $0 + C; 665to_hex(C) when C >= 0, C < 16 -> $A + (C - 10). 666 667%%-------------------------------------------------------------------- 668%% @spec from_hex(hex()) -> char() 669%% 670%% @doc Used to get char from hex code. 671%% @end 672%%-------------------------------------------------------------------- 673-spec from_hex(byte()) -> 0..16. 674from_hex(C) when C >= $0, C =< $9 -> C - $0; 675from_hex(C) when C >= $a, C =< $f -> C - $a + 10; 676from_hex(C) when C >= $A, C =< $F -> C - $A + 10. 677 678%%-------------------------------------------------------------------- 679%% @spec join([string()], Sep::string()) -> string() 680%% 681%% @doc Joins a list of elements using a separator. 682%% The result is reversed for efficiency. 683%% @end 684%%-------------------------------------------------------------------- 685-spec join([string()], string() | char()) -> string(). 686join(Strings, Sep) -> 687 join(Strings, Sep, []). 688 689-spec join([string()], string() | char(), list()) -> string(). 690join([], _Sep, _Acc) -> 691 []; 692join([Last], _Sep, Acc) -> 693 [Last|Acc]; 694join([H|Rest], Sep, Acc) -> 695 join(Rest, Sep, [Sep, H|Acc]). 696 697-spec nhdr(atom() | binary() | string()) -> string(). 698nhdr(L) when is_atom(L) -> 699 nhdr(atom_to_list(L)); 700nhdr(L) when is_binary(L) -> 701 nhdr(binary_to_list(L)); 702nhdr(L) when is_list(L) -> 703 string:strip(string:to_lower(L)). 704 705normalize_header({K, V}) -> 706 {nhdr(K), string:strip(V)}. 707 708%% http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 709%% and 710%% http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2 711unquote_path(Path) -> 712 PathComponents = [unquote(X) || X <- path_components(Path, [], [])], 713 lists:flatten(join(PathComponents, "%2F")). 714 715path_components([], Piece, Acc) -> 716 [lists:reverse(Piece)|Acc]; 717path_components("%2f" ++ Rest, Piece, Acc) -> 718 path_components(Rest, [], [lists:reverse(Piece)|Acc]); 719path_components("%2F" ++ Rest, Piece, Acc) -> 720 path_components(Rest, [], [lists:reverse(Piece)|Acc]); 721path_components([C|Rest], Piece, Acc) -> 722 path_components(Rest, [C|Piece], Acc). 723 724-define(EWGI_SPEC_FIELDS, [{read_input, fun(E, V) -> ?SET_EWGI_READ_INPUT(V, E) end}, 725 {write_error, fun(E, V) -> ?SET_EWGI_WRITE_ERROR(V, E) end}, 726 {url_scheme, fun(E, V) -> ?SET_EWGI_URL_SCHEME(V, E) end}, 727 {version, fun(E, V) -> ?SET_EWGI_VERSION(V, E) end}, 728 {data, fun(E, V) -> ?SET_EWGI_DATA(V, E) end}]). 729 730-define(EWGI_HTTP_HEADER_FIELDS, [{http_accept, fun(E, V) -> ?SET_HTTP_ACCEPT(V, E) end}, 731 {http_cookie, fun(E, V) -> ?SET_HTTP_COOKIE(V, E) end}, 732 {http_host, fun(E, V) -> ?SET_HTTP_HOST(V, E) end}, 733 {http_if_modified_since, fun(E, V) -> ?SET_HTTP_IF_MODIFIED_SINCE(V, E) end}, 734 {http_user_agent, fun(E, V) -> ?SET_HTTP_USER_AGENT(V, E) end}, 735 {http_x_http_method_override, fun(E, V) -> ?SET_HTTP_X_HTTP_METHOD_OVERRIDE(V, E) end}, 736 {other, fun(E, V) -> ?SET_HTTP_OTHER(V, E) end}]). 737 738-define(EWGI_REQUEST_FIELDS, [{auth_type, fun(E, V) -> ?SET_AUTH_TYPE(V, E) end}, 739 {content_length, fun(E, V) -> ?SET_CONTENT_LENGTH(V, E) end}, 740 {content_type, fun(E, V) -> ?SET_CONTENT_TYPE(V, E) end}, 741 {ewgi, fun(E, V) -> ?SET_EWGI(V, E) end}, 742 {gateway_interface, fun(E, V) -> ?SET_GATEWAY_INTERFACE(V, E) end}, 743 {http_headers, fun(E, V) -> ?SET_HTTP_HEADERS(V, E) end}, 744 {path_info, fun(E, V) -> ?SET_PATH_INFO(V, E) end}, 745 {path_translated, fun(E, V) -> ?SET_PATH_TRANSLATED(V, E) end}, 746 {query_string, fun(E, V) -> ?SET_QUERY_STRING(V, E) end}, 747 {remote_addr, fun(E, V) -> ?SET_REMOTE_ADDR(V, E) end}, 748 {remote_host, fun(E, V) -> ?SET_REMOTE_HOST(V, E) end}, 749 {remote_ident, fun(E, V) -> ?SET_REMOTE_IDENT(V, E) end}, 750 {remote_user, fun(E, V) -> ?SET_REMOTE_USER(V, E) end}, 751 {remote_user_data, fun(E, V) -> ?SET_REMOTE_USER_DATA(V, E) end}, 752 {request_method, fun(E, V) -> ?SET_REQUEST_METHOD(V, E) end}, 753 {script_name, fun(E, V) -> ?SET_SCRIPT_NAME(V, E) end}, 754 {server_name, fun(E, V) -> ?SET_SERVER_NAME(V, E) end}, 755 {server_port, fun(E, V) -> ?SET_SERVER_PORT(V, E) end}, 756 {server_protocol, fun(E, V) -> ?SET_SERVER_PROTOCOL(V, E) end}, 757 {server_software, fun(E, V) -> ?SET_SERVER_SOFTWARE(V, E) end}]). 758 759empty_ewgi_spec() -> 760 {'ewgi_spec', undefined, undefined, undefined, undefined, 761 gb_trees:empty()}. 762 763empty_http_headers() -> 764 {'ewgi_http_headers', undefined, undefined, undefined, undefined, 765 undefined, undefined, gb_trees:empty()}. 766 767server_request_foldl(Req0, ParseFun0, ParseEwgiFun, ParseHttpFun) -> 768 ParseFun = fun(ewgi, Req) -> 769 request_foldl(Req, ParseEwgiFun, empty_ewgi_spec(), ?EWGI_SPEC_FIELDS); 770 (http_headers, Req) -> 771 request_foldl(Req, ParseHttpFun, empty_http_headers(), ?EWGI_HTTP_HEADER_FIELDS); 772 (Field, Req) -> 773 ParseFun0(Field, Req) 774 end, 775 request_foldl(Req0, ParseFun, empty_request(), ?EWGI_REQUEST_FIELDS). 776 777request_foldl(Req, ParseFun, EmptyRec, Fields) -> 778 lists:foldl(fun({Field, F}, Rec) -> 779 case ParseFun(Field, Req) of 780 undefined -> 781 Rec; 782 V -> 783 F(Rec, V) 784 end 785 end, EmptyRec, Fields). 786 787t_lookup_default(K, T, Default) -> 788 case gb_trees:lookup(K, T) of 789 {value, V} -> 790 V; 791 none -> 792 Default 793 end. 794 795t_insert_header(K, Pair, T) -> 796 gb_trees:enter(K, lists:reverse([Pair|lists:reverse(t_lookup_default(K, T, []))]), T). 797 798t_enter_header(K, Pair, T) -> 799 gb_trees:enter(K, Pair, T). 800 801%% The following method (urlsplit/1) is taken from the MochiWeb 802%% project module mochiweb_util. 803%% Copyright 2007 MochiMedia, Inc. 804%% See LICENSE for more details. 805 806%% @spec urlsplit(Url) -> {Scheme, Netloc, Path, Query, Fragment} 807%% @doc Return a 5-tuple, does not expand % escapes. Only supports HTTP style 808%% URLs. 809urlsplit(Url) -> 810 {Scheme, Url1} = urlsplit_scheme(Url), 811 {Netloc, Url2} = urlsplit_netloc(Url1), 812 {Path, Query, Fragment} = urlsplit_path(Url2), 813 {Scheme, Netloc, Path, Query, Fragment}. 814 815urlsplit_scheme(Url) -> 816 urlsplit_scheme(Url, []). 817 818urlsplit_scheme([], Acc) -> 819 {"", lists:reverse(Acc)}; 820urlsplit_scheme(":" ++ Rest, Acc) -> 821 {string:to_lower(lists:reverse(Acc)), Rest}; 822urlsplit_scheme([C | Rest], Acc) -> 823 urlsplit_scheme(Rest, [C | Acc]). 824 825urlsplit_netloc("//" ++ Rest) -> 826 urlsplit_netloc(Rest, []); 827urlsplit_netloc(Path) -> 828 {"", Path}. 829 830urlsplit_netloc(Rest=[C | _], Acc) when C =:= $/; C =:= $?; C =:= $# -> 831 {lists:reverse(Acc), Rest}; 832urlsplit_netloc([C | Rest], Acc) -> 833 urlsplit_netloc(Rest, [C | Acc]). 834 835%% @spec urlsplit_path(Url) -> {Path, Query, Fragment} 836%% @doc Return a 3-tuple, does not expand % escapes. Only supports HTTP style 837%% paths. 838urlsplit_path(Path) -> 839 urlsplit_path(Path, []). 840 841urlsplit_path("", Acc) -> 842 {lists:reverse(Acc), "", ""}; 843urlsplit_path("?" ++ Rest, Acc) -> 844 {Query, Fragment} = urlsplit_query(Rest), 845 {lists:reverse(Acc), Query, Fragment}; 846urlsplit_path("#" ++ Rest, Acc) -> 847 {lists:reverse(Acc), "", Rest}; 848urlsplit_path([C | Rest], Acc) -> 849 urlsplit_path(Rest, [C | Acc]). 850 851urlsplit_query(Query) -> 852 urlsplit_query(Query, []). 853 854urlsplit_query("", Acc) -> 855 {lists:reverse(Acc), ""}; 856urlsplit_query("#" ++ Rest, Acc) -> 857 {lists:reverse(Acc), Rest}; 858urlsplit_query([C | Rest], Acc) -> 859 urlsplit_query(Rest, [C | Acc]). 860 861%%-------------------------------------------------------------------- 862%% Stream methods 863%%-------------------------------------------------------------------- 864%% chunked response 865stream_process_init(Ctx, chunked) when ?IS_EWGI_CONTEXT(Ctx) -> 866 {StatusCode, _} = ewgi_api:response_status(Ctx), 867 Headers = ewgi_api:response_headers(Ctx), 868 ChunkedHeader = {"Transfer-Encoding", "chunked"}, 869 wait_for_socket(StatusCode, [ChunkedHeader|Headers], chunked); 870 871%% non chunked response 872stream_process_init(Ctx, CL) when ?IS_EWGI_CONTEXT(Ctx), is_integer(CL) -> 873 {StatusCode, _} = ewgi_api:response_status(Ctx), 874 Headers = ewgi_api:response_headers(Ctx), 875 CLHeader = {"Content-Length", integer_to_list(CL)}, 876 wait_for_socket(StatusCode, [CLHeader|Headers], non_chunked). 877 878%% This API is for processes that don't have access the original ewgi_context() 879stream_process_init(StatusCode, Headers, chunked) -> 880 ChunkedHeader = {"Transfer-Encoding", "chunked"}, 881 wait_for_socket(StatusCode, [ChunkedHeader|Headers], chunked); 882stream_process_init(StatusCode, Headers, CL) when is_integer(CL) -> 883 CLHeader = {"Content-Length", integer_to_list(CL)}, 884 wait_for_socket(StatusCode, [CLHeader|Headers], non_chunked). 885 886-define(STREAM_INIT_TIMEOUT, 5000). 887 888wait_for_socket(StatusCode, Headers, TransferEncoding) -> 889 receive 890 {push_stream_init, ServerModule, ServerPid, Socket} -> 891 ServerPid ! {push_stream_init, self(), StatusCode, Headers, TransferEncoding}, 892 Connection = {ServerModule, ServerPid, Socket}, 893 %% The server should report back on whether we should send data. 894 %% Sometimes (Method='HEAD') only the headers are sent. 895 receive 896 {ok, ServerPid} -> 897 {ok, Connection}; 898 {discard, ServerPid} -> 899 stream_process_end(Connection), 900 discard 901 end 902 after ?STREAM_INIT_TIMEOUT -> 903 error_logger:error_msg(?MODULE_STRING ++": Timeout while trying to init stream process!~n"), 904 discard 905 end. 906 907stream_process_deliver({ServerModule, _ServerPid, Socket}, IoList) -> 908 ServerModule:stream_process_deliver(Socket, IoList). 909 910stream_process_deliver_chunk({ServerModule, _ServerPid, Socket}, IoList) -> 911 ServerModule:stream_process_deliver_chunk(Socket, IoList). 912 913stream_process_deliver_final_chunk({ServerModule, _ServerPid, Socket}, IoList) -> 914 ServerModule:stream_process_deliver_final_chunk(Socket, IoList). 915 916stream_process_end({ServerModule, ServerPid, Socket}) -> 917 ServerModule:stream_process_end(Socket, ServerPid). 918