PageRenderTime 179ms CodeModel.GetById 15ms app.highlight 150ms RepoModel.GetById 1ms app.codeStats 1ms

/src/ewgi_api.erl

http://github.com/skarab/ewgi
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 &amp; character or &amp;amp; entity at the beginning of string.
625%% @end
626%%--------------------------------------------------------------------
627-spec is_amp(string()) -> boolean().
628is_amp("&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