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 <<">">>; 662htmlize_char($<) -> 663 <<"<">>; 664htmlize_char($&) -> 665 <<"&">>; 666htmlize_char($") -> 667 <<""">>; 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