1%%----------------------------------------------------------------------
2%%% File    : yaws_api.erl
3%%% Author  : Claes Wikstrom <klacke@hyber.org>
4%%% Purpose :
5%%% Created : 24 Jan 2002 by Claes Wikstrom <klacke@hyber.org>
6%%%----------------------------------------------------------------------
7
8-module(yaws_api).
9-author('klacke@hyber.org').
10
11-include("../include/yaws.hrl").
12-include("../include/yaws_api.hrl").
13-include("yaws_debug.hrl").
14
15
16
17-export([parse_query/1, parse_post/1,
18         parse_multipart_post/1, parse_multipart_post/2,
19         parse_multipart/2, parse_multipart/3]).
20-export([code_to_phrase/1, ssi/2, redirect/1]).
21-export([setcookie/2, setcookie/3, setcookie/4, setcookie/5, setcookie/6]).
22-deprecated([{setcookie, 2, eventually},
23             {setcookie, 3, eventually},
24             {setcookie, 4, eventually},
25             {setcookie, 5, eventually},
26             {setcookie, 6, eventually}]).
27-export([set_cookie/3]).
28-export([pre_ssi_files/2,  pre_ssi_string/1, pre_ssi_string/2,
29         set_content_type/1,
30         htmlize/1, htmlize_char/1, f/2, fl/1]).
31-export([find_cookie_val/2, secs/0,
32         url_decode/1, url_decode_q_split/1, url_decode_with_encoding/2,
33         url_encode/1, parse_url/1, parse_url/2, format_url/1,
34         format_partial_url/2]).
35-export([is_absolute_URI/1]).
36-export([path_norm/1, path_norm_reverse/1,
37         sanitize_file_name/1]).
38-export([get_line/1, mime_type/1, mime_type/2]).
39-export([stream_chunk_deliver/2, stream_chunk_deliver_blocking/2,
40         stream_chunk_end/1]).
41-export([stream_process_deliver/2, stream_process_deliver_chunk/2,
42         stream_process_deliver_final_chunk/2, stream_process_end/2]).
43-export([websocket_send/2, websocket_close/1, websocket_close/2]).
44-export([get_sslsocket/1]).
45-export([new_cookie_session/1, new_cookie_session/2, new_cookie_session/3,
46         cookieval_to_opaque/1, request_url/1,
47         print_cookie_sessions/0,
48         replace_cookie_session/2, replace_cookie_session/3,
49         delete_cookie_session/1]).
50
51-export([getconf/0,
52         setconf/2,
53         get_listen_port/1,
54         embedded_start_conf/1, embedded_start_conf/2,
55         embedded_start_conf/3, embedded_start_conf/4]).
56
57-export([set_status_code/1, reformat_header/1, reformat_header/2,
58         reformat_request/1, reformat_response/1, reformat_url/1]).
59
60-export([set_trace/1,
61         set_tty_trace/1,
62         set_access_log/1]).
63
64-export([call_cgi/2, call_cgi/3]).
65
66-export([call_fcgi_responder/1, call_fcgi_responder/2,
67         call_fcgi_authorizer/1, call_fcgi_authorizer/2]).
68
69-export([ehtml_expand/1, ehtml_expander/1, ehtml_apply/2,
70         ehtml_expander_test/0]).
71
72-export([parse_set_cookie/1, parse_cookie/1, format_set_cookie/1,
73         format_cookie/1, postvar/2, queryvar/2, getvar/2]).
74
75-export([binding/1,binding_exists/1,
76         dir_listing/1, dir_listing/2, redirect_self/1]).
77
78-export([arg_clisock/1, arg_client_ip_port/1, arg_headers/1, arg_req/1,
79         arg_orig_req/1, arg_clidata/1, arg_server_path/1, arg_querydata/1,
80         arg_appmoddata/1, arg_docroot/1, arg_docroot_mount/1, arg_fullpath/1,
81         arg_cont/1, arg_state/1, arg_pid/1, arg_opaque/1, arg_appmod_prepath/1,
82         arg_prepath/1,
83         arg_pathinfo/1]).
84-export([http_request_method/1, http_request_path/1, http_request_version/1,
85         http_response_version/1, http_response_status/1,
86         http_response_phrase/1,
87         headers_connection/1, headers_accept/1, headers_host/1,
88         headers_if_modified_since/1, headers_if_match/1,
89         headers_if_none_match/1,
90         headers_if_range/1, headers_if_unmodified_since/1, headers_range/1,
91         headers_referer/1, headers_user_agent/1, headers_accept_ranges/1,
92         headers_cookie/1, headers_keep_alive/1, headers_location/1,
93         headers_content_length/1, headers_content_type/1,
94         headers_content_encoding/1, headers_authorization/1,
95         headers_transfer_encoding/1, headers_x_forwarded_for/1,
96         headers_other/1]).
97
98-export([set_header/2, set_header/3, merge_header/2, merge_header/3,
99         get_header/2, get_header/3, delete_header/2]).
100
101-import(lists, [flatten/1, reverse/1]).
102
103%% These are a bunch of accessor functions that are useful inside
104%% yaws scripts.
105
106arg_clisock(#arg{clisock = X}) -> X.
107arg_client_ip_port(#arg{client_ip_port = X}) -> X.
108arg_headers(#arg{headers = X}) -> X.
109arg_req(#arg{req = X}) -> X.
110arg_orig_req(#arg{orig_req = X}) -> X.
111arg_clidata(#arg{clidata = X}) -> X.
112arg_server_path(#arg{server_path = X}) -> X.
113arg_querydata(#arg{querydata = X}) -> X.
114arg_appmoddata(#arg{appmoddata = X}) -> X.
115arg_docroot(#arg{docroot = X}) -> X.
116arg_docroot_mount(#arg{docroot_mount = X}) -> X.
117arg_fullpath(#arg{fullpath = X}) -> X.
118arg_cont(#arg{cont = X}) -> X.
119arg_state(#arg{state = X}) -> X.
120arg_pid(#arg{pid = X}) -> X.
121arg_opaque(#arg{opaque = X}) -> X.
122arg_appmod_prepath(#arg{appmod_prepath = X}) -> X.
123arg_prepath(#arg{prepath = X}) -> X.
124arg_pathinfo(#arg{pathinfo = X}) ->  X.
125
126http_request_method(#http_request{method = X}) -> X.
127http_request_path(#http_request{path = X}) -> X.
128http_request_version(#http_request{version = X}) -> X.
129
130http_response_version(#http_response{version = X}) -> X.
131http_response_status(#http_response{status = X}) -> X.
132http_response_phrase(#http_response{phrase = X}) -> X.
133
134headers_connection(#headers{connection = X}) -> X.
135headers_accept(#headers{accept = X}) -> X.
136headers_host(#headers{host = X}) -> X.
137headers_if_modified_since(#headers{if_modified_since = X}) -> X.
138headers_if_match(#headers{if_match = X}) -> X.
139headers_if_none_match(#headers{if_none_match = X}) -> X.
140headers_if_range(#headers{if_range = X}) -> X.
141headers_if_unmodified_since(#headers{if_unmodified_since = X}) -> X.
142headers_range(#headers{range = X}) -> X.
143headers_referer(#headers{referer = X}) -> X.
144headers_user_agent(#headers{user_agent = X}) -> X.
145headers_accept_ranges(#headers{accept_ranges = X}) -> X.
146headers_cookie(#headers{cookie = X}) -> X.
147headers_keep_alive(#headers{keep_alive = X}) -> X.
148headers_location(#headers{location = X}) -> X.
149headers_content_length(#headers{content_length = X}) -> X.
150headers_content_type(#headers{content_type = X}) -> X.
151headers_content_encoding(#headers{content_encoding = X}) -> X.
152headers_authorization(#headers{authorization = X}) -> X.
153headers_transfer_encoding(#headers{transfer_encoding = X}) -> X.
154headers_x_forwarded_for(#headers{x_forwarded_for = X}) -> X.
155headers_other(#headers{other = X}) -> X.
156
157
158%% parse the command line query data
159parse_query(Arg) ->
160    case get(query_parse) of
161        undefined ->
162            Res = case Arg#arg.querydata of
163                      [] -> [];
164                      D  -> parse_post_data_urlencoded(D)
165                  end,
166            put(query_parse, Res),
167            Res;
168        Res ->
169            Res
170    end.
171
172%% parse url encoded POST data
173parse_post(Arg) ->
174    case get(post_parse) of
175        undefined ->
176            H = Arg#arg.headers,
177            Res = case H#headers.content_type of
178                      "application/x-www-form-urlencoded"++_ ->
179                          case Arg#arg.clidata of
180                              [] -> [];
181                              D  -> parse_post_data_urlencoded(D)
182                          end;
183                      _ ->
184                          []
185                  end,
186            put(post_parse, Res),
187            Res;
188        Res ->
189            Res
190    end.
191
192
193%%
194%% Changed implementation of multipart form data. There is a new config
195%% parameter called
196%%
197%%      partial_post_size
198%%
199%% which if set to an integer value
200%% will cause the content of the post content to be sent to the out/1
201%% function in chunks of this size.
202%%
203%% It is possible to get the server to maintain a state on behalf of the
204%% out/1 user by returning {get_more, Cont, State}.
205%%
206%%
207%% yaws_api:parse_multipart_post/1 will return either:
208%%
209%% {cont, Cont, Res} where Res is new result(s) from this segment. This
210%% indicates that there is more data to come and the out/1 function
211%% should return {get_more, Cont, User_state} where User_state might
212%% usefully be a File Descriptor.
213%%
214%% {result, Res} if this is the last (or only) segment.
215%%
216%% or {error, Reason} if an error occurred during the parsing.
217%%
218%% Res is a list of {head, {Name, Hdrs}} | {part_body, Binary} | {body, Binary}
219%%
220%% Example usage could be:
221%%
222%% <erl>
223%%
224%% out(A) ->
225%%        case yaws_api:parse_multipart_post(A) of
226%%             {cont, Cont, Res} ->
227%%                    St = handle_res(A, Res),
228%%                    {get_more, Cont, St};
229%%             {result, Res} ->
230%%                    handle_res(A, Res),
231%%                    {html, f("<pre>Done </pre>",[])};
232%%             {error, Reason} ->
233%%                    {html, f("An error occured: ~p", [Reason])}
234%%        end.
235%%
236%% handle_res(A, [{head, {Name, Hdrs}}|T]) ->
237%%      io:format("head:~p~n",[Name]),
238%%      handle_res(A, T);
239%% handle_res(A, [{part_body, Data}|T]) ->
240%%      io:format("part_body:~p~n",[Data]),
241%%      handle_res(A, T);
242%% handle_res(A, [{body, Data}|T]) ->
243%%      io:format("body:~p~n",[Data]),
244%%      handle_res(A, T);
245%% handle_res(A, []) ->
246%%      io:format("End_res~n").
247%%
248%% </erl>
249
250parse_multipart_post(Arg) ->
251    parse_multipart_post(Arg, [list]).
252parse_multipart_post(Arg, Options) ->
253    H = Arg#arg.headers,
254    case H#headers.content_type of
255        undefined ->
256            {error, no_content_type};
257        "multipart/form-data"++Line ->
258            case Arg#arg.cont of
259                {cont, Cont} ->
260                    parse_multipart(un_partial(Arg#arg.clidata), {cont, Cont});
261                undefined ->
262                    LineArgs = parse_arg_line(Line),
263                    {value, {_, Boundary}} = lists:keysearch("boundary", 1,
264                                                             LineArgs),
265                    parse_multipart(un_partial(Arg#arg.clidata),
266                                    Boundary, Options)
267            end;
268        _Other ->
269            {error, no_multipart_form_data}
270    end.
271
272un_partial({partial, Bin}) ->
273    Bin;
274un_partial(Bin) ->
275    Bin.
276
277parse_arg_line(Line) ->
278    parse_arg_line(Line, []).
279
280parse_arg_line([],Acc) -> Acc;
281parse_arg_line([$ |Line], Acc) ->
282    parse_arg_line(Line, Acc);
283parse_arg_line([$;|Line], Acc) ->
284    {KV,Rest} = parse_arg_key(Line, [], []),
285    parse_arg_line(Rest, [KV|Acc]).
286
287%%
288
289parse_arg_key([], Key, Value) ->
290    make_parse_line_reply(Key, Value, []);
291parse_arg_key([$;|Line], Key, Value) ->
292    make_parse_line_reply(Key, Value, [$;|Line]);
293parse_arg_key([$ |Line], Key, Value) ->
294    parse_arg_key(Line, Key, Value);
295parse_arg_key([$=|Line], Key, Value) ->
296    parse_arg_value(Line, Key, Value, false, false);
297parse_arg_key([C|Line], Key, Value) ->
298    parse_arg_key(Line, [C|Key], Value).
299
300%%
301%% We need to deal with quotes and initial spaces here.
302%% parse_arg_value(String, Key, ValueAcc, InQuoteBool, InValueBool)
303%%
304
305parse_arg_value([], Key, Value, _, _) ->
306    make_parse_line_reply(Key, Value, []);
307parse_arg_value([$\\,$"], Key, Value, _, _) ->
308    make_parse_line_reply(Key, [$\\|Value], []);
309parse_arg_value([$\\,$"|Line], Key, Value, Quote, Begun) ->
310    parse_arg_value(Line, Key, [$"|Value], Quote, Begun);
311parse_arg_value([$"|Line], Key, Value, false, _) ->
312    parse_arg_value(Line, Key, Value, true, true);
313parse_arg_value([$"], Key, Value, true, _) ->
314    make_parse_line_reply(Key, Value, []);
315parse_arg_value([$",$;|Line], Key, Value, true, _) ->
316    make_parse_line_reply(Key, Value, [$;|Line]);
317parse_arg_value([$;|Line], Key, Value, false, _) ->
318    make_parse_line_reply(Key, Value, [$;|Line]);
319parse_arg_value([$ |Line], Key, Value, false, true) ->
320    make_parse_line_reply(Key, Value, Line);
321parse_arg_value([$ |Line], Key, Value, false, false) ->
322    parse_arg_value(Line, Key, Value, false, false);
323parse_arg_value([C|Line], Key, Value, Quote, _) ->
324    parse_arg_value(Line, Key, [C|Value], Quote, true).
325
326
327%%
328
329make_parse_line_reply(Key, Value, Rest) ->
330    {{yaws:funreverse(Key, fun yaws:to_lowerchar/1),
331      lists:reverse(Value)}, Rest}.
332
333
334-record(mp_parse_state, {
335          state,
336          boundary_ctx,
337          boundary_len,
338          hdr_end_ctx,
339          old_data,
340          data_type
341         }).
342
343%% Stateful parser of multipart data - allows easy re-entry
344parse_multipart(Data, St) ->
345    parse_multipart(Data, St, [list]).
346parse_multipart(Data, St, Options) ->
347    case parse_multi(Data, St, Options) of
348        {cont, St2, Res} -> {cont, {cont, St2}, lists:reverse(Res)};
349        {result, Res}    -> {result, lists:reverse(Res)};
350        {error, Reason}  -> {error, Reason}
351    end.
352
353parse_multi(Data, #mp_parse_state{state=boundary}=ParseState, Acc) ->
354    %% Find the beginning of the next part or the last boundary
355    case binary:match(Data, ParseState#mp_parse_state.boundary_ctx) of
356        {Pos, Len} ->
357            %% If Pos != 0, ignore data preceding the boundary
358            case Data of
359                <<_:Pos/binary, Rest/binary>> when size(Rest) < Len+2 ->
360                    %% Not enough data to tell if it is the last boundary or not
361                    {cont, ParseState#mp_parse_state{old_data=Rest}, Acc};
362                <<_:Pos/binary, _:Len/binary, "\r\n", Rest/binary>> ->
363                    %% It is not the last boundary, so parse the next part
364                    NPState = ParseState#mp_parse_state{state=start_headers},
365                    parse_multi(Rest, NPState, Acc);
366                <<_:Pos/binary, _:Len/binary, "--\r\n", _/binary>> ->
367                    %% Match on the last boundary and ignore remaining data
368                    {result, Acc};
369                <<_:Pos/binary, Boundary:Len/binary, "--", Rest/binary>> when size(Rest) < 2 ->
370                    %% Partial match on the last boundary; need more data
371		    {cont, ParseState#mp_parse_state{old_data = <<Boundary/binary, "--", Rest/binary>>}, Acc};
372                _ ->
373                    {error, malformed_multipart_post}
374            end;
375        nomatch ->
376            %% No boundary found, request more data. Here we keep just enough
377            %% data to match on the boundary the next time
378            DLen = size(Data),
379            BLen = ParseState#mp_parse_state.boundary_len,
380            SkipLen = erlang:max(DLen - BLen, 0),
381            KeepLen = erlang:min(BLen, DLen),
382            <<_:SkipLen/binary, OldData:KeepLen/binary>> = Data,
383            {cont, ParseState#mp_parse_state{old_data=OldData}, Acc}
384    end;
385
386parse_multi(Data, #mp_parse_state{state=start_headers}=ParseState, Acc) ->
387    parse_multi(Data, ParseState, Acc, [], []);
388
389parse_multi(Data, #mp_parse_state{state=body}=ParseState, Acc) ->
390    %% Find the end of this part (i.e the next boundary)
391    case binary:match(Data, ParseState#mp_parse_state.boundary_ctx) of
392        {Pos, _Len} ->
393            %% Extract the body and keep the boundary
394            <<Body:Pos/binary, Rest/binary>> = Data,
395            BodyData = case ParseState#mp_parse_state.data_type of
396                           list   -> binary_to_list(Body);
397                           binary -> Body
398                       end,
399            NAcc = [{body, BodyData}|Acc],
400            NParseState = ParseState#mp_parse_state{state=boundary},
401            parse_multi(Rest, NParseState, NAcc);
402        nomatch ->
403            %% No boundary found, request more data.
404            DLen = size(Data),
405            BLen = ParseState#mp_parse_state.boundary_len,
406            SkipLen = erlang:max(DLen - BLen, 0),
407            KeepLen = erlang:min(BLen, DLen),
408            <<PartData:SkipLen/binary, OldData:KeepLen/binary>> = Data,
409            NParseState = ParseState#mp_parse_state{state=body,
410                                                    old_data=OldData},
411            BodyData = case ParseState#mp_parse_state.data_type of
412                           list   -> binary_to_list(PartData);
413                           binary -> PartData
414                       end,
415            {cont, NParseState, [{part_body, BodyData}|Acc]}
416    end;
417
418parse_multi(Data, {cont, #mp_parse_state{old_data=OldData}=ParseState}, _) ->
419    %% Reentry point
420    NData = <<OldData/binary, Data/binary>>,
421    parse_multi(NData, ParseState, []);
422
423parse_multi(Data, Boundary, Options) ->
424    %% Initial entry point
425    FullBoundary = list_to_binary(["\r\n--", Boundary]),
426    BoundaryCtx  = binary:compile_pattern(FullBoundary),
427    HdrEndCtx    = binary:compile_pattern(<<"\r\n\r\n">>),
428    DataType     = lists:foldl(fun(_,      list)      -> list;
429				  (list,   _)         -> list;
430				  (binary, undefined) -> binary;
431				  (_,      Acc)       -> Acc
432			       end, undefined, Options),
433    ParseState = #mp_parse_state{state        = boundary,
434                                 boundary_ctx = BoundaryCtx,
435                                 boundary_len = size(FullBoundary),
436                                 hdr_end_ctx  = HdrEndCtx,
437                                 data_type    = DataType},
438    parse_multi(<<"\r\n", Data/binary>>, ParseState, []).
439
440
441parse_multi(Data, #mp_parse_state{state=start_headers}=ParseState,
442            Acc, [], []) ->
443    %% Find the end of headers for this part
444    case binary:match(Data, ParseState#mp_parse_state.hdr_end_ctx) of
445        {_Pos, _Len} ->
446            %% We have all headers, we can parse it
447            NParseState = ParseState#mp_parse_state{state=headers},
448            parse_multi(Data, NParseState, Acc, [], []);
449        nomatch ->
450            {cont, ParseState#mp_parse_state{old_data=Data}, Acc}
451    end;
452parse_multi(Data, #mp_parse_state{state=headers}=ParseState, Acc, Name, Hdrs) ->
453    case erlang:decode_packet(httph_bin, Data, [{packet_size, 16#4000}]) of
454        {ok, http_eoh, Rest} ->
455            %% All headers are parsed, get the body now
456            Head = case Name of
457                       [] -> lists:reverse(Hdrs);
458                       _  -> {Name, lists:reverse(Hdrs)}
459                   end,
460            NParseState = ParseState#mp_parse_state{state=body},
461            parse_multi(Rest, NParseState, [{head, Head}|Acc]);
462        {ok, {http_header, _, Hdr, _, HdrVal}, Rest} when is_atom(Hdr) ->
463            Header = {case Hdr of
464                          'Content-Type' -> content_type;
465                          Else           -> Else
466                      end,
467                      binary_to_list(HdrVal)},
468            parse_multi(Rest, ParseState, Acc, Name, [Header|Hdrs]);
469        {ok, {http_header, _, Hdr, _, HdrVal}, Rest} ->
470            HdrValStr = binary_to_list(HdrVal),
471            case yaws:to_lower(binary_to_list(Hdr)) of
472                "content-disposition" ->
473                    "form-data"++Params = HdrValStr,
474                    Parameters = parse_arg_line(Params),
475                    {_, NewName} = lists:keyfind("name", 1, Parameters),
476                    parse_multi(Rest, ParseState, Acc,
477                                NewName, Parameters++Hdrs);
478                LowerHdr ->
479                    parse_multi(Rest, ParseState, Acc,
480                                Name, [{LowerHdr, HdrValStr}|Hdrs])
481            end;
482        _ ->
483            {error, malformed_multipart_post}
484    end.
485
486%% parse POST data when ENCTYPE is unset or
487%% Content-type: application/x-www-form-urlencoded
488%% Bin is the content of ARG#arg.clidata
489%% the alternative is
490%% Content-type: multipart/form-data; boundary=-------------------7cd1d6371ec
491%% which is used for file upload
492
493parse_post_data_urlencoded(Bin) ->
494    do_parse_spec(Bin, nokey, [], key).
495
496
497%% It will return a [{Key, Value}] list from the post data
498
499do_parse_spec(<<$%, Hi:8, Lo:8, Tail/binary>>, Last, Cur, State)
500    when Hi /= $u ->
501    Hex = yaws:hex_to_integer([Hi, Lo]),
502    do_parse_spec(Tail, Last, [ Hex | Cur],  State);
503
504do_parse_spec(<<$&, Tail/binary>>, _Last , Cur,  key) ->
505    [{lists:reverse(Cur), undefined} |
506     do_parse_spec(Tail, nokey, [], key)];  %% cont keymode
507
508do_parse_spec(<<$&, Tail/binary>>, Last, Cur, value) ->
509    V = {Last, lists:reverse(Cur)},
510    [V | do_parse_spec(Tail, nokey, [], key)];
511
512do_parse_spec(<<$+, Tail/binary>>, Last, Cur,  State) ->
513    do_parse_spec(Tail, Last, [$\s|Cur], State);
514
515do_parse_spec(<<$=, Tail/binary>>, _Last, Cur, key) ->
516    do_parse_spec(Tail, lists:reverse(Cur), [], value); %% change mode
517
518do_parse_spec(<<$%, $u, A:8, B:8,C:8,D:8, Tail/binary>>,
519               Last, Cur, State) ->
520    %% non-standard encoding for Unicode characters: %uxxxx,
521    Hex = yaws:hex_to_integer([A,B,C,D]),
522    do_parse_spec(Tail, Last, [ Hex | Cur],  State);
523
524do_parse_spec(<<H:8, Tail/binary>>, Last, Cur, State) ->
525    do_parse_spec(Tail, Last, [H|Cur], State);
526do_parse_spec(<<>>, nokey, Cur, _State) ->
527    [{lists:reverse(Cur), undefined}];
528do_parse_spec(<<>>, Last, Cur, _State) ->
529    [{Last, lists:reverse(Cur)}];
530do_parse_spec(undefined,_,_,_) ->
531    [];
532do_parse_spec(QueryList, Last, Cur, State) when is_list(QueryList) ->
533    do_parse_spec(list_to_binary(QueryList), Last, Cur, State).
534
535
536code_to_phrase(100) -> "Continue";
537code_to_phrase(101) -> "Switching Protocols ";
538code_to_phrase(102) -> "Processing";
539code_to_phrase(200) -> "OK";
540code_to_phrase(201) -> "Created";
541code_to_phrase(202) -> "Accepted";
542code_to_phrase(203) -> "Non-Authoritative Information";
543code_to_phrase(204) -> "No Content";
544code_to_phrase(205) -> "Reset Content";
545code_to_phrase(206) -> "Partial Content";
546code_to_phrase(207) -> "Multi-Status";
547code_to_phrase(208) -> "Already Reported";
548code_to_phrase(226) -> "IM Used";
549code_to_phrase(300) -> "Multiple Choices";
550code_to_phrase(301) -> "Moved Permanently";
551code_to_phrase(302) -> "Found";
552code_to_phrase(303) -> "See Other";
553code_to_phrase(304) -> "Not Modified";
554code_to_phrase(305) -> "Use Proxy";
555code_to_phrase(306) -> "(Unused)";
556code_to_phrase(307) -> "Temporary Redirect";
557code_to_phrase(308) -> "Permanent Redirect";
558code_to_phrase(400) -> "Bad Request";
559code_to_phrase(401) -> "Unauthorized";
560code_to_phrase(402) -> "Payment Required";
561code_to_phrase(403) -> "Forbidden";
562code_to_phrase(404) -> "Not Found";
563code_to_phrase(405) -> "Method Not Allowed";
564code_to_phrase(406) -> "Not Acceptable";
565code_to_phrase(407) -> "Proxy Authentication Required";
566code_to_phrase(408) -> "Request Timeout";
567code_to_phrase(409) -> "Conflict";
568code_to_phrase(410) -> "Gone";
569code_to_phrase(411) -> "Length Required";
570code_to_phrase(412) -> "Precondition Failed";
571code_to_phrase(413) -> "Request Entity Too Large";
572code_to_phrase(414) -> "Request-URI Too Long";
573code_to_phrase(415) -> "Unsupported Media Type";
574code_to_phrase(416) -> "Requested Range Not Satisfiable";
575code_to_phrase(417) -> "Expectation Failed";
576code_to_phrase(418) -> "I'm a teapot";
577code_to_phrase(420) -> "Enhance Your Calm";
578code_to_phrase(422) -> "Unprocessable Entity";
579code_to_phrase(423) -> "Locked";
580code_to_phrase(424) -> "Failed Dependency";
581code_to_phrase(425) -> "Unordered Collection";
582code_to_phrase(426) -> "Upgrade Required";
583code_to_phrase(428) -> "Precondition Required";
584code_to_phrase(429) -> "Too Many Requests";
585code_to_phrase(431) -> "Request Header Fields Too Large";
586code_to_phrase(451) -> "Unavailable For Legal Reasons";
587code_to_phrase(500) -> "Internal Server Error";
588code_to_phrase(501) -> "Not Implemented";
589code_to_phrase(502) -> "Bad Gateway";
590code_to_phrase(503) -> "Service Unavailable";
591code_to_phrase(504) -> "Gateway Timeout";
592code_to_phrase(505) -> "HTTP Version Not Supported";
593code_to_phrase(506) -> "Variant Also Negotiates";
594code_to_phrase(507) -> "Insufficient Storage";
595code_to_phrase(508) -> "Loop Detected";
596code_to_phrase(510) -> "Not Extended";
597code_to_phrase(511) -> "Network Authentication Required";
598
599%% Below are some non-HTTP status codes from other protocol standards that
600%% we've seen used with HTTP in the wild, so we include them here. HTTP 1.1
601%% section 6.1.1 allows for this sort of extensibility, but we recommend
602%% sticking with the HTTP status codes above for maximal portability and
603%% interoperability.
604%%
605code_to_phrase(452) -> "Insufficient Storage Space"; % from FTP (RFC 959)
606code_to_phrase(453) -> "Not Enough Bandwidth".       % from RTSP (RFC 2326)
607
608
609
610%%
611%% server side include
612%%
613
614ssi(DocRoot, Files) ->
615    L = lists:map(fun(F) ->
616                          case file:read_file([DocRoot ++ [$/|F]]) of
617                              {ok, Bin} ->
618                                  Bin;
619                              {error, Reason} ->
620                                  io_lib:format("Cannot include file ~p: ~p",
621                                                [F, Reason])
622                          end
623                  end, Files),
624    {html, L}.
625
626
627%% include pre
628pre_ssi_files(DocRoot, Files) ->
629    {html, L} = ssi(DocRoot, Files),
630    pre_ssi_string(L).
631
632pre_ssi_string(Str) ->
633    pre_ssi_string(Str, "box").
634
635pre_ssi_string(Str, Class) ->
636    {html, ["<br><br>\n<div class=\"", Class, "\"> <pre>\n",
637            htmlize_l(Str),
638            "\n</pre></div>\n<br>\n\n"]}.
639
640
641%% convenience
642
643f(Fmt, Args) ->
644    io_lib:format(Fmt, Args).
645
646
647fl([Fmt, Arg | Tail]) ->
648    [f(Fmt, Arg) | fl(Tail)];
649fl([]) ->
650    [].
651
652%% htmlize
653htmlize(Bin) when is_binary(Bin) ->
654    list_to_binary(htmlize_l(binary_to_list(Bin)));
655htmlize(List) when is_list(List) ->
656    htmlize_l(List).
657
658
659
660htmlize_char($>) ->
661    <<"&gt;">>;
662htmlize_char($<) ->
663    <<"&lt;">>;
664htmlize_char($&) ->
665    <<"&amp;">>;
666htmlize_char($") ->
667    <<"&quot;">>;
668htmlize_char(X) ->
669    X.
670
671
672%% htmlize list (usually much more efficient than above)
673htmlize_l(List) ->
674    htmlize_l(List, []).
675
676htmlize_l([], Acc) -> lists:reverse(Acc);
677htmlize_l([$>|Tail], Acc) ->
678    htmlize_l(Tail, [$;,$t,$g,$&|Acc]);
679htmlize_l([$<|Tail], Acc) ->
680    htmlize_l(Tail, [$;,$t,$l,$&|Acc]);
681htmlize_l([$&|Tail], Acc) ->
682    htmlize_l(Tail, [$;,$p,$m,$a,$&|Acc]);
683htmlize_l([$"|Tail], Acc) ->
684    htmlize_l(Tail, [$; , $t, $o,  $u,  $q  ,$&|Acc]);
685
686htmlize_l([X|Tail], Acc) when is_integer(X) ->
687    htmlize_l(Tail, [X|Acc]);
688htmlize_l([X|Tail], Acc) when is_binary(X) ->
689    X2 = htmlize_l(binary_to_list(X)),
690    htmlize_l(Tail, [X2|Acc]);
691htmlize_l([X|Tail], Ack) when is_list(X) ->
692    X2 = htmlize_l(X),
693    htmlize_l(Tail, [X2|Ack]).
694
695
696
697secs() ->
698    {MS, S, _} = yaws:get_time_tuple(),
699    (MS * 1000000) + S.
700
701cookie_option(secure) ->
702    "; Secure";
703cookie_option(http_only) ->
704    "; HttpOnly";
705cookie_option(I) ->
706    throw({badarg, I}).
707cookie_option(expires, UTC) when is_tuple(UTC) ->
708    ["; Expires=" | yaws:universal_time_as_string(UTC)];
709cookie_option(max_age, Age) when is_integer(Age) ->
710    V = if Age < 0 -> "0"; true -> integer_to_list(Age) end,
711    ["; Max-Age=" | V];
712cookie_option(path, Path) when is_list(Path), Path =/= [] ->
713    ["; Path=" | Path];
714cookie_option(domain, Domain) when is_list(Domain), Domain =/= [] ->
715    ["; Domain=" | Domain];
716cookie_option(comment, Comment) when is_list(Comment), Comment=/= [] ->
717    ["; Comment=" | Comment];
718cookie_option(I, _) ->
719    throw({badarg, I}).
720
721%% @doc Generate a set_cookie header field tuple.
722%%      This function is more RFC6265 compliant than setcookie/6 and
723%%      therefore it deprecates setcookie/6 completely.
724set_cookie(Key, Value, Options)
725        when is_list(Key), is_list(Value), is_list(Options) ->
726    %% RFC6265 (4.1.1): Name=Value options must come first.
727    {NV,SV} = lists:foldl(fun
728        ({N,V}, {L1, L2}) -> {[cookie_option(N,V) | L1], L2};
729        (N,     {L1, L2}) -> {L1, [cookie_option(N) | L2]}
730    end, {[], []}, Options),
731    {header, {set_cookie, [Key, $=, Value, "; Version=1", NV | SV]}}.
732
733setcookie(Name, Value) ->
734    {header, {set_cookie, f("~s=~s;", [Name, Value])}}.
735
736setcookie(Name, Value, Path) ->
737    {header, {set_cookie, f("~s=~s; path=~s", [Name, Value, Path])}}.
738
739setcookie(Name, Value, Path, Expire) ->
740    setcookie(Name, Value, Path,  Expire, [], []).
741
742setcookie(Name, Value, Path, Expire, Domain) ->
743    setcookie(Name, Value, Path, Expire, Domain,[]).
744
745setcookie(Name, Value, Path, Expire, Domain, Secure) ->
746    SetDomain = if Domain == [] -> "";
747                   true -> " Domain="++Domain++";"
748                end,
749    SetExpire = if Expire == [] -> "";
750                   true -> " Expires="++Expire++";"
751                end,
752    SetPath = if Path == [] -> "/";
753                 true -> Path
754              end,
755    SetSecure = if Secure == on -> " secure;";
756                   true -> ""
757                end,
758    {header, {set_cookie, f("~s=~s;~s~s~s Path=~s",
759                            [Name,Value,SetDomain,SetExpire,
760                             SetSecure, SetPath])}}.
761
762
763%% This function can be passed the cookie we get in the Arg#arg.headers.cookies
764%% to search for a specific cookie
765%% return [] if not found
766%%        Str if found
767%% if several cookies with the same name are passed fron the browser,
768%% only the first match is returned
769find_cookie_val(Name, #arg{}=A) ->
770    find_cookie_val(Name, (A#arg.headers)#headers.cookie);
771find_cookie_val(Name, Cookies) ->
772    find_cookie_val2(yaws:to_lower(Name), Cookies).
773
774find_cookie_val2(_, []) ->
775    [];
776find_cookie_val2(Name, [Cookie|Rest]) ->
777    L = parse_cookie(Cookie),
778    case lists:keyfind(Name, #cookie.key, L) of
779        #cookie{value=undefined} -> [];
780        #cookie{value=Value}     -> Value;
781        false                    -> find_cookie_val2(Name, Rest)
782    end.
783
784
785%%
786url_decode(Path) ->
787    url_decode_with_encoding(Path, file:native_name_encoding()).
788
789url_decode_with_encoding(Path, Encoding) ->
790    {DecPath, QS} = url_decode(Path, []),
791    DecPath1 = case Encoding of
792                   latin1 ->
793                       DecPath;
794                   utf8 ->
795                       case unicode:characters_to_list(list_to_binary(DecPath)) of
796                           UTF8DecPath when is_list(UTF8DecPath) -> UTF8DecPath;
797                           _ -> DecPath
798                       end
799               end,
800    case QS of
801        [] -> lists:flatten(DecPath1);
802        _  -> lists:flatten([DecPath1, $?, QS])
803    end.
804
805url_decode([], Acc) ->
806    {lists:reverse(Acc), []};
807url_decode([$?|Tail], Acc) ->
808    %% Don't decode the query string here, that is parsed separately.
809    {lists:reverse(Acc), Tail};
810url_decode([$%, Hi, Lo | Tail], Acc) ->
811    Hex = yaws:hex_to_integer([Hi, Lo]),
812    url_decode(Tail, [Hex|Acc]);
813url_decode([H|T], Acc) when is_integer(H) ->
814    url_decode(T, [H|Acc]);
815%% deep lists
816url_decode([H|T], Acc) when is_list(H) ->
817    case url_decode(H, Acc) of
818        {P1, []} ->
819            {P2, QS} = url_decode(T, []),
820            {[P1,P2], QS};
821        {P1, QS} ->
822            {P1, QS++T}
823    end.
824
825
826path_norm(Path) ->
827    path_norm_reverse(lists:reverse(Path)).
828
829path_norm_reverse("/" ++ T) -> start_dir(0, "/", T);
830path_norm_reverse(       T) -> start_dir(0,  "", T).
831
832start_dir(N, Path, [$\\|T]     ) -> start_dir(N, Path, [$/|T]);
833start_dir(N, Path, ".."        ) -> rest_dir(N, Path, "");
834start_dir(N, Path, "/"    ++ T ) -> start_dir(N    , Path, T);
835start_dir(N, Path, "./"   ++ T ) -> start_dir(N    , Path, T);
836start_dir(N, Path, ".\\"  ++ T ) -> start_dir(N    , Path, T);
837start_dir(N, Path, "../"  ++ T ) -> start_dir(N + 1, Path, T);
838start_dir(N, Path, "..\\" ++ T ) -> start_dir(N + 1, Path, T);
839start_dir(N, Path,           T ) -> rest_dir (N    , Path, T).
840
841rest_dir (_N, Path, []         ) -> case Path of
842                                        [] -> "/";
843                                        _  -> Path
844                                    end;
845rest_dir (0, Path, [ $/ | T ] ) -> start_dir(0    , [ $/ | Path ], T);
846rest_dir (N, Path, [ $/ | T ] ) -> start_dir(N - 1,        Path  , T);
847rest_dir (N, Path, [ $\\ | T ] ) -> rest_dir(N, Path, [$/|T]);
848rest_dir (0, Path, [  H | T ] ) -> rest_dir (0    , [  H | Path ], T);
849rest_dir (N, Path, [  _H | T ] ) -> rest_dir (N    ,        Path  , T).
850
851%% url decode the path and return {Path, QueryPart}
852
853url_decode_q_split(Path) ->
854    {DecPath, QS} = url_decode_q_split(Path, []),
855    case file:native_name_encoding() of
856        latin1 ->
857            {DecPath, QS};
858        utf8 ->
859            case unicode:characters_to_list(list_to_binary(DecPath)) of
860                UTF8DecPath when is_list(UTF8DecPath) -> {UTF8DecPath, QS};
861                _ -> {DecPath, QS}
862            end
863    end.
864
865url_decode_q_split([$%, Hi, Lo | Tail], Ack) ->
866    Hex = yaws:hex_to_integer([Hi, Lo]),
867    if Hex  == 0 -> exit(badurl);
868       true -> ok
869    end,
870    url_decode_q_split(Tail, [Hex|Ack]);
871url_decode_q_split([$?|T], Ack) ->
872    %% Don't decode the query string here,
873    %% that is parsed separately.
874    {path_norm_reverse(Ack), T};
875url_decode_q_split([H|T], Ack) when H /= 0 ->
876    url_decode_q_split(T, [H|Ack]);
877url_decode_q_split([], Ack) ->
878    {path_norm_reverse(Ack), []}.
879
880
881url_encode(URL) when is_list(URL) ->
882    Bin = case file:native_name_encoding() of
883              latin1 -> list_to_binary(URL);
884              utf8   -> unicode:characters_to_binary(URL)
885          end,
886    %% ReservedChars = "!*'();:@&=+$,/?%#[]",
887    UnreservedChars = sets:from_list("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
888                                     "abcdefghijklmnopqrstuvwxyz"
889                                     "0123456789-_.~"),
890    flatten([url_encode_byte(Byte, UnreservedChars) || <<Byte>> <= Bin]).
891
892url_encode_byte($:, _) -> $:;  % FIXME: both : and / should be encoded, but
893url_encode_byte($/, _) -> $/;  % too much code currently assumes they're not
894url_encode_byte(Byte, UnreservedChars) ->
895    case sets:is_element(Byte, UnreservedChars) of
896        true -> Byte;
897        false ->
898            case yaws:integer_to_hex(Byte) of
899                [X, Y] -> [$%, X, Y];
900                [X]    -> [$%, $0, X]
901            end
902    end.
903
904redirect(Url) -> [{redirect, Url}].
905
906is_nb_space(X) ->
907    lists:member(X, [$\s, $\t]).
908
909%% ret: {line, Line, Trail} | {lastline, Line, Trail} | need_more
910
911get_line(L) ->
912    get_line(L, []).
913get_line("\r\n\r\n" ++ Tail, Cur) ->
914    {lastline, lists:reverse(Cur), Tail};
915get_line("\r\n" ++ Tail, Cur) when Tail /= []  ->
916    case is_nb_space(hd(Tail)) of
917        true ->  %% multiline ... continue
918            get_line(Tail, [$\n, $\r | Cur]);
919        false ->
920            {line, lists:reverse(Cur), Tail}
921    end;
922get_line("\r\n", Cur)   ->
923    {line, lists:reverse(Cur), []};
924get_line([H|T], Cur) ->
925    get_line(T, [H|Cur]);
926get_line([], _) ->
927    need_more.
928
929
930
931mime_type(FileName) ->
932    mime_type(get(sc), FileName).
933
934mime_type(S, FileName) ->
935    case filename:extension(FileName) of
936        [_|T] -> element(2, mime_types:t(S, T));
937        []    -> element(2, mime_types:t(S, []))
938    end.
939
940
941%% Asynchronously delivery
942stream_chunk_deliver(YawsPid, Data) ->
943    YawsPid  ! {streamcontent, Data}.
944
945
946%% Use timeout here to guard against bug in the SSL application
947%% that apparently does not close the socket in between
948%% ssl_esock and erlang (FIN_WAIT2 resp. CLOSE_WAIT).
949%% Thus, the stream process hangs forever...
950-define(STREAM_GARBAGE_TIMEOUT, 3600000). % 1 hour
951
952%% Synchronous (on ultimate gen_tcp:send) delivery
953%% Returns: ok | {error, Rsn}
954stream_chunk_deliver_blocking(YawsPid, Data) ->
955    Ref = erlang:monitor(process, YawsPid),
956    YawsPid  ! {streamcontent_with_ack, self(), Data},
957    receive
958        {YawsPid, streamcontent_ack} ->
959            erlang:demonitor(Ref),
960            %% flush incase a DOWN message was sent before the demonitor call
961            receive
962                {'DOWN', Ref, _, _, _} ->
963                    ok
964            after 0 ->
965                    ok
966            end;
967        {'DOWN', Ref, _, _, Info} ->
968            {error, {ypid_crash, Info}}
969    after ?STREAM_GARBAGE_TIMEOUT ->
970            %% Killing (unless this function is caught) process tree but
971            %% NOTE that as this is probably due to the OTP SSL application
972            %% not managing to close the socket (FIN_WAIT2
973            %% resp. CLOSE_WAIT) the SSL process is not killed (it traps
974            %% exit signals) and thus we will leak one file descriptor.
975            error_logger:error_msg(
976              "~p:stream_chunk_deliver_blocking/2 STREAM_GARBAGE_TIMEOUT "
977              "(default 1 hour). Killing ~p", [?MODULE, YawsPid]),
978            erlang:error(stream_garbage_timeout, [YawsPid, Data])
979    end.
980
981stream_chunk_end(YawsPid) ->
982    YawsPid ! endofstreamcontent.
983
984stream_process_deliver({ssl, SslSock}, IoList) ->
985    ssl:send(SslSock, IoList);
986stream_process_deliver(Sock, IoList) ->
987    gen_tcp:send(Sock, IoList).
988
989stream_process_deliver_chunk(Sock, IoList) ->
990    Chunk = case erlang:iolist_size(IoList) of
991                0 ->
992                    stream_process_deliver_final_chunk(Sock, IoList);
993                S ->
994                    [yaws:integer_to_hex(S), "\r\n", IoList, "\r\n"]
995            end,
996    stream_process_deliver(Sock, Chunk).
997
998stream_process_deliver_final_chunk(Sock, IoList) ->
999    Chunk = case erlang:iolist_size(IoList) of
1000                0 ->
1001                    <<"0\r\n\r\n">>;
1002                S ->
1003                    [yaws:integer_to_hex(S), "\r\n", IoList, "\r\n0\r\n\r\n"]
1004            end,
1005    stream_process_deliver(Sock, Chunk).
1006
1007stream_process_end(closed, YawsPid) ->
1008    YawsPid ! {endofstreamcontent, closed};
1009stream_process_end({ssl, SslSock}, YawsPid) ->
1010    ssl:controlling_process(SslSock, YawsPid),
1011    YawsPid ! endofstreamcontent;
1012stream_process_end(Sock, YawsPid) ->
1013    gen_tcp:controlling_process(Sock, YawsPid),
1014    YawsPid ! endofstreamcontent.
1015
1016
1017websocket_send(#ws_state{}=WSState, {Type, Data}) ->
1018    yaws_websockets:send(WSState, {Type, Data});
1019websocket_send(#ws_state{}=WSState, #ws_frame{}=Frame) ->
1020    yaws_websockets:send(WSState, Frame);
1021%% Pid must be the process in control of the websocket connection.
1022websocket_send(Pid, {Type, Data}) when is_pid(Pid) ->
1023    yaws_websockets:send(Pid, {Type, Data});
1024websocket_send(Pid, #ws_frame{}=Frame) when is_pid(Pid) ->
1025    yaws_websockets:send(Pid, Frame).
1026
1027websocket_close(#ws_state{}=WSState) ->
1028    yaws_websockets:close(WSState, normal);
1029websocket_close(Pid) when is_pid(Pid) ->
1030    yaws_websockets:close(Pid, normal).
1031websocket_close(#ws_state{}=WSState, Reason) ->
1032    yaws_websockets:close(WSState, Reason);
1033websocket_close(Pid, Reason) when is_pid(Pid) ->
1034    yaws_websockets:close(Pid, Reason).
1035
1036
1037%% returns {ok, SSL socket} if an SSL socket, undefined otherwise
1038get_sslsocket({ssl, SslSocket}) ->
1039    {ok, SslSocket};
1040get_sslsocket(_Socket) ->
1041    undefined.
1042
1043%% Return new cookie string
1044new_cookie_session(Opaque) ->
1045    yaws_session_server:new_session(Opaque).
1046
1047new_cookie_session(Opaque, TTL) ->
1048    yaws_session_server:new_session(Opaque, TTL).
1049
1050new_cookie_session(Opaque, TTL, Cleanup) ->
1051    yaws_session_server:new_session(Opaque, TTL, Cleanup).
1052
1053%% as returned in #ysession.cookie
1054cookieval_to_opaque(CookieVal) ->
1055    yaws_session_server:cookieval_to_opaque(CookieVal).
1056
1057print_cookie_sessions() ->
1058    yaws_session_server:print_sessions().
1059
1060replace_cookie_session(Cookie, NewOpaque) ->
1061    yaws_session_server:replace_session(Cookie, NewOpaque).
1062replace_cookie_session(Cookie, NewOpaque, Cleanup) ->
1063    yaws_session_server:replace_session(Cookie, NewOpaque, Cleanup).
1064
1065delete_cookie_session(Cookie) ->
1066    yaws_session_server:delete_session(Cookie).
1067
1068
1069lmap(F, [H|T]) ->
1070    [lists:map(F, H) | lmap(F, T)];
1071lmap(_, []) ->
1072    [].
1073
1074
1075%% interactively turn on|off tracing
1076set_trace(Val) ->
1077    Str = yaws_ctl:actl_trace(Val),
1078    io:format("~s", [Str]).
1079
1080
1081set_access_log(Bool) ->
1082    {ok, GC, Groups} = getconf(),
1083    Groups2 = lmap(fun(SC) ->
1084                           ?sc_set_access_log(SC, Bool)
1085                   end, Groups),
1086    setconf(GC, Groups2).
1087
1088
1089%% interactively turn on|off tracing to the tty (as well)
1090%% typically useful in embedded mode
1091set_tty_trace(Bool) ->
1092    yaws_trace:set_tty_trace(Bool).
1093
1094
1095
1096set_status_code(Code) ->
1097    {status, Code}.
1098
1099
1100
1101
1102%% returns [ Header1, Header2 .....]
1103reformat_header(H) ->
1104    FormatFun = fun(Hname, {multi, Values}) ->
1105                        [lists:flatten(io_lib:format("~s: ~s", [Hname, Val])) ||
1106                            Val <- Values];
1107                   (Hname, Str) ->
1108                        lists:flatten(io_lib:format("~s: ~s", [Hname, Str]))
1109                end,
1110    reformat_header(H, FormatFun).
1111reformat_header(H, FormatFun) ->
1112    lists:zf(fun({Hname, Str}) ->
1113                     I =  FormatFun(Hname, Str),
1114                     {true, I};
1115                (undefined) ->
1116                     false
1117             end,
1118             [
1119              if H#headers.connection == undefined ->
1120                      undefined;
1121                 true ->
1122                      {"Connection", H#headers.connection}
1123              end,
1124
1125              if H#headers.accept == undefined ->
1126                      undefined;
1127                 true ->
1128                      {"Accept", H#headers.accept}
1129              end,
1130              if H#headers.host == undefined ->
1131                      undefined;
1132                 true ->
1133                      {"Host", H#headers.host}
1134              end,
1135              if H#headers.if_modified_since == undefined ->
1136                      undefined;
1137                 true ->
1138                      {"If-Modified-Since", H#headers.if_modified_since}
1139              end,
1140              if H#headers.if_match == undefined ->
1141                      undefined;
1142                 true ->
1143                      {"If-Match", H#headers.if_match}
1144              end,
1145              if H#headers.if_none_match == undefined ->
1146                      undefined;
1147                 true ->
1148                      {"If-None-Match", H#headers.if_none_match}
1149              end,
1150
1151
1152              if H#headers.if_range == undefined ->
1153                      undefined;
1154                 true ->
1155                      {"If-Range", H#headers.if_range}
1156              end,
1157              if H#headers.if_unmodified_since == undefined ->
1158                      undefined;
1159                 true ->
1160                      {"If-Unmodified-Since", H#headers.if_unmodified_since}
1161              end,
1162              if H#headers.range == undefined ->
1163                      undefined;
1164                 true ->
1165                      {"Range", H#headers.range}
1166              end,
1167              if H#headers.referer == undefined ->
1168                      undefined;
1169                 true ->
1170                      {"Referer", H#headers.referer}
1171              end,
1172              if H#headers.user_agent == undefined ->
1173                      undefined;
1174                 true ->
1175                      {"User-Agent", H#headers.user_agent}
1176              end,
1177              if H#headers.accept_ranges == undefined ->
1178                      undefined;
1179                 true ->
1180                      {"Accept-Ranges", H#headers.accept_ranges}
1181              end,
1182              if H#headers.cookie == [] ->
1183                      undefined;
1184                 true ->
1185                      {"Cookie", H#headers.cookie}
1186              end,
1187              if H#headers.keep_alive == undefined ->
1188                      undefined;
1189                 true ->
1190                      {"Keep-Alive", H#headers.keep_alive}
1191              end,
1192              if H#headers.content_length == undefined ->
1193                      undefined;
1194                 true ->
1195                      {"Content-Length", H#headers.content_length}
1196              end,
1197              if H#headers.content_type == undefined ->
1198                      undefined;
1199                 true ->
1200                      {"Content-Type", H#headers.content_type}
1201              end,
1202              if H#headers.content_encoding == undefined ->
1203                      undefined;
1204                 true ->
1205                      {"Content-Encoding", H#headers.content_encoding}
1206              end,
1207
1208              if H#headers.authorization == undefined ->
1209                      undefined;
1210                 true ->
1211                      {"Authorization", element(3, H#headers.authorization)}
1212              end,
1213              if H#headers.transfer_encoding == undefined ->
1214                      undefined;
1215                 true ->
1216                      {"Transfer-Encoding", H#headers.transfer_encoding}
1217              end,
1218              if H#headers.location == undefined ->
1219                      undefined;
1220                 true ->
1221                      {"Location", H#headers.location}
1222              end,
1223              if H#headers.x_forwarded_for == undefined ->
1224                      undefined;
1225                 true ->
1226                      {"X-Forwarded-For", H#headers.x_forwarded_for}
1227              end
1228
1229             ]
1230            ) ++
1231        lists:map(
1232          fun({http_header,_,K,_,V}) ->
1233                  FormatFun(K,V)
1234          end, H#headers.other).
1235
1236
1237set_header(#headers{}=Hdrs, {Header, Value}) ->
1238    set_header(Hdrs, Header, Value).
1239
1240set_header(#headers{}=Hdrs, connection, Value) ->
1241    Hdrs#headers{connection = Value};
1242set_header(#headers{}=Hdrs, {lower, "connection"}, Value) ->
1243    Hdrs#headers{connection = Value};
1244set_header(#headers{}=Hdrs, accept, Value) ->
1245    Hdrs#headers{accept = Value};
1246set_header(#headers{}=Hdrs, {lower, "accept"}, Value) ->
1247    Hdrs#headers{accept = Value};
1248set_header(#headers{}=Hdrs, host, Value) ->
1249    Hdrs#headers{host = Value};
1250set_header(#headers{}=Hdrs, {lower, "host"}, Value) ->
1251    Hdrs#headers{host = Value};
1252set_header(#headers{}=Hdrs, if_modified_since, Value) ->
1253    Hdrs#headers{if_modified_since = Value};
1254set_header(#headers{}=Hdrs, {lower, "if-modified-since"}, Value) ->
1255    Hdrs#headers{if_modified_since = Value};
1256set_header(#headers{}=Hdrs, if_match, Value) ->
1257    Hdrs#headers{if_match = Value};
1258set_header(#headers{}=Hdrs, {lower, "if-match"}, Value) ->
1259    Hdrs#headers{if_match = Value};
1260set_header(#headers{}=Hdrs, if_none_match, Value) ->
1261    Hdrs#headers{if_none_match = Value};
1262set_header(#headers{}=Hdrs, {lower, "if-none-match"}, Value) ->
1263    Hdrs#headers{if_none_match = Value};
1264set_header(#headers{}=Hdrs, if_range, Value) ->
1265    Hdrs#headers{if_range = Value};
1266set_header(#headers{}=Hdrs, {lower, "if-range"}, Value) ->
1267    Hdrs#headers{if_range = Value};
1268set_header(#headers{}=Hdrs, if_unmodified_since, Value) ->
1269    Hdrs#headers{if_unmodified_since = Value};
1270set_header(#headers{}=Hdrs, {lower, "if-unmodified-since"}, Value) ->
1271    Hdrs#headers{if_unmodified_since = Value};
1272set_header(#headers{}=Hdrs, range, Value) ->
1273    Hdrs#headers{range = Value};
1274set_header(#headers{}=Hdrs, {lower, "range"}, Value) ->
1275    Hdrs#headers{range = Value};
1276set_header(#headers{}=Hdrs, referer, Value) ->
1277    Hdrs#headers{referer = Value};
1278set_header(#headers{}=Hdrs, {lower, "referer"}, Value) ->
1279    Hdrs#headers{referer = Value};
1280set_header(#headers{}=Hdrs, user_agent, Value) ->
1281    Hdrs#headers{user_agent = Value};
1282set_header(#headers{}=Hdrs, {lower, "user-agent"}, Value) ->
1283    Hdrs#headers{user_agent = Value};
1284set_header(#headers{}=Hdrs, accept_ranges, Value) ->
1285    Hdrs#headers{accept_ranges = Value};
1286set_header(#headers{}=Hdrs, {lower, "accept-ranges"}, Value) ->
1287    Hdrs#headers{accept_ranges = Value};
1288set_header(#headers{}=Hdrs, cookie, Value) ->
1289    Hdrs#headers{cookie = Value};
1290set_header(#headers{}=Hdrs, {lower, "cookie"}, Value) ->
1291    Hdrs#headers{cookie = Value};
1292set_header(#headers{}=Hdrs, keep_alive, Value) ->
1293    Hdrs#headers{keep_alive = Value};
1294set_header(#headers{}=Hdrs, {lower, "keep-alive"}, Value) ->
1295    Hdrs#headers{keep_alive = Value};
1296set_header(#headers{}=Hdrs, location, Value) ->
1297    Hdrs#headers{location = Value};
1298set_header(#headers{}=Hdrs, {lower, "location"}, Value) ->
1299    Hdrs#headers{location = Value};
1300set_header(#headers{}=Hdrs, content_length, Value) ->
1301    Hdrs#headers{content_length = Value};
1302set_header(#headers{}=Hdrs, {lower, "content-length"}, Value) ->
1303    Hdrs#headers{content_length = Value};
1304set_header(#headers{}=Hdrs, content_type, Value) ->
1305    Hdrs#headers{content_type = Value};
1306set_header(#headers{}=Hdrs, {lower, "content-type"}, Value) ->
1307    Hdrs#headers{content_type = Value};
1308set_header(#headers{}=Hdrs, content_encoding, Value) ->
1309    Hdrs#headers{content_encoding = Value};
1310set_header(#headers{}=Hdrs, {lower, "content-encoding"}, Value) ->
1311    Hdrs#headers{content_encoding = Value};
1312set_header(#headers{}=Hdrs, authorization, Value) ->
1313    Hdrs#headers{authorization = Value};
1314set_header(#headers{}=Hdrs, {lower, "authorization"}, Value) ->
1315    Hdrs#headers{authorization = Value};
1316set_header(#headers{}=Hdrs, transfer_encoding, Value) ->
1317    Hdrs#headers{transfer_encoding = Value};
1318set_header(#headers{}=Hdrs, {lower, "transfer-encoding"}, Value) ->
1319    Hdrs#headers{transfer_encoding = Value};
1320set_header(#headers{}=Hdrs, x_forwarded_for, Value) ->
1321    Hdrs#headers{x_forwarded_for = Value};
1322set_header(#headers{}=Hdrs, {lower, "x-forwarded-for"}, Value) ->
1323    Hdrs#headers{x_forwarded_for = Value};
1324set_header(#headers{}=Hdrs, Header, Value) when is_atom(Header) ->
1325    set_header(Hdrs, atom_to_list(Header), Value);
1326set_header(#headers{}=Hdrs, Header, Value) when is_binary(Header) ->
1327    set_header(Hdrs, binary_to_list(Header), Value);
1328set_header(#headers{}=Hdrs, Header, Val) when is_binary(Val) ->
1329    set_header(Hdrs, {lower, string:to_lower(Header)}, binary_to_list(Val));
1330set_header(#headers{other=Other}=Hdrs, {lower, Header}, undefined) ->
1331    Handler = fun(_, true, Acc) ->
1332                      Acc;
1333                 (HdrVal, false, Acc) ->
1334                      [HdrVal|Acc]
1335              end,
1336    NewOther = fold_others(Header, Handler, Other, []),
1337    Hdrs#headers{other = lists:reverse(NewOther)};
1338set_header(#headers{other=Other}=Hdrs, {lower, Header}, Val) ->
1339    HdrName = erlang_header_name(Header),
1340    Handler = fun({http_header, Int, _, Rsv, _}, true, {Acc, _}) ->
1341                      {[{http_header, Int, HdrName, Rsv, Val}|Acc],true};
1342                 (HdrVal, false, {Acc, Found}) ->
1343                      {[HdrVal|Acc], Found}
1344              end,
1345    {NewOther0, Found} = fold_others(Header, Handler, Other, {[], false}),
1346    NewOther = case Found of
1347                   true ->
1348                       NewOther0;
1349                   false ->
1350                       [{http_header, 0, HdrName, undefined, Val}|NewOther0]
1351               end,
1352    Hdrs#headers{other = lists:reverse(NewOther)};
1353set_header(#headers{}=Hdrs, Header, undefined) ->
1354    set_header(Hdrs, {lower, string:to_lower(Header)}, undefined);
1355set_header(#headers{}=Hdrs, Header, Value) ->
1356    set_header(Hdrs, {lower, string:to_lower(Header)}, Value).
1357
1358merge_header(#headers{}=Hdrs, {Header, Value}) ->
1359    merge_header(Hdrs, Header, Value).
1360
1361merge_header(#headers{}=Hdrs, _Header, undefined) ->
1362    Hdrs;
1363merge_header(#headers{}=Hdrs, Header, Value) when is_atom(Header) ->
1364    merge_header(Hdrs, atom_to_list(Header), Value);
1365merge_header(#headers{}=Hdrs, Header, Value) when is_binary(Header) ->
1366    merge_header(Hdrs, binary_to_list(Header), Value);
1367merge_header(#headers{}=Hdrs, Header, Value) when is_binary(Value) ->
1368    merge_header(Hdrs, Header, binary_to_list(Value));
1369merge_header(Hdrs, {lower, "set-cookie"}=LHdr, Value) ->
1370    NewValue = case get_header(Hdrs, LHdr) of
1371                   undefined ->
1372                       {multi, [Value]};
1373                   {multi, MultiVal} ->
1374                       {multi, MultiVal ++ [Value]};
1375                   ExistingValue ->
1376                       {multi, [ExistingValue, Value]}
1377               end,
1378    set_header(Hdrs, LHdr, NewValue);
1379merge_header(Hdrs, {lower, _Header}=LHdr, Value) ->
1380    NewValue = case get_header(Hdrs, LHdr) of
1381                   undefined ->
1382                       Value;
1383                   ExistingValue ->
1384                       ExistingValue ++ ", " ++ Value
1385               end,
1386    set_header(Hdrs, LHdr, NewValue);
1387merge_header(#headers{}=Hdrs, Header, Value) ->
1388    merge_header(Hdrs, {lower, string:to_lower(Header)}, Value).
1389
1390get_header(#headers{}=Hdrs, connection) ->
1391    Hdrs#headers.connection;
1392get_header(#headers{}=Hdrs, {lower, "connection"}) ->
1393    Hdrs#headers.connection;
1394get_header(#headers{}=Hdrs, accept) ->
1395    Hdrs#headers.accept;
1396get_header(#headers{}=Hdrs, {lower, "accept"}) ->
1397    Hdrs#headers.accept;
1398get_header(#headers{}=Hdrs, host) ->
1399    Hdrs#headers.host;
1400get_header(#headers{}=Hdrs, {lower, "host"}) ->
1401    Hdrs#headers.host;
1402get_header(#headers{}=Hdrs, if_modified_since) ->
1403    Hdrs#headers.if_modified_since;
1404get_header(#headers{}=Hdrs, {lower, "if-modified-since"}) ->
1405    Hdrs#headers.if_modified_since;
1406get_header(#headers{}=Hdrs, if_match) ->
1407    Hdrs#headers.if_match;
1408get_header(#headers{}=Hdrs, {lower, "if-match"}) ->
1409    Hdrs#headers.if_match;
1410get_header(#headers{}=Hdrs, if_none_match) ->
1411    Hdrs#headers.if_none_match;
1412get_header(#headers{}=Hdrs, {lower, "if-none-match"}) ->
1413    Hdrs#headers.if_none_match;
1414get_header(#headers{}=Hdrs, if_range) ->
1415    Hdrs#headers.if_range;
1416get_header(#headers{}=Hdrs, {lower, "if-range"}) ->
1417    Hdrs#headers.if_range;
1418get_header(#headers{}=Hdrs, if_unmodified_since) ->
1419    Hdrs#headers.if_unmodified_since;
1420get_header(#headers{}=Hdrs, {lower, "if-unmodified-since"}) ->
1421    Hdrs#headers.if_unmodified_since;
1422get_header(#headers{}=Hdrs, range) ->
1423    Hdrs#headers.range;
1424get_header(#headers{}=Hdrs, {lower, "range"}) ->
1425    Hdrs#headers.range;
1426get_header(#headers{}=Hdrs, referer) ->
1427    Hdrs#headers.referer;
1428get_header(#headers{}=Hdrs, {lower, "referer"}) ->
1429    Hdrs#headers.referer;
1430get_header(#headers{}=Hdrs, user_agent) ->
1431    Hdrs#headers.user_agent;
1432get_header(#headers{}=Hdrs, {lower, "user-agent"}) ->
1433    Hdrs#headers.user_agent;
1434get_header(#headers{}=Hdrs, accept_ranges) ->
1435    Hdrs#headers.accept_ranges;
1436get_header(#headers{}=Hdrs, {lower, "accept-ranges"}) ->
1437    Hdrs#headers.accept_ranges;
1438get_header(#headers{}=Hdrs, cookie) ->
1439    Hdrs#headers.cookie;
1440get_header(#headers{}=Hdrs, {lower, "cookie"}) ->
1441    Hdrs#headers.cookie;
1442get_header(#headers{}=Hdrs, keep_alive) ->
1443    Hdrs#headers.keep_alive;
1444get_header(#headers{}=Hdrs, {lower, "keep-alive"}) ->
1445    Hdrs#headers.keep_alive;
1446get_header(#headers{}=Hdrs, location) ->
1447    Hdrs#headers.location;
1448get_header(#headers{}=Hdrs, {lower, "location"}) ->
1449    Hdrs#headers.location;
1450get_header(#headers{}=Hdrs, content_length) ->
1451    Hdrs#headers.content_length;
1452get_header(#headers{}=Hdrs, {lower, "content-length"}) ->
1453    Hdrs#headers.content_length;
1454get_header(#headers{}=Hdrs, content_type) ->
1455    Hdrs#headers.content_type;
1456get_header(#headers{}=Hdrs, {lower, "content-type"}) ->
1457    Hdrs#headers.content_type;
1458get_header(#headers{}=Hdrs, content_encoding) ->
1459    Hdrs#headers.content_encoding;
1460get_header(#headers{}=Hdrs, {lower, "content-encoding"}) ->
1461    Hdrs#headers.content_encoding;
1462get_header(#headers{}=Hdrs, authorization) ->
1463    Hdrs#headers.authorization;
1464get_header(#headers{}=Hdrs, {lower, "authorization"}) ->
1465    Hdrs#headers.authorization;
1466get_header(#headers{}=Hdrs, transfer_encoding) ->
1467    Hdrs#headers.transfer_encoding;
1468get_header(#headers{}=Hdrs, {lower, "transfer-encoding"}) ->
1469    Hdrs#headers.transfer_encoding;
1470get_header(#headers{}=Hdrs, x_forwarded_for) ->
1471    Hdrs#headers.x_forwarded_for;
1472get_header(#headers{}=Hdrs, {lower, "x-forwarded-for"}) ->
1473    Hdrs#headers.x_forwarded_for;
1474get_header(#headers{}=Hdrs, Header) when is_atom(Header) ->
1475    get_header(Hdrs, atom_to_list(Header));
1476get_header(#headers{}=Hdrs, Header) when is_binary(Header) ->
1477    get_header(Hdrs, binary_to_list(Header));
1478get_header(#headers{other = Other}, {lower, Header}) ->
1479    Handler = fun({http_header, _, _, _, Value}, true, _Acc) ->
1480                      throw(Value);
1481                 (_, false, Acc) ->
1482                      Acc
1483              end,
1484    catch fold_others(Header, Handler, Other, undefined);
1485get_header(#headers{}=Hdrs, Header) ->
1486    get_header(Hdrs, {lower, string:to_lower(Header)}).
1487
1488get_header(#headers{}=Hdrs, Header, Default) ->
1489    case get_header(Hdrs, Header) of
1490        undefined ->
1491            Default;
1492        Value ->
1493            Value
1494    end.
1495
1496delete_header(#headers{}=Hdrs, Header) ->
1497    set_header(Hdrs, Header, undefined).
1498
1499%% assumes that LowerHdr is already downcased
1500fold_others(LowerHdr, Handler, Other, StartAcc) ->
1501    lists:foldl(fun({http_header, _, Hdr, _, _}=HdrVal, Acc) ->
1502                        HdrNm = string:to_lower(
1503                                  if
1504                                      is_atom(Hdr) -> atom_to_list(Hdr);
1505                                      is_binary(Hdr) -> binary_to_list(Hdr);
1506                                      true -> Hdr
1507                                  end),
1508                        Handler(HdrVal, HdrNm == LowerHdr, Acc);
1509		   (_, Acc) ->
1510			Acc
1511                end, StartAcc, Other).
1512
1513erlang_header_name("cache-control")       -> 'Cache-Control';
1514erlang_header_name("date")                -> 'Date';
1515erlang_header_name("pragma")              -> 'Pragma';
1516erlang_header_name("upgrade")             -> 'Upgrade';
1517erlang_header_name("via")                 -> 'Via';
1518erlang_header_name("accept-charset")      -> 'Accept-Charset';
1519erlang_header_name("accept-encoding")     -> 'Accept-Encoding';
1520erlang_header_name("accept-language")     -> 'Accept-Language';
1521erlang_header_name("from")                -> 'From';
1522erlang_header_name("max-forwards")        -> 'Max-Forwards';
1523erlang_header_name("proxy-authorization") -> 'Proxy-Authorization';
1524erlang_header_name("age")                 -> 'Age';
1525erlang_header_name("proxy-authenticate")  -> 'Proxy-Authenticate';
1526erlang_header_name("public")              -> 'Public';
1527erlang_header_name("retry-after")         -> 'Retry-After';
1528erlang_header_name("server")              -> 'Server';
1529erlang_header_name("vary")                -> 'Vary';
1530erlang_header_name("warning")             -> 'Warning';
1531erlang_header_name("www-authenticate")    -> 'Www-Authenticate';
1532erlang_header_name("allow")               -> 'Allow';
1533erlang_header_name("content-base")        -> 'Content-Base';
1534erlang_header_name("content-encoding")    -> 'Content-Encoding';
1535erlang_header_name("content-language")    -> 'Content-Language';
1536erlang_header_name("content-location")    -> 'Content-Location';
1537erlang_header_name("content-md5")         -> 'Content-Md5';
1538erlang_header_name("content-range")       -> 'Content-Range';
1539erlang_header_name("etag")                -> 'Etag';
1540erlang_header_name("expires")             -> 'Expires';
1541erlang_header_name("last-modified")       -> 'Last-Modified';
1542erlang_header_name("set-cookie")          -> 'Set-Cookie';
1543erlang_header_name("set-cookie2")         -> 'Set-Cookie2';
1544erlang_header_name("proxy-connection")    -> 'Proxy-Connection';
1545erlang_header_name(Name)                  -> capitalize_header(Name).
1546
1547capitalize_header(Name) ->
1548    %% Before R16B Erlang capitalized words inside header names only for
1549    %% headers less than 20 characters long. In R16B that length was raised
1550    %% to 50. Using decode_packet lets us be portable.
1551    {ok, {http_header, _, Result, _, _}, _} =
1552        erlang:decode_packet(httph, list_to_binary([Name, <<": x\r\n\r\n">>]),
1553                             []),
1554    Result.
1555
1556reformat_request(#http_request{method = bad_request}) ->
1557    ["Bad request"];
1558reformat_request(Req) ->
1559    Path = case Req#http_request.path of
1560               {abs_path, AbsPath} ->
1561                   AbsPath;
1562               {absoluteURI, _Scheme, _Host0, _Port, RawPath} ->
1563                   RawPath
1564           end,
1565    {Maj, Min} = Req#http_request.version,
1566    [yaws:to_list(Req#http_request.method), " ", Path," HTTP/",
1567     integer_to_list(Maj),".", integer_to_list(Min)].
1568
1569
1570reformat_response(Resp) ->
1571    {Maj,Min} = Resp#http_response.version,
1572    ["HTTP/",integer_to_list(Maj),".", integer_to_list(Min),
1573     " ", integer_to_list(Resp#http_response.status),
1574     " ", Resp#http_response.phrase].
1575
1576
1577
1578%% stringify the scheme://host[:port] part of a #url
1579reformat_url(U) ->
1580    [yaws:to_string(U#url.scheme),
1581     "://",
1582     U#url.host,
1583     if
1584         U#url.port == undefined ->
1585             [];
1586         true ->
1587             [$: | integer_to_list(U#url.port)]
1588     end].
1589
1590set_content_type(MimeType) ->
1591    {header, {content_type, MimeType}}.
1592
1593
1594%% returns a #url{} record
1595parse_url(Str) ->
1596    parse_url(Str, strict).
1597
1598parse_url(Str, Strict) ->
1599    case Str of
1600        "http://" ++ Rest ->
1601            parse_url(host, Strict, #url{scheme = http}, Rest, []);
1602        "https://" ++ Rest ->
1603            parse_url(host, Strict, #url{scheme = https}, Rest, []);
1604        "ftp://" ++ Rest ->
1605            parse_url(host, Strict, #url{scheme = ftp}, Rest, []);
1606        "file://" ++ Rest ->
1607            parse_url(host, Strict, #url{scheme = file}, Rest, []);
1608        _ when Strict == sloppy ->
1609            parse_url(host, Strict, #url{scheme = undefined}, Str, [])
1610    end.
1611
1612
1613parse_url(host, Strict, U, Str, Ack) ->
1614    case Str of
1615        [] ->
1616            U#url{host = lists:reverse(Ack),
1617                  path = "/"
1618                 };
1619        [$/|Tail] ->
1620            U2 = U#url{host = lists:reverse(Ack)},
1621            parse_url(path, Strict, U2, Tail,"/");
1622        [$:|T] ->
1623            U2 = U#url{host = lists:reverse(Ack)},
1624            parse_url(port, Strict, U2, T,[]);
1625        [$[|T] ->
1626            parse_url(ipv6, Strict, U, T, [$[]);
1627        [H|T] ->
1628            parse_url(host, Strict, U, T, [H|Ack])
1629    end;
1630parse_url(ipv6, Strict, U, Str, Ack) ->
1631    case Str of
1632        [$]] ->
1633            U#url{host = lists:reverse([$]|Ack]),
1634                  path = "/"
1635                 };
1636        [$], $/|T] ->
1637            U2 = U#url{host = lists:reverse([$]|Ack])},
1638            parse_url(path, Strict, U2, T,"/");
1639        [$], $:|T] ->
1640            U2 = U#url{host = lists:reverse([$]|Ack])},
1641            parse_url(port, Strict, U2, T,[]);
1642        [H|T] ->
1643            parse_url(ipv6, Strict, U, T, [H|Ack])
1644    end;
1645parse_url(port, Strict, U, Str, Ack) ->
1646    case Str of
1647        [] ->
1648            U#url{port = list_to_integer(lists:reverse(Ack)),
1649                  path = "/"};
1650        [$/|T] ->
1651            U2 = U#url{port = list_to_integer(lists:reverse(Ack))},
1652            parse_url(path, Strict, U2, T,"/");
1653        [H|T] ->
1654            parse_url(port, Strict, U,T,[H|Ack])
1655    end;
1656parse_url(path, Strict, U, Str, Ack) ->
1657    case Str of
1658        [] ->
1659            U#url{path = lists:reverse(Ack)};
1660        [$?|T] ->
1661            U#url{path = lists:reverse(Ack),
1662                  querypart = T};
1663        [H|T] ->
1664            parse_url(path, Strict, U, T, [H|Ack])
1665    end.
1666
1667
1668%% used to construct redir headers from partial URLs such
1669%% as e.g. /foo/bar
1670
1671format_partial_url(Url, SC) ->
1672    [if
1673         Url#url.scheme == undefined ->
1674             yaws:redirect_scheme(SC);
1675         true ->
1676             yaws:to_string(Url#url.scheme) ++ "://"
1677     end,
1678     if
1679         Url#url.host == undefined orelse Url#url.host == [] ->
1680             yaws:redirect_host(SC, undefined);
1681         true ->
1682             Url#url.host
1683     end,
1684     if
1685         Url#url.port == undefined ->
1686             [];
1687         true  ->
1688             [$: | integer_to_list(Url#url.port)]
1689     end,
1690     Url#url.path,
1691     if
1692         Url#url.querypart == [] ->
1693             [];
1694         true ->
1695             [$?|Url#url.querypart]
1696     end
1697    ].
1698
1699
1700format_url(Url) when is_record(Url, url) ->
1701    [
1702     if
1703         Url#url.scheme == undefined ->
1704             "http://";
1705         true ->
1706             yaws:to_string(Url#url.scheme) ++ "://"
1707     end,
1708     Url#url.host,
1709     if
1710         Url#url.port == undefined ->
1711             [];
1712         true  ->
1713             [$: | integer_to_list(Url#url.port)]
1714     end,
1715     Url#url.path,
1716     if
1717         Url#url.querypart == [] ->
1718             [];
1719         true ->
1720             [$?|Url#url.querypart]
1721     end
1722    ].
1723
1724is_absolute_URI([C|T]) when ((C>=$a) and (C=<$z)) or ((C>=$A) and (C=<$Z))->
1725    is_abs_URI1(T);
1726is_absolute_URI(_) ->
1727    false.
1728
1729is_abs_URI1([$:|_]) ->
1730    true;
1731is_abs_URI1([C|T]) when
1732((C>=$a) and (C=<$z))
1733or ((C>=$A) and (C=<$Z))
1734or ((C>=$0) and (C=<$9))
1735or (C==$+) or (C==$-) or (C==$.) ->
1736    is_abs_URI1(T);
1737is_abs_URI1(_) ->
1738    false.
1739
1740
1741%% ------------------------------------------------------------
1742%% simple erlang term representation of HTML:
1743%% EHTML = [EHTML] | {Tag, Attrs, Body} | {Tag, Attrs} | {Tag} |
1744%%         {Module, Fun, [Args]} | fun/0 |
1745%%         binary() | character()
1746%% Tag   = atom()
1747%% Attrs = [{Key, Value}]
1748%% Key   = atom()
1749%% Value = string() | binary() | atom() | integer() | float() |
1750%%         {Module, Fun, [Args]} | fun/0
1751%% Body  = EHTML
1752
1753ehtml_expand(Ch) when Ch >= 0, Ch =< 255 -> Ch; %yaws_api:htmlize_char(Ch);
1754ehtml_expand(Bin) when is_binary(Bin) -> Bin; % yaws_api:htmlize(Bin);
1755
1756ehtml_expand({ssi,File, Del, Bs}) ->
1757    case yaws_server:ssi(File, Del, Bs) of
1758        {error, Rsn} ->
1759            io_lib:format("ERROR: ~p~n",[Rsn]);
1760        X ->
1761            X
1762    end;
1763
1764%%!todo (low priority) - investigate whether tail-recursion would be of any
1765%% benefit here instead of the current ehtml_expand(Body) recursion.
1766%%                - provide a tail_recursive version & add a file in the
1767%% benchmarks folder to measure it.
1768                                                %
1769ehtml_expand({Tag}) ->
1770    ["<", atom_to_list(Tag), ehtml_end_tag(Tag)];
1771ehtml_expand({pre_html, X}) -> X;
1772ehtml_expand({Mod, Fun, Args})
1773  when is_atom(Mod), is_atom(Fun), is_list(Args) ->
1774    ehtml_expand(Mod:Fun(Args));
1775ehtml_expand({Tag, Attrs}) ->
1776    NL = ehtml_nl(Tag),
1777    [NL, "<", atom_to_list(Tag), ehtml_attrs(Attrs), ehtml_end_tag(Tag)];
1778ehtml_expand({Tag, Attrs, Body}) when is_atom(Tag) ->
1779    Ts = atom_to_list(Tag),
1780    NL = ehtml_nl(Tag),
1781    [NL, "<", Ts, ehtml_attrs(Attrs), ">", ehtml_expand(Body), "</", Ts, ">"];
1782ehtml_expand([H|T]) -> [ehtml_expand(H)|ehtml_expand(T)];
1783ehtml_expand([]) -> [];
1784ehtml_expand(Fun) when is_function(Fun) ->
1785    ehtml_expand(Fun()).
1786
1787
1788ehtml_attrs([]) -> [];
1789ehtml_attrs([Attribute|Tail]) when is_atom(Attribute) ->
1790    [[$ |atom_to_list(Attribute)]|ehtml_attrs(Tail)];
1791ehtml_attrs([Attribute|Tail]) when is_list(Attribute) ->
1792    [" ", Attribute|ehtml_attrs(Tail)];
1793ehtml_attrs([{Name, {Mod, Fun, Args}} | Tail])
1794  when is_atom(Mod), is_atom(Fun), is_list(Args) ->
1795    ehtml_attrs([{Name,  Mod:Fun(Args)} | Tail]);
1796ehtml_attrs([{Name, Value} | Tail]) when is_function(Value) ->
1797    ehtml_attrs([{Name, Value()} | Tail]);
1798ehtml_attrs([{Name, Value} | Tail]) ->
1799    ValueString = [$", value2string(Value), $"],
1800    [[$ |atom_to_list(Name)], [$=|ValueString]|ehtml_attrs(Tail)];
1801ehtml_attrs([{check, Name, {Mod, Fun, Args}} | Tail])
1802  when is_atom(Mod), is_atom(Fun), is_list(Args) ->
1803    ehtml_attrs([{check, Name,  Mod:Fun(Args)} | Tail]);
1804ehtml_attrs([{check, Name, Value} | Tail]) when is_function(Value) ->
1805    ehtml_attrs([{check, Name, Value()} | Tail]);
1806ehtml_attrs([{check, Name, Value} | Tail]) ->
1807    Val = value2string(Value),
1808    Q = case deepmember($", Val) of
1809            true -> $';
1810            false -> $"
1811        end,
1812    ValueString = [Q,Val,Q],
1813    [[$ |atom_to_list(Name)], [$=|ValueString]|ehtml_attrs(Tail)].
1814
1815value2string(Atom) when is_atom(Atom) -> atom_to_list(Atom);
1816value2string(String) when is_list(String) -> String;
1817value2string(Binary) when is_binary(Binary) -> Binary;
1818value2string(Integer) when is_integer(Integer) -> integer_to_list(Integer);
1819value2string(Float) when is_float(Float) -> float_to_list(Float).
1820
1821
1822
1823%% Tags for which we must not add extra white space.
1824%% FIXME: should there be anything more in this list?
1825
1826ehtml_nl(a) -> [];
1827ehtml_nl(br) -> [];
1828ehtml_nl(span) -> [];
1829ehtml_nl(em) -> [];
1830ehtml_nl(strong) -> [];
1831ehtml_nl(dfn) -> [];
1832ehtml_nl(code) -> [];
1833ehtml_nl(samp) -> [];
1834ehtml_nl(kbd) -> [];
1835ehtml_nl(var) -> [];
1836ehtml_nl(cite) -> [];
1837ehtml_nl(abbr) -> [];
1838ehtml_nl(acronym) -> [];
1839ehtml_nl(q) -> [];
1840ehtml_nl(sub) -> [];
1841ehtml_nl(sup) -> [];
1842ehtml_nl(ins) -> [];
1843ehtml_nl(del) -> [];
1844ehtml_nl(img) -> [];
1845ehtml_nl(tt) -> [];
1846ehtml_nl(i) -> [];
1847ehtml_nl(b) -> [];
1848ehtml_nl(big) -> [];
1849ehtml_nl(small) -> [];
1850ehtml_nl(strike) -> [];
1851ehtml_nl(s) -> [];
1852ehtml_nl(u) -> [];
1853ehtml_nl(font) -> [];
1854ehtml_nl(basefont) -> [];
1855ehtml_nl(input) -> [];
1856ehtml_nl(button) -> [];
1857ehtml_nl(object) -> [];
1858ehtml_nl(_) -> "\n".
1859
1860
1861%% Void elements must not have an end tag (</tag>) in HTML5, while for most
1862%% elements a proper end tag (<tag></tag>, not <tag />) is mandatory.
1863%%
1864%% http://www.w3.org/TR/html5/syntax.html#void-elements
1865%% http://www.w3.org/TR/html5/syntax.html#syntax-tag-omission
1866
1867-define(self_closing, " />"). % slash ignored in HTML5
1868
1869ehtml_end_tag(area) -> ?self_closing;
1870ehtml_end_tag(base) -> ?self_closing;
1871ehtml_end_tag(br) -> ?self_closing;
1872ehtml_end_tag(col) -> ?self_closing;
1873ehtml_end_tag(embed) -> ?self_closing;
1874ehtml_end_tag(hr) -> ?self_closing;
1875ehtml_end_tag(img) -> ?self_closing;
1876ehtml_end_tag(input) -> ?self_closing;
1877ehtml_end_tag(keygen) -> ?self_closing;
1878ehtml_end_tag(link) -> ?self_closing;
1879ehtml_end_tag(meta) -> ?self_closing;
1880ehtml_end_tag(param) -> ?self_closing;
1881ehtml_end_tag(source) -> ?self_closing;
1882ehtml_end_tag(track) -> ?self_closing;
1883ehtml_end_tag(wbr) -> ?self_closing;
1884ehtml_end_tag(Tag) -> ["></", atom_to_list(Tag), ">"].
1885
1886
1887%% ------------------------------------------------------------
1888%% ehtml_expander/1: an EHTML optimizer
1889%%
1890%% This is an optimization for generating the same EHTML multiple times with
1891%% only small differences, by using fast re-usable templates that contain
1892%% variables. The variables are atoms starting with a dollar sign, like
1893%% '$myvar'. There are two functions: ehtml_expander/1 to create an optimized
1894%% EHTML template, then ehtml_apply/2 takes a template and a dictionary of
1895%% variable values and generates the actual HTML.
1896%%
1897%% If you are spending a lot of time regenerating similar EHTML fragments then
1898%% this is for you.
1899%%
1900%% Variables can appear in three places:
1901%% - As a body element, where you would normally have a tag. The values of
1902%%   these variables are expanded as EHTML.
1903%% - As the name or value of an attribute. The values of these variables are
1904%%   strings.
1905%% - As the CDR of an attribute list. The values of these variables are
1906%%   key-value lists of more attributes.
1907%%
1908%% See ehtml_expander_test/0 for an example.
1909%%
1910%% The approach is inspired by the way that Yaws already treats .yaws files,
1911%% and the article ``A Hacker's Introduction To Partial Evaluation'' by Darius
1912%% Bacon (cool guy), http://www.lisp-p.org/htdocs/peval/peval.cgi
1913%%
1914%% (For now I flatter myself that this is some kind of partial evaluator, but
1915%% I don't really know :-) -luke)
1916
1917ehtml_expander(X) ->
1918    ehtml_expander_compress(flatten(ehtml_expander(X, [], [])), []).
1919
1920%% Returns a deep list of text and variable references (atoms)
1921
1922%% Text
1923ehtml_expander(Ch, Before, After) when Ch >= 0, Ch =< 255 ->
1924    ehtml_expander_done(yaws_api:htmlize_char(Ch), Before, After);
1925ehtml_expander(Bin, Before, After) when is_binary(Bin) ->
1926    ehtml_expander_done(yaws_api:htmlize(Bin), Before, After);
1927
1928ehtml_expander({ssi,File, Del, Bs}, Before, After) ->
1929    Str = case yaws_server:ssi(File, Del, Bs) of
1930              {error, Rsn} ->
1931                  io_lib:format("ERROR: ~p~n",[Rsn]);
1932              X ->
1933                  X
1934          end,
1935    ehtml_expander_done(Str, Before, After);
1936
1937ehtml_expander({pre_html, X}, Before, After) ->
1938    ehtml_expander_done(X, Before, After);
1939%% Tags
1940ehtml_expander({Tag}, Before, After) ->
1941    ehtml_expander_done(["<", atom_to_list(Tag), ehtml_end_tag(Tag)],
1942                        Before, After);
1943ehtml_expander({Tag, Attrs}, Before, After) ->
1944    NL = ehtml_nl(Tag),
1945    ehtml_expander_done([NL, "<", atom_to_list(Tag), ehtml_attrs(Attrs),
1946                         ehtml_end_tag(Tag)],
1947                        Before,
1948                        After);
1949ehtml_expander({Tag, Attrs, Body}, Before, After) ->
1950    ehtml_expander(Body,
1951                   [["\n<", atom_to_list(Tag),
1952                     ehtml_attrs_expander(Attrs), ">"]|
1953                    Before],
1954                   ["</", atom_to_list(Tag), ">"|After]);
1955%% Variable references
1956ehtml_expander(Var, Before, After) when is_atom(Var) ->
1957    [reverse(Before), {ehtml, ehtml_var_name(Var)}, After];
1958%% Lists
1959ehtml_expander([H|T], Before, After) ->
1960    ehtml_expander(T, [ehtml_expander(H, [], [])|Before], After);
1961ehtml_expander([], Before, After) ->
1962    ehtml_expander_done("", Before, After).
1963
1964%% Expander for attributes. The attribute name and value can each be a
1965%% variable reference.
1966ehtml_attrs_expander([]) -> "";
1967ehtml_attrs_expander([{Var,Val}|T]) ->
1968    [[" ",
1969      ehtml_attr_part_expander(Var),
1970      "=",
1971      "\"", ehtml_attr_part_expander(Val), "\""]|
1972     ehtml_attrs_expander(T)];
1973ehtml_attrs_expander([Var|T]) ->
1974    [[" ",
1975      ehtml_attr_part_expander(Var)]|
1976     ehtml_attrs_expander(T)];
1977ehtml_attrs_expander(Var) when is_atom(Var) ->
1978    %% Var in the cdr of an attribute list
1979    [{ehtml_attrs, ehtml_var_name(Var)}].
1980
1981ehtml_attr_part_expander(A) when is_atom(A) ->
1982    case atom_to_list(A) of
1983        [$$|_Rest] -> {preformatted, ehtml_var_name(A)};
1984        Other -> Other
1985    end;
1986ehtml_attr_part_expander(I) when is_integer(I) -> integer_to_list(I);
1987ehtml_attr_part_expander(S) when is_list(S) -> S.
1988
1989ehtml_expander_done(X, Before, After) -> [reverse([X|Before]), After].
1990
1991%% Compress an EHTML expander, converting all adjacent bits of text into
1992%% binaries.
1993%% Returns: [binary() | {ehtml, Var} | {preformatted, Var}, {ehtml_attrs, Var}]
1994%% Var = atom()
1995ehtml_expander_compress([Tag|T], Acc) when is_tuple(Tag) ->
1996    [list_to_binary(reverse(Acc)), Tag | ehtml_expander_compress(T, [])];
1997ehtml_expander_compress([], Acc) -> [list_to_binary(reverse(Acc))];
1998ehtml_expander_compress([H|T], Acc) when is_integer(H) ->
1999    ehtml_expander_compress(T, [H|Acc]).
2000
2001%% Apply an expander with the variable bindings in Env.  Env is a list of
2002%% {VarName, Value} tuples, where VarName is an atom and Value is an ehtml
2003%% term.
2004ehtml_apply(Expander, Env) -> [ehtml_eval(X, Env) || X <- Expander].
2005
2006ehtml_eval(Bin, _Env) when is_binary(Bin) -> Bin;
2007ehtml_eval({Type, Var}, Env) ->
2008    case lists:keysearch(Var, 1, Env) of
2009        false -> erlang:error({ehtml_unbound, Var});
2010        {value, {Var, Val}} ->
2011            case Type of
2012                ehtml -> ehtml_expand(Val);
2013                preformatted -> Val;
2014                ehtml_attrs -> ehtml_attrs(Val)
2015            end
2016    end.
2017
2018%% Get the name part of a variable reference.
2019%% e.g. ehtml_var_name('$foo') -> foo.
2020ehtml_var_name(A) when is_atom(A) ->
2021    case atom_to_list(A) of
2022        [$$|Rest] -> list_to_atom(Rest);
2023        _Other -> erlang:error({bad_ehtml_var_name, A})
2024    end.
2025
2026ehtml_expander_test() ->
2027    %% Expr is a template containing variables.
2028    Expr = {html, [{title, '$title'}],
2029            {body, [],
2030             [{h1, [], '$heading'},
2031              '$text']}},
2032    %% Expand is an expander that can be used to quickly generate the HTML
2033    %% specified in Expr.
2034    Expand = ehtml_expander(Expr),
2035    %% Bs{1,2} are lists of variable bindings to fill in the gaps in the
2036    %% template. We can reuse the template on many sets of bindings, and this
2037    %% is much faster than doing a full ehtml of the whole page each time.
2038    Bs1 = [{title, "First page"},
2039           {heading, "Heading"},
2040           {text, {pre_html, "<b>My text!</b>"}}],
2041    Bs2 = [{title, "Second page"},
2042           {heading, "Foobar"},
2043           {text, {b, [], "My text again!"}}],
2044    %% Page1 and Page2 are generated from the template. They are I/O lists
2045    %% (i.e. deep lists of strings and binaries, ready to ship)
2046    Page1 = ehtml_apply(Expand, Bs1),
2047    Page2 = ehtml_apply(Expand, Bs2),
2048    %% We return the two pages as strings, plus the actual expander (which is
2049    %% an "opaque" data structure, but maybe interesting to see.)
2050    {binary_to_list(list_to_binary(Page1)),
2051     binary_to_list(list_to_binary(Page2)),
2052     Expand}.
2053
2054
2055%% call_cgi calls the script `Scriptfilename' (full path).  If
2056%% `Exefilename' is given, it is the executable to handle this,
2057%% otherwise `Scriptfilame' is assumed to be executable itself.
2058%%
2059%% Note however, that these functions usually generate stream content.
2060%% (If you have good use for a version generating {content, _, _}
2061%% instead, contact carsten@codimi.de)
2062%%
2063%% Also note, that they may return `get_more' and expect to be called
2064%% again.
2065
2066call_cgi(Arg, Scriptfilename) ->
2067    yaws_cgi:call_cgi(Arg, Scriptfilename).
2068
2069call_cgi(Arg, Exefilename, Scriptfilename) ->
2070    yaws_cgi:call_cgi(Arg, Exefilename, Scriptfilename).
2071
2072%% call_fci_responder issues a responder role call to the FastCGI
2073%% application server. It returns the same return value as out/1.
2074%%
2075%% call_fci_authorizer issues a authorizer role call to the FastCGI
2076%% application server. It returns:
2077%%
2078%% {denied, Out} : Access is denied. Out is the same return value as
2079%% out/1.
2080%%
2081%% {allowed, Variables} : Access is allowed. Variables is a list of
2082%% environment variables returned by the authorization server using
2083%% Variable-XXX: YYY headers.
2084%%
2085%% Note: the FastCGI filter role is not yet supported.
2086%%
2087%% The following information is taken from the server configuration:
2088%% - The hostname (or address) and port number of the application server.
2089%% - Extra CGI variables.
2090%% - Trace FastCGI protocol messages?
2091%% - Log application server error messages?
2092%%
2093%% The caller can optionally provide an Options argument which supports
2094%% the following options. These override the defaults taken from the
2095%% server config.
2096%%
2097%% {app_server_host, string() | ip_address()} : The hostname or IP address
2098%% of the application server.
2099%%
2100%% {app_server_port, int()} : The TCP port number of the application server.
2101%%
2102%% {path_info, string()} : Override the pathinfo string from Arg.
2103%%
2104%% {extra_env, [{string()|binary(), string()|binary()}]} : Extra
2105%% environment variables to be passed to the application server, as a list
2106%% of name-value pairs.
2107%%
2108%% trace_protocol : Trace FastCGI protocol messages.
2109%%
2110%% log_app_error : Log application errors (output to stderr and non-zero
2111%% exit value).
2112%%
2113call_fcgi_responder(Arg) ->
2114    yaws_cgi:call_fcgi_responder(Arg).
2115
2116call_fcgi_responder(Arg, Options) ->
2117    yaws_cgi:call_fcgi_responder(Arg, Options).
2118
2119call_fcgi_authorizer(Arg) ->
2120    yaws_cgi:call_fcgi_authorizer(Arg).
2121
2122call_fcgi_authorizer(Arg, Options) ->
2123    yaws_cgi:call_fcgi_authorizer(Arg, Options).
2124
2125%%
2126
2127deepmember(_C,[]) ->
2128    false;
2129deepmember(C,[C|_Cs]) ->
2130    true;
2131deepmember(C,[L|Cs]) when is_list(L) ->
2132    case deepmember(C,L) of
2133        true  -> true;
2134        false -> deepmember(C,Cs)
2135    end;
2136deepmember(C,[N|Cs]) when C /= N ->
2137    deepmember(C, Cs);
2138deepmember(_C,<<>>) ->
2139    false;
2140deepmember(C, <<C,_Cs/binary>>) ->
2141    true;
2142deepmember(C, <<_,Cs/binary>>) ->
2143    deepmember(C, Cs).
2144
2145
2146%%  . Parse a Set-Cookie header, following the RFC6265:
2147%%
2148%% "Set-Cookie: " set-cookie-string
2149%%    set-cookie-string = cookie-pair *( ";" SP cookie-av )
2150%%    cookie-pair       = cookie-name "=" cookie-value
2151%%    cookie-name       = token
2152%%    cookie-value      = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
2153%%    cookie-octet      = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
2154%%    token             = <token, defined in [RFC2616], Section 2.2>
2155%%
2156%%    cookie-av         = expires-av / max-age-av / domain-av / path-av /
2157%%                        secure-av / httponly-av / extension-av
2158%%    expires-av        = "Expires=" <rfc1123-date, defined in [RFC2616]>
2159%%    max-age-av        = "Max-Age=" [1-9] *DIGIT
2160%%    domain-av         = "Domain=" <subdomain> ; defined in [RFC1034]
2161%%    path-av           = "Path=" <any CHAR except CTLs or ";">
2162%%    secure-av         = "Secure"
2163%%    httponly-av       = "HttpOnly"
2164%%    extension-av      = <any CHAR except CTLs or ";">
2165%%
2166%% NOTE: in RFC2109 and RFC2965, multiple cookies, separated by comma, can be
2167%% defined in a single header. So, To be backward compatible with these RFCs,
2168%% comma is forbidden in 'path-av' and 'extension-av' except for double-quoted
2169%% value.
2170%%
2171%%
2172%%  . Parse a Cookie header, following the RFC6265:
2173%%
2174%% "Cookie: " cookie-string
2175%%    cookie-string = cookie-pair *( ";" SP cookie-pair )
2176%%
2177%% NOTE: To be backward compatible with RFCs, comma is considered as a cookie
2178%% separator, like semicolon.
2179%%
2180parse_set_cookie(Str) ->
2181    parse_set_cookie(Str, []).
2182
2183parse_set_cookie([], [SetCookie]) ->
2184    SetCookie;
2185parse_set_cookie([], SetCookies) ->
2186    lists:reverse(SetCookies);
2187parse_set_cookie(Str, SetCookies) ->
2188    case do_parse_set_cookie(Str) of
2189        {#setcookie{extensions=Exts}=C0, Rest} ->
2190            C1 = C0#setcookie{extensions=lists:reverse(Exts)},
2191            parse_set_cookie(Rest, [C1|SetCookies]);
2192        error ->
2193            []
2194    end.
2195
2196
2197do_parse_set_cookie(Str) ->
2198    {Key, Rest0} = parse_cookie_key(skip_space(Str), []),
2199    case yaws:to_lower(Key) of
2200        [] ->
2201            error;
2202        K ->
2203            Cookie0 = #setcookie{key=K, quoted=false},
2204            case skip_space(Rest0) of
2205                [$=|Rest1] ->
2206                    {V, Q, Rest2} = parse_cookie_value(skip_space(Rest1)),
2207                    Cookie1 = Cookie0#setcookie{value=V, quoted=Q},
2208                    parse_set_cookie_result(Cookie1, skip_space(Rest2));
2209                [$;|Rest1] -> parse_set_cookie_options(Rest1, Cookie0);
2210                [$,|Rest1] -> {Cookie0, Rest1};
2211                []         -> {Cookie0, []};
2212                _          -> error
2213            end
2214    end.
2215
2216parse_set_cookie_options(Str, Cookie0) ->
2217    {Key, Rest0} = parse_cookie_key(skip_space(Str), []),
2218    case yaws:to_lower(Key) of
2219        [] ->
2220            {Cookie0, Rest0};
2221        "domain" ->
2222            case skip_space(Rest0) of
2223                [$=|Rest1] ->
2224                    {V,_,Rest2} = parse_set_cookie_domain(skip_space(Rest1),[]),
2225                    Cookie1 = Cookie0#setcookie{domain=V},
2226                    parse_set_cookie_result(Cookie1, skip_space(Rest2));
2227                [$;|Rest1] -> parse_set_cookie_options(Rest1, Cookie0);
2228                [$,|Rest1] -> {Cookie0, Rest1};
2229                []         -> {Cookie0, []};
2230                _          -> error
2231            end;
2232        "max-age" ->
2233            case skip_space(Rest0) of
2234                [$=|Rest1] ->
2235                    {V,_,Rest2} = parse_set_cookie_maxage(skip_space(Rest1),[]),
2236                    Cookie1 = Cookie0#setcookie{max_age=V},
2237                    parse_set_cookie_result(Cookie1, skip_space(Rest2));
2238                [$;|Rest1] -> parse_set_cookie_options(Rest1, Cookie0);
2239                [$,|Rest1] -> {Cookie0, Rest1};
2240                []         -> {Cookie0, []};
2241                _          -> error
2242            end;
2243        "expires" ->
2244            case skip_space(Rest0) of
2245                [$=|Rest1] ->
2246                    {V, _, Rest2} = parse_set_cookie_expires(skip_space(Rest1)),
2247                    Cookie1 = Cookie0#setcookie{expires=V},
2248                    parse_set_cookie_result(Cookie1, skip_space(Rest2));
2249                [$;|Rest1] -> parse_set_cookie_options(Rest1, Cookie0);
2250                [$,|Rest1] -> {Cookie0, Rest1};
2251                []         -> {Cookie0, []};
2252                _          -> error
2253            end;
2254        "path" ->
2255            case skip_space(Rest0) of
2256                [$=|Rest1] ->
2257                    {V, _, Rest2} = parse_cookie_value(skip_space(Rest1)),
2258                    Cookie1 = Cookie0#setcookie{path=V},
2259                    parse_set_cookie_result(Cookie1, skip_space(Rest2));
2260                [$;|Rest1] -> parse_set_cookie_options(Rest1, Cookie0);
2261                [$,|Rest1] -> {Cookie0, Rest1};
2262                []         -> {Cookie0, []};
2263                _          -> error
2264            end;
2265        "secure" ->
2266            Cookie1 = Cookie0#setcookie{secure=true},
2267            parse_set_cookie_result(Cookie1, skip_space(Rest0));
2268        "httponly" ->
2269            Cookie1 = Cookie0#setcookie{http_only=true},
2270            parse_set_cookie_result(Cookie1, skip_space(Rest0));
2271        K ->
2272            Exts = Cookie0#setcookie.extensions,
2273            case skip_space(Rest0) of
2274                [$=|Rest1] ->
2275                    {V, Q, Rest2} = parse_cookie_value(skip_space(Rest1)),
2276                    Cookie1 = Cookie0#setcookie{extensions=[{K,V,Q}|Exts]},
2277                    parse_set_cookie_result(Cookie1, skip_space(Rest2));
2278                [$;|Rest1] ->
2279                    Cookie1 = Cookie0#setcookie{
2280                                extensions=[{K,undefined,false}|Exts]
2281                               },
2282                    parse_set_cookie_options(Rest1, Cookie1);
2283                [$,|Rest1] ->
2284                    Cookie1 = Cookie0#setcookie{
2285                                extensions=[{K,undefined,false}|Exts]
2286                               },
2287                    {Cookie1, Rest1};
2288                [] ->
2289                    Cookie1 = Cookie0#setcookie{
2290                                extensions=[{K,undefined,false}|Exts]
2291                               },
2292                    {Cookie1, []};
2293                _ ->
2294                    error
2295            end
2296    end.
2297
2298
2299parse_set_cookie_domain([C|_]=Rest, []) when C < $A orelse C > $Z orelse
2300                                             C < $a orelse C > $z orelse
2301                                             C /= $. ->
2302    parse_cookie_value(Rest);
2303parse_set_cookie_domain([C|_]=Rest, [_|_]=Acc) when C < $0 orelse C > $9 orelse
2304                                                    C < $A orelse C > $Z orelse
2305                                                    C < $a orelse C > $z orelse
2306                                                    C /= $. orelse C /= $- ->
2307    {lists:reverse(Acc), false, Rest};
2308parse_set_cookie_domain([], Acc) ->
2309    {lists:reverse(Acc), false, []};
2310parse_set_cookie_domain([C|T], Acc) ->
2311    parse_set_cookie_domain(T, [C|Acc]).
2312
2313
2314parse_set_cookie_maxage([C|_]=Rest, []) when C < $1 orelse C > $9 ->
2315    parse_cookie_value(Rest);
2316parse_set_cookie_maxage([C|_]=Rest, [_|_]=Acc) when C < $0 orelse C > $9 ->
2317    {lists:reverse(Acc), false, Rest};
2318parse_set_cookie_maxage([], Acc) ->
2319    {lists:reverse(Acc), false, []};
2320parse_set_cookie_maxage([C|T], Acc) ->
2321    parse_set_cookie_maxage(T, [C|Acc]).
2322
2323
2324%% First of all, try to parse valid rfc1123 date (faster), then use a regex
2325%% (more permissive)
2326parse_set_cookie_expires([D,A,Y,$,,$\s,D1,D2,SEP,M,O,N,SEP,Y1,Y2,Y3,Y4,$\s,
2327                          H1,H2,$:,M1,M2,$:,S1,S2,$\s,Z1,Z2,Z3|Rest])
2328  when SEP =:= $- orelse SEP =:= $\s ->
2329    {[D,A,Y,$,,$\s,D1,D2,SEP,M,O,N,SEP,Y1,Y2,Y3,Y4,$\s,
2330      H1,H2,$:,M1,M2,$:,S1,S2,$\s,Z1,Z2,Z3], false, Rest};
2331parse_set_cookie_expires(Str) ->
2332    RE = "^("
2333        "(?:[a-zA-Z]+,\s+)?"                    %% Week day
2334        "[0-9]+(?:\s|-)[a-zA-Z]+(?:\s|-)[0-9]+" %% DD Month YYYY
2335        "\s+[0-9]+:[0-9]+:[0-9]+"               %% hh:mm:ss
2336        "(?:\s+[a-zA-Z]+)?"                     %% timezone
2337        ")"
2338        "(.*)$",
2339    case re:run(Str, RE, [{capture, all_but_first, list}, caseless]) of
2340        {match, [Date, Rest]} -> {Date, false, Rest};
2341        nomatch               -> parse_cookie_value(Str)
2342    end.
2343
2344
2345parse_set_cookie_result(Cookie, [$;|Rest]) ->
2346    parse_set_cookie_options(Rest, Cookie);
2347parse_set_cookie_result(Cookie, [$,|Rest]) ->
2348    {Cookie, Rest};
2349parse_set_cookie_result(Cookie, []) ->
2350    {Cookie, []};
2351parse_set_cookie_result(_, _) ->
2352    error.
2353
2354
2355%%
2356parse_cookie(Str) ->
2357    parse_cookie(skip_space(Str), []).
2358
2359parse_cookie([], Cookies) ->
2360    lists:reverse(Cookies);
2361parse_cookie(Str, Cookies) ->
2362    case parse_cookie_key(Str, []) of
2363        {[], _}   -> [];
2364        {K, Rest} -> parse_cookie(yaws:to_lower(K), skip_space(Rest), Cookies)
2365    end.
2366
2367parse_cookie(Key, [], Cookies) ->
2368    lists:reverse([#cookie{key=Key}|Cookies]);
2369parse_cookie(Key, [$=|Str], Cookies) ->
2370    {Val, QVal, Rest0} = parse_cookie_value(skip_space(Str)),
2371    C = #cookie{key=Key, value=Val, quoted=QVal},
2372    case skip_space(Rest0) of
2373        [$;|Rest1] -> parse_cookie(skip_space(Rest1), [C|Cookies]);
2374        [$,|Rest1] -> parse_cookie(skip_space(Rest1), [C|Cookies]);
2375        []         -> lists:reverse([C|Cookies]);
2376        _          -> []
2377    end;
2378parse_cookie(Key, [$;|Str], Cookies) ->
2379    parse_cookie(skip_space(Str), [#cookie{key=Key}|Cookies]);
2380parse_cookie(Key, [$,|Str], Cookies) ->
2381    parse_cookie(skip_space(Str), [#cookie{key=Key}|Cookies]);
2382parse_cookie(_, _, _) ->
2383    [].
2384
2385
2386
2387%%
2388%% All CHAR except ('=' | ';' | ',' | SP | HT | CRLF | LF)
2389parse_cookie_key([], Acc) ->
2390    {lists:reverse(Acc), []};
2391parse_cookie_key(T=[$=|_], Acc) ->
2392    {lists:reverse(Acc), T};
2393parse_cookie_key(T=[$;|_], Acc) ->
2394    {lists:reverse(Acc), T};
2395parse_cookie_key(T=[$,|_], Acc) ->
2396    {lists:reverse(Acc), T};
2397parse_cookie_key(T=[$\s|_], Acc) ->
2398    {lists:reverse(Acc), T};
2399parse_cookie_key(T=[$\t|_], Acc) ->
2400    {lists:reverse(Acc), T};
2401parse_cookie_key(T=[$\r,$\n|_], Acc) ->
2402    {lists:reverse(Acc), T};
2403parse_cookie_key(T=[$\n|_], Acc) ->
2404    {lists:reverse(Acc), T};
2405parse_cookie_key([C|T], Acc) ->
2406    parse_cookie_key(T, [C|Acc]).
2407
2408
2409%%
2410parse_cookie_value([$"|T]) ->
2411    parse_cookie_quoted(T,[]);
2412parse_cookie_value(T) ->
2413    parse_cookie_value(T,[]).
2414
2415%% All CHAR except (';' | ',' | SP | HT | CRLF | LF)
2416parse_cookie_value([],Acc) ->
2417    {lists:reverse(Acc), false, []};
2418parse_cookie_value(T=[$;|_], Acc) ->
2419    {lists:reverse(Acc), false, T};
2420parse_cookie_value(T=[$,|_], Acc) ->
2421    {lists:reverse(Acc), false, T};
2422parse_cookie_value(T=[$\s|_], Acc) ->
2423    {lists:reverse(Acc), false, T};
2424parse_cookie_value(T=[$\t|_], Acc) ->
2425    {lists:reverse(Acc), false, T};
2426parse_cookie_value(T=[$\r,$\n|_], Acc) ->
2427    {lists:reverse(Acc), false, T};
2428parse_cookie_value(T=[$\n|_], Acc) ->
2429    {lists:reverse(Acc), false, T};
2430parse_cookie_value([C|T], Acc) ->
2431    parse_cookie_value(T, [C|Acc]).
2432
2433
2434%% All CHAR except ('"' | CTLs) but including LWS and escape DQUOTEs
2435%%   CTL = any US-ASCII control character (octets 0 - 31) and DEL (127)
2436%%   LWS = [CRLF] 1*( SP | HT )
2437parse_cookie_quoted([], Acc) ->
2438    {lists:reverse(Acc), true, []};
2439parse_cookie_quoted([$"|T], Acc) ->
2440    {lists:reverse(Acc), true, T};
2441parse_cookie_quoted([$\\,C|T], Acc) ->
2442    parse_cookie_quoted(T,[C,$\\|Acc]);
2443parse_cookie_quoted([$\t|T], Acc) ->
2444    parse_cookie_quoted(T,[$\t|Acc]);
2445parse_cookie_quoted([$\r,$\n,$\s|T], Acc) ->
2446    parse_cookie_quoted(T,[$\s,$\n,$\r|Acc]);
2447parse_cookie_quoted([$\r,$\n,$\t|T], Acc) ->
2448    parse_cookie_quoted(T,[$\t,$\n,$\r|Acc]);
2449parse_cookie_quoted([C|T], Acc) when C > 31 andalso C < 127 ->
2450    parse_cookie_quoted(T,[C|Acc]);
2451parse_cookie_quoted(T, Acc) ->
2452    {lists:reverse(Acc), true, T}.
2453
2454
2455%%
2456format_set_cookie(C) when C#setcookie.value == undefined ->
2457    [C#setcookie.key|format_cookie_opts(C)];
2458format_set_cookie(C) when C#setcookie.quoted ->
2459    [C#setcookie.key,$=,$",C#setcookie.value,$"|format_cookie_opts(C)];
2460format_set_cookie(C) ->
2461    [C#setcookie.key,$=,C#setcookie.value|format_cookie_opts(C)].
2462
2463%%
2464format_cookie([Cookie]) ->
2465    format_cookie(Cookie);
2466format_cookie([Cookie|Rest]) ->
2467    [format_cookie(Cookie),$;,$\s|format_cookie(Rest)];
2468format_cookie(#cookie{key=Key, value=undefined}) ->
2469    Key;
2470format_cookie(#cookie{key=Key, value=Value, quoted=true}) ->
2471    [Key,$=,$",Value,$"];
2472format_cookie(#cookie{key=Key, value=Value, quoted=false}) ->
2473    [Key,$=,Value].
2474
2475%%
2476format_cookie_opts(C=#setcookie{}) ->
2477    [
2478     add_opt("Domain",   C#setcookie.domain,    false),
2479     add_opt("Max-Age",  C#setcookie.max_age,   false),
2480     add_opt("Expires",  C#setcookie.expires,   false),
2481     add_opt("Path",     C#setcookie.path,      false),
2482     add_opt("Secure",   C#setcookie.secure,    false),
2483     add_opt("HttpOnly", C#setcookie.http_only, false)
2484    ] ++ [add_opt(K,V,Q) || {K,V,Q} <- C#setcookie.extensions].
2485
2486
2487add_opt(_, undefined, _) -> [];
2488add_opt(_, false, _)     -> [];
2489add_opt(Key, true, _)    -> [$;,$\s,Key];
2490add_opt(Key, Opt, true)  -> [$;,$\s,Key,$=,$",Opt,$"];
2491add_opt(Key, Opt, false)  -> [$;,$\s,Key,$=,Opt].
2492
2493
2494%%
2495skip_space([])          -> [];
2496skip_space([$\s|T])     -> skip_space(T);
2497skip_space([$\t|T])     -> skip_space(T);
2498skip_space([$\r,$\n|T]) -> skip_space(T);
2499skip_space([$\n|T])     -> skip_space(T);
2500skip_space(T)           -> T.
2501
2502
2503%%
2504getvar(ARG,Key) when is_atom(Key) ->
2505    getvar(ARG, atom_to_list(Key));
2506getvar(ARG,Key) ->
2507    filter_parse(Key, yaws_api:parse_query(ARG), yaws_api:parse_post(ARG)).
2508
2509
2510queryvar(ARG,Key) when is_atom(Key) ->
2511    queryvar(ARG, atom_to_list(Key));
2512queryvar(ARG, Key) ->
2513    filter_parse(Key, yaws_api:parse_query(ARG), []).
2514
2515postvar(ARG, Key) when is_atom(Key) ->
2516    postvar(ARG, atom_to_list(Key));
2517postvar(ARG, Key) ->
2518    filter_parse(Key, [], yaws_api:parse_post(ARG)).
2519
2520filter_parse(Key, QueryParse, PostParse) ->
2521    Fun = fun({K,V}) -> (Key == K andalso V /= undefined) end,
2522    Values = lists:filter(Fun, QueryParse) ++ lists:filter(Fun, PostParse),
2523    case Values of
2524        [] -> undefined;
2525        [{_, V}] -> {ok,V};
2526        %% Multivalued case - return a list of values as a tuple
2527        _  -> list_to_tuple(lists:map(fun({_,V}) -> V end, Values))
2528    end.
2529
2530
2531binding(Key) ->
2532    case get({binding, Key}) of
2533        undefined -> erlang:error({unknown_binding, Key});
2534        Value -> Value
2535    end.
2536
2537binding_exists(Key) ->
2538    case get({binding, Key}) of
2539        undefined -> false;
2540        _ -> true
2541    end.
2542
2543
2544
2545%% Return the parsed url that the client requested.
2546request_url(ARG) ->
2547    SC        = get(sc),
2548    Headers   = ARG#arg.headers,
2549    {_, Path} = (ARG#arg.req)#http_request.path,
2550    DecPath   = url_decode(Path),
2551    {P,Q}     = yaws:split_at(DecPath, $?),
2552    Url       = case Headers#headers.host of
2553                    undefined ->
2554                        parse_url(SC#sconf.servername, sloppy);
2555                    HostHdr ->
2556                        try          parse_url(HostHdr, sloppy)
2557                        catch _:_ -> parse_url(SC#sconf.servername, sloppy)
2558                        end
2559                end,
2560    Url#url{scheme = case SC#sconf.ssl of
2561                         undefined -> "http";
2562                         _         -> "https"
2563                     end,
2564            path = P, querypart = Q}.
2565
2566
2567
2568%% remove sick characters
2569
2570sanitize_file_name(".." ++ T) ->
2571    sanitize_file_name([$.|T]);
2572sanitize_file_name([H|T]) ->
2573    case lists:member(H,  " &;'`{}!\\?<>\"()$") of
2574        true ->
2575            sanitize_file_name(T);
2576        false ->
2577            [H|sanitize_file_name(T)]
2578    end;
2579sanitize_file_name([]) ->
2580    [].
2581
2582
2583
2584%% to be used in embedded mode, make it possible
2585%% to pass a config to yaws from another data source
2586%% than /etc/yaws/yaws.conf, for example from a database
2587%% this code is also called by the server -h hup code
2588setconf(GC0, Groups0) ->
2589    setconf(GC0, Groups0, true).
2590setconf(GC0, Groups0, CheckCertsChanged) ->
2591    case CheckCertsChanged of
2592        true ->
2593            CertCheck = gen_server:call(yaws_server, check_certs, infinity),
2594            case lists:member(yes, CertCheck) of
2595                true ->
2596                    application:stop(ssl),
2597                    application:start(ssl);
2598                false ->
2599                    ok
2600            end;
2601        false ->
2602            ok
2603    end,
2604
2605    {GC, Groups1} = yaws_config:verify_upgrade_args(GC0, Groups0),
2606    Groups2 = lists:map(fun(X) -> yaws_config:add_yaws_auth(X) end, Groups1),
2607    {ok, OLDGC, OldGroups} = yaws_api:getconf(),
2608    case {yaws_config:can_hard_gc(GC, OLDGC),
2609          yaws_config:can_soft_setconf(GC, Groups2, OLDGC, OldGroups)} of
2610        {true, true} ->
2611            yaws_config:soft_setconf(GC, Groups2, OLDGC, OldGroups);
2612        {true, false} ->
2613            ok = yaws_config:hard_setconf(GC, Groups2);
2614        _ ->
2615            {error, need_restart}
2616    end.
2617
2618%% return {ok, GC, Groups}.
2619getconf() ->
2620    gen_server:call(yaws_server, getconf, infinity).
2621
2622%% return listen port number for the given sconf, useful if yaws is used in
2623%% a test scenario where the configured port number is 0 (for requesting an
2624%% ephemeral port)
2625get_listen_port(SC) ->
2626    yaws_server:listen_port(SC).
2627
2628embedded_start_conf(DocRoot) when is_list(DocRoot) ->
2629    embedded_start_conf(DocRoot, []).
2630embedded_start_conf(DocRoot, SL) when is_list(DocRoot), is_list(SL) ->
2631    embedded_start_conf(DocRoot, SL, []).
2632embedded_start_conf(DocRoot, SL, GL)
2633  when is_list(DocRoot), is_list(SL), is_list(GL) ->
2634    embedded_start_conf(DocRoot, SL, GL, "default").
2635embedded_start_conf(DocRoot, SL, GL, Id)
2636  when is_list(DocRoot), is_list(SL), is_list(GL) ->
2637    case application:load(yaws) of
2638        ok -> ok;
2639        {error, {already_loaded,yaws}} -> ok;
2640        _ -> exit("cannot load yaws")
2641    end,
2642    ok = application:set_env(yaws, embedded, true),
2643    ok = application:set_env(yaws, id, Id),
2644    ChildSpecs = yaws_sup:child_specs(),
2645    GC = yaws:create_gconf(GL, Id),
2646    SCList  = case SL of
2647                  [] ->
2648                      [[]];
2649                  [Cnf|_] when is_tuple(Cnf) ->
2650                      [[yaws:create_sconf(DocRoot, SL)]];
2651                  [Cnf|_] when is_list(Cnf) ->
2652                      [[yaws:create_sconf(DocRoot, SLItem)] || SLItem <- SL]
2653              end,
2654    SoapChild = yaws_config:add_yaws_soap_srv(GC, false),
2655
2656    %% In case a server is started before any configuration has been set,
2657    %% this makes it possible to get hold of the 'pending' configuration.
2658    %% (see for example the start of the yaws_session_server)
2659    ok = application:set_env(yaws, embedded_conf, [{sclist,SCList},{gc,GC}]),
2660
2661    yaws:mkdir(yaws:id_dir(Id)),
2662    {ok, SCList, GC, ChildSpecs ++ SoapChild}.
2663
2664
2665%% Function which is invoked typically from an index.yaws file
2666dir_listing(Arg) ->
2667    dir_listing(Arg, ".").
2668dir_listing(Arg, RelDir) ->
2669    %% .yaws.auth
2670    Dir0 = filename:dirname(Arg#arg.fullpath),
2671    Dir = case RelDir of
2672              "." -> Dir0;
2673              _ -> filename:join([Dir0, RelDir])
2674          end,
2675    Req = Arg#arg.req,
2676    case file:list_dir(Dir) of
2677        {ok, Data0} ->
2678            Data = Data0 -- [".yaws.auth", "index.yaws"],
2679            yaws_ls:list_directory(Arg, Arg#arg.clisock, Data,
2680                                   Dir,
2681                                   Req,  false),
2682            ok;
2683        _Err ->
2684            %% Just ignore errors ??, the programmer has to
2685            %% make sure it's a valid path here
2686            ok
2687    end.
2688
2689%% Returns #redir_self{} record
2690redirect_self(A) ->
2691    SC = get(sc),
2692    {Port, PortStr} =
2693        case {SC#sconf.rmethod, SC#sconf.ssl, SC#sconf.port} of
2694            {"https", _, 443} -> {443, ""};
2695            {"http", _, 80} -> {80, ""};
2696            {_, undefined, 80} -> {80, ""};
2697            {_, undefined, Port2} ->
2698                {port, [$:|integer_to_list(Port2)]};
2699            {_, _SSL, 443} ->
2700                {443, ""};
2701            {_, _SSL, Port2} ->
2702                {Port2, [$:|integer_to_list(Port2)]}
2703        end,
2704    H = A#arg.headers,
2705    Host0 = yaws:redirect_host(get(sc), H#headers.host),
2706    %% redirect host contains the port number - for mysterious reasons
2707    Host = case string:tokens(Host0, ":") of
2708               [H0, _] -> H0;
2709               [H1] -> H1
2710           end,
2711    {Scheme, SchemeStr} =
2712        case {SC#sconf.ssl,SC#sconf.rmethod} of
2713            {_, Method} when is_list(Method) ->
2714                {list_to_atom(Method), Method++"://"};
2715            {undefined,_} ->
2716                {http, "http://"};
2717            {_SSl,_} ->
2718                {https, "https://"}
2719        end,
2720    #redir_self{host = Host,
2721                scheme = Scheme,
2722                scheme_str = SchemeStr,
2723                port = Port,
2724                port_str = PortStr}.
2725