diff options
Diffstat (limited to 'src/mochiweb/mochiweb_util.erl')
-rw-r--r-- | src/mochiweb/mochiweb_util.erl | 859 |
1 files changed, 0 insertions, 859 deletions
diff --git a/src/mochiweb/mochiweb_util.erl b/src/mochiweb/mochiweb_util.erl deleted file mode 100644 index d8fc89d5..00000000 --- a/src/mochiweb/mochiweb_util.erl +++ /dev/null @@ -1,859 +0,0 @@ -%% @author Bob Ippolito <bob@mochimedia.com> -%% @copyright 2007 Mochi Media, Inc. - -%% @doc Utilities for parsing and quoting. - --module(mochiweb_util). --author('bob@mochimedia.com'). --export([join/2, quote_plus/1, urlencode/1, parse_qs/1, unquote/1]). --export([path_split/1]). --export([urlsplit/1, urlsplit_path/1, urlunsplit/1, urlunsplit_path/1]). --export([guess_mime/1, parse_header/1]). --export([shell_quote/1, cmd/1, cmd_string/1, cmd_port/2]). --export([record_to_proplist/2, record_to_proplist/3]). --export([safe_relative_path/1, partition/2]). --export([parse_qvalues/1, pick_accepted_encodings/3]). --export([test/0]). - --define(PERCENT, 37). % $\% --define(FULLSTOP, 46). % $\. --define(IS_HEX(C), ((C >= $0 andalso C =< $9) orelse - (C >= $a andalso C =< $f) orelse - (C >= $A andalso C =< $F))). --define(QS_SAFE(C), ((C >= $a andalso C =< $z) orelse - (C >= $A andalso C =< $Z) orelse - (C >= $0 andalso C =< $9) orelse - (C =:= ?FULLSTOP orelse C =:= $- orelse C =:= $~ orelse - C =:= $_))). - -hexdigit(C) when C < 10 -> $0 + C; -hexdigit(C) when C < 16 -> $A + (C - 10). - -unhexdigit(C) when C >= $0, C =< $9 -> C - $0; -unhexdigit(C) when C >= $a, C =< $f -> C - $a + 10; -unhexdigit(C) when C >= $A, C =< $F -> C - $A + 10. - -%% @spec partition(String, Sep) -> {String, [], []} | {Prefix, Sep, Postfix} -%% @doc Inspired by Python 2.5's str.partition: -%% partition("foo/bar", "/") = {"foo", "/", "bar"}, -%% partition("foo", "/") = {"foo", "", ""}. -partition(String, Sep) -> - case partition(String, Sep, []) of - undefined -> - {String, "", ""}; - Result -> - Result - end. - -partition("", _Sep, _Acc) -> - undefined; -partition(S, Sep, Acc) -> - case partition2(S, Sep) of - undefined -> - [C | Rest] = S, - partition(Rest, Sep, [C | Acc]); - Rest -> - {lists:reverse(Acc), Sep, Rest} - end. - -partition2(Rest, "") -> - Rest; -partition2([C | R1], [C | R2]) -> - partition2(R1, R2); -partition2(_S, _Sep) -> - undefined. - - - -%% @spec safe_relative_path(string()) -> string() | undefined -%% @doc Return the reduced version of a relative path or undefined if it -%% is not safe. safe relative paths can be joined with an absolute path -%% and will result in a subdirectory of the absolute path. -safe_relative_path("/" ++ _) -> - undefined; -safe_relative_path(P) -> - safe_relative_path(P, []). - -safe_relative_path("", Acc) -> - case Acc of - [] -> - ""; - _ -> - string:join(lists:reverse(Acc), "/") - end; -safe_relative_path(P, Acc) -> - case partition(P, "/") of - {"", "/", _} -> - %% /foo or foo//bar - undefined; - {"..", _, _} when Acc =:= [] -> - undefined; - {"..", _, Rest} -> - safe_relative_path(Rest, tl(Acc)); - {Part, "/", ""} -> - safe_relative_path("", ["", Part | Acc]); - {Part, _, Rest} -> - safe_relative_path(Rest, [Part | Acc]) - end. - -%% @spec shell_quote(string()) -> string() -%% @doc Quote a string according to UNIX shell quoting rules, returns a string -%% surrounded by double quotes. -shell_quote(L) -> - shell_quote(L, [$\"]). - -%% @spec cmd_port([string()], Options) -> port() -%% @doc open_port({spawn, mochiweb_util:cmd_string(Argv)}, Options). -cmd_port(Argv, Options) -> - open_port({spawn, cmd_string(Argv)}, Options). - -%% @spec cmd([string()]) -> string() -%% @doc os:cmd(cmd_string(Argv)). -cmd(Argv) -> - os:cmd(cmd_string(Argv)). - -%% @spec cmd_string([string()]) -> string() -%% @doc Create a shell quoted command string from a list of arguments. -cmd_string(Argv) -> - join([shell_quote(X) || X <- Argv], " "). - -%% @spec join([string()], Separator) -> string() -%% @doc Join a list of strings together with the given separator -%% string or char. -join([], _Separator) -> - []; -join([S], _Separator) -> - lists:flatten(S); -join(Strings, Separator) -> - lists:flatten(revjoin(lists:reverse(Strings), Separator, [])). - -revjoin([], _Separator, Acc) -> - Acc; -revjoin([S | Rest], Separator, []) -> - revjoin(Rest, Separator, [S]); -revjoin([S | Rest], Separator, Acc) -> - revjoin(Rest, Separator, [S, Separator | Acc]). - -%% @spec quote_plus(atom() | integer() | float() | string() | binary()) -> string() -%% @doc URL safe encoding of the given term. -quote_plus(Atom) when is_atom(Atom) -> - quote_plus(atom_to_list(Atom)); -quote_plus(Int) when is_integer(Int) -> - quote_plus(integer_to_list(Int)); -quote_plus(Binary) when is_binary(Binary) -> - quote_plus(binary_to_list(Binary)); -quote_plus(Float) when is_float(Float) -> - quote_plus(mochinum:digits(Float)); -quote_plus(String) -> - quote_plus(String, []). - -quote_plus([], Acc) -> - lists:reverse(Acc); -quote_plus([C | Rest], Acc) when ?QS_SAFE(C) -> - quote_plus(Rest, [C | Acc]); -quote_plus([$\s | Rest], Acc) -> - quote_plus(Rest, [$+ | Acc]); -quote_plus([C | Rest], Acc) -> - <<Hi:4, Lo:4>> = <<C>>, - quote_plus(Rest, [hexdigit(Lo), hexdigit(Hi), ?PERCENT | Acc]). - -%% @spec urlencode([{Key, Value}]) -> string() -%% @doc URL encode the property list. -urlencode(Props) -> - RevPairs = lists:foldl(fun ({K, V}, Acc) -> - [[quote_plus(K), $=, quote_plus(V)] | Acc] - end, [], Props), - lists:flatten(revjoin(RevPairs, $&, [])). - -%% @spec parse_qs(string() | binary()) -> [{Key, Value}] -%% @doc Parse a query string or application/x-www-form-urlencoded. -parse_qs(Binary) when is_binary(Binary) -> - parse_qs(binary_to_list(Binary)); -parse_qs(String) -> - parse_qs(String, []). - -parse_qs([], Acc) -> - lists:reverse(Acc); -parse_qs(String, Acc) -> - {Key, Rest} = parse_qs_key(String), - {Value, Rest1} = parse_qs_value(Rest), - parse_qs(Rest1, [{Key, Value} | Acc]). - -parse_qs_key(String) -> - parse_qs_key(String, []). - -parse_qs_key([], Acc) -> - {qs_revdecode(Acc), ""}; -parse_qs_key([$= | Rest], Acc) -> - {qs_revdecode(Acc), Rest}; -parse_qs_key(Rest=[$; | _], Acc) -> - {qs_revdecode(Acc), Rest}; -parse_qs_key(Rest=[$& | _], Acc) -> - {qs_revdecode(Acc), Rest}; -parse_qs_key([C | Rest], Acc) -> - parse_qs_key(Rest, [C | Acc]). - -parse_qs_value(String) -> - parse_qs_value(String, []). - -parse_qs_value([], Acc) -> - {qs_revdecode(Acc), ""}; -parse_qs_value([$; | Rest], Acc) -> - {qs_revdecode(Acc), Rest}; -parse_qs_value([$& | Rest], Acc) -> - {qs_revdecode(Acc), Rest}; -parse_qs_value([C | Rest], Acc) -> - parse_qs_value(Rest, [C | Acc]). - -%% @spec unquote(string() | binary()) -> string() -%% @doc Unquote a URL encoded string. -unquote(Binary) when is_binary(Binary) -> - unquote(binary_to_list(Binary)); -unquote(String) -> - qs_revdecode(lists:reverse(String)). - -qs_revdecode(S) -> - qs_revdecode(S, []). - -qs_revdecode([], Acc) -> - Acc; -qs_revdecode([$+ | Rest], Acc) -> - qs_revdecode(Rest, [$\s | Acc]); -qs_revdecode([Lo, Hi, ?PERCENT | Rest], Acc) when ?IS_HEX(Lo), ?IS_HEX(Hi) -> - qs_revdecode(Rest, [(unhexdigit(Lo) bor (unhexdigit(Hi) bsl 4)) | Acc]); -qs_revdecode([C | Rest], Acc) -> - qs_revdecode(Rest, [C | Acc]). - -%% @spec urlsplit(Url) -> {Scheme, Netloc, Path, Query, Fragment} -%% @doc Return a 5-tuple, does not expand % escapes. Only supports HTTP style -%% URLs. -urlsplit(Url) -> - {Scheme, Url1} = urlsplit_scheme(Url), - {Netloc, Url2} = urlsplit_netloc(Url1), - {Path, Query, Fragment} = urlsplit_path(Url2), - {Scheme, Netloc, Path, Query, Fragment}. - -urlsplit_scheme(Url) -> - urlsplit_scheme(Url, []). - -urlsplit_scheme([], Acc) -> - {"", lists:reverse(Acc)}; -urlsplit_scheme(":" ++ Rest, Acc) -> - {string:to_lower(lists:reverse(Acc)), Rest}; -urlsplit_scheme([C | Rest], Acc) -> - urlsplit_scheme(Rest, [C | Acc]). - -urlsplit_netloc("//" ++ Rest) -> - urlsplit_netloc(Rest, []); -urlsplit_netloc(Path) -> - {"", Path}. - -urlsplit_netloc(Rest=[C | _], Acc) when C =:= $/; C =:= $?; C =:= $# -> - {lists:reverse(Acc), Rest}; -urlsplit_netloc([C | Rest], Acc) -> - urlsplit_netloc(Rest, [C | Acc]). - - -%% @spec path_split(string()) -> {Part, Rest} -%% @doc Split a path starting from the left, as in URL traversal. -%% path_split("foo/bar") = {"foo", "bar"}, -%% path_split("/foo/bar") = {"", "foo/bar"}. -path_split(S) -> - path_split(S, []). - -path_split("", Acc) -> - {lists:reverse(Acc), ""}; -path_split("/" ++ Rest, Acc) -> - {lists:reverse(Acc), Rest}; -path_split([C | Rest], Acc) -> - path_split(Rest, [C | Acc]). - - -%% @spec urlunsplit({Scheme, Netloc, Path, Query, Fragment}) -> string() -%% @doc Assemble a URL from the 5-tuple. Path must be absolute. -urlunsplit({Scheme, Netloc, Path, Query, Fragment}) -> - lists:flatten([case Scheme of "" -> ""; _ -> [Scheme, "://"] end, - Netloc, - urlunsplit_path({Path, Query, Fragment})]). - -%% @spec urlunsplit_path({Path, Query, Fragment}) -> string() -%% @doc Assemble a URL path from the 3-tuple. -urlunsplit_path({Path, Query, Fragment}) -> - lists:flatten([Path, - case Query of "" -> ""; _ -> [$? | Query] end, - case Fragment of "" -> ""; _ -> [$# | Fragment] end]). - -%% @spec urlsplit_path(Url) -> {Path, Query, Fragment} -%% @doc Return a 3-tuple, does not expand % escapes. Only supports HTTP style -%% paths. -urlsplit_path(Path) -> - urlsplit_path(Path, []). - -urlsplit_path("", Acc) -> - {lists:reverse(Acc), "", ""}; -urlsplit_path("?" ++ Rest, Acc) -> - {Query, Fragment} = urlsplit_query(Rest), - {lists:reverse(Acc), Query, Fragment}; -urlsplit_path("#" ++ Rest, Acc) -> - {lists:reverse(Acc), "", Rest}; -urlsplit_path([C | Rest], Acc) -> - urlsplit_path(Rest, [C | Acc]). - -urlsplit_query(Query) -> - urlsplit_query(Query, []). - -urlsplit_query("", Acc) -> - {lists:reverse(Acc), ""}; -urlsplit_query("#" ++ Rest, Acc) -> - {lists:reverse(Acc), Rest}; -urlsplit_query([C | Rest], Acc) -> - urlsplit_query(Rest, [C | Acc]). - -%% @spec guess_mime(string()) -> string() -%% @doc Guess the mime type of a file by the extension of its filename. -guess_mime(File) -> - case filename:extension(File) of - ".html" -> - "text/html"; - ".xhtml" -> - "application/xhtml+xml"; - ".xml" -> - "application/xml"; - ".css" -> - "text/css"; - ".js" -> - "application/x-javascript"; - ".jpg" -> - "image/jpeg"; - ".gif" -> - "image/gif"; - ".png" -> - "image/png"; - ".swf" -> - "application/x-shockwave-flash"; - ".zip" -> - "application/zip"; - ".bz2" -> - "application/x-bzip2"; - ".gz" -> - "application/x-gzip"; - ".tar" -> - "application/x-tar"; - ".tgz" -> - "application/x-gzip"; - ".txt" -> - "text/plain"; - ".doc" -> - "application/msword"; - ".pdf" -> - "application/pdf"; - ".xls" -> - "application/vnd.ms-excel"; - ".rtf" -> - "application/rtf"; - ".mov" -> - "video/quicktime"; - ".mp3" -> - "audio/mpeg"; - ".z" -> - "application/x-compress"; - ".wav" -> - "audio/x-wav"; - ".ico" -> - "image/x-icon"; - ".bmp" -> - "image/bmp"; - ".m4a" -> - "audio/mpeg"; - ".m3u" -> - "audio/x-mpegurl"; - ".exe" -> - "application/octet-stream"; - ".csv" -> - "text/csv"; - _ -> - "text/plain" - end. - -%% @spec parse_header(string()) -> {Type, [{K, V}]} -%% @doc Parse a Content-Type like header, return the main Content-Type -%% and a property list of options. -parse_header(String) -> - %% TODO: This is exactly as broken as Python's cgi module. - %% Should parse properly like mochiweb_cookies. - [Type | Parts] = [string:strip(S) || S <- string:tokens(String, ";")], - F = fun (S, Acc) -> - case lists:splitwith(fun (C) -> C =/= $= end, S) of - {"", _} -> - %% Skip anything with no name - Acc; - {_, ""} -> - %% Skip anything with no value - Acc; - {Name, [$\= | Value]} -> - [{string:to_lower(string:strip(Name)), - unquote_header(string:strip(Value))} | Acc] - end - end, - {string:to_lower(Type), - lists:foldr(F, [], Parts)}. - -unquote_header("\"" ++ Rest) -> - unquote_header(Rest, []); -unquote_header(S) -> - S. - -unquote_header("", Acc) -> - lists:reverse(Acc); -unquote_header("\"", Acc) -> - lists:reverse(Acc); -unquote_header([$\\, C | Rest], Acc) -> - unquote_header(Rest, [C | Acc]); -unquote_header([C | Rest], Acc) -> - unquote_header(Rest, [C | Acc]). - -%% @spec record_to_proplist(Record, Fields) -> proplist() -%% @doc calls record_to_proplist/3 with a default TypeKey of '__record' -record_to_proplist(Record, Fields) -> - record_to_proplist(Record, Fields, '__record'). - -%% @spec record_to_proplist(Record, Fields, TypeKey) -> proplist() -%% @doc Return a proplist of the given Record with each field in the -%% Fields list set as a key with the corresponding value in the Record. -%% TypeKey is the key that is used to store the record type -%% Fields should be obtained by calling record_info(fields, record_type) -%% where record_type is the record type of Record -record_to_proplist(Record, Fields, TypeKey) - when tuple_size(Record) - 1 =:= length(Fields) -> - lists:zip([TypeKey | Fields], tuple_to_list(Record)). - - -shell_quote([], Acc) -> - lists:reverse([$\" | Acc]); -shell_quote([C | Rest], Acc) when C =:= $\" orelse C =:= $\` orelse - C =:= $\\ orelse C =:= $\$ -> - shell_quote(Rest, [C, $\\ | Acc]); -shell_quote([C | Rest], Acc) -> - shell_quote(Rest, [C | Acc]). - -%% @spec parse_qvalues(string()) -> [qvalue()] | error() -%% @type qvalue() -> {element(), q()} -%% @type element() -> string() -%% @type q() -> 0.0 .. 1.0 -%% @type error() -> invalid_qvalue_string -%% -%% @doc Parses a list (given as a string) of elements with Q values associated -%% to them. Elements are separated by commas and each element is separated -%% from its Q value by a semicolon. Q values are optional but when missing -%% the value of an element is considered as 1.0. A Q value is always in the -%% range [0.0, 1.0]. A Q value list is used for example as the value of the -%% HTTP "Accept-Encoding" header. -%% -%% Q values are described in section 2.9 of the RFC 2616 (HTTP 1.1). -%% -%% Example: -%% -%% parse_qvalues("gzip; q=0.5, deflate, identity;q=0.0") -> -%% [{"gzip", 0.5}, {"deflate", 1.0}, {"identity", 0.0}] -%% -parse_qvalues(QValuesStr) -> - try - {ok, Re} = re:compile("^\\s*q\\s*=\\s*((?:0|1)(?:\\.\\d{1,3})?)\\s*$"), - lists:map( - fun(Pair) -> - case string:tokens(Pair, ";") of - [Enc] -> - {string:strip(Enc), 1.0}; - [Enc, QStr] -> - case re:run(QStr, Re, [{capture, [1], list}]) of - {match, [Q]} -> - QVal = case Q of - "0" -> - 0.0; - "1" -> - 1.0; - Else -> - list_to_float(Else) - end, - case QVal < 0.0 orelse QVal > 1.0 of - false -> - {string:strip(Enc), QVal} - end - end - end - end, - string:tokens(string:to_lower(QValuesStr), ",") - ) - catch - _Type:_Error -> - invalid_qvalue_string - end. - -%% @spec pick_accepted_encodings(qvalues(), [encoding()], encoding()) -> -%% [encoding()] -%% @type qvalues() -> [ {encoding(), q()} ] -%% @type encoding() -> string() -%% @type q() -> 0.0 .. 1.0 -%% -%% @doc Determines which encodings specified in the given Q values list are -%% valid according to a list of supported encodings and a default encoding. -%% -%% The returned list of encodings is sorted, descendingly, according to the -%% Q values of the given list. The last element of this list is the given -%% default encoding unless this encoding is explicitily or implicitily -%% marked with a Q value of 0.0 in the given Q values list. -%% Note: encodings with the same Q value are kept in the same order as -%% found in the input Q values list. -%% -%% This encoding picking process is described in section 14.3 of the -%% RFC 2616 (HTTP 1.1). -%% -%% Example: -%% -%% pick_accepted_encodings( -%% [{"gzip", 0.5}, {"deflate", 1.0}], -%% ["gzip", "identity"], -%% "identity" -%% ) -> -%% ["gzip", "identity"] -%% -pick_accepted_encodings(AcceptedEncs, SupportedEncs, DefaultEnc) -> - SortedQList = lists:reverse( - lists:sort(fun({_, Q1}, {_, Q2}) -> Q1 < Q2 end, AcceptedEncs) - ), - {Accepted, Refused} = lists:foldr( - fun({E, Q}, {A, R}) -> - case Q > 0.0 of - true -> - {[E | A], R}; - false -> - {A, [E | R]} - end - end, - {[], []}, - SortedQList - ), - Refused1 = lists:foldr( - fun(Enc, Acc) -> - case Enc of - "*" -> - lists:subtract(SupportedEncs, Accepted) ++ Acc; - _ -> - [Enc | Acc] - end - end, - [], - Refused - ), - Accepted1 = lists:foldr( - fun(Enc, Acc) -> - case Enc of - "*" -> - lists:subtract(SupportedEncs, Accepted ++ Refused1) ++ Acc; - _ -> - [Enc | Acc] - end - end, - [], - Accepted - ), - Accepted2 = case lists:member(DefaultEnc, Accepted1) of - true -> - Accepted1; - false -> - Accepted1 ++ [DefaultEnc] - end, - [E || E <- Accepted2, lists:member(E, SupportedEncs), - not lists:member(E, Refused1)]. - -test() -> - test_join(), - test_quote_plus(), - test_unquote(), - test_urlencode(), - test_parse_qs(), - test_urlsplit_path(), - test_urlunsplit_path(), - test_urlsplit(), - test_urlunsplit(), - test_path_split(), - test_guess_mime(), - test_parse_header(), - test_shell_quote(), - test_cmd(), - test_cmd_string(), - test_partition(), - test_safe_relative_path(), - test_parse_qvalues(), - test_pick_accepted_encodings(), - ok. - -test_shell_quote() -> - "\"foo \\$bar\\\"\\`' baz\"" = shell_quote("foo $bar\"`' baz"), - ok. - -test_cmd() -> - "$bling$ `word`!\n" = cmd(["echo", "$bling$ `word`!"]), - ok. - -test_cmd_string() -> - "\"echo\" \"\\$bling\\$ \\`word\\`!\"" = cmd_string(["echo", "$bling$ `word`!"]), - ok. - -test_parse_header() -> - {"multipart/form-data", [{"boundary", "AaB03x"}]} = - parse_header("multipart/form-data; boundary=AaB03x"), - ok. - -test_guess_mime() -> - "text/plain" = guess_mime(""), - "text/plain" = guess_mime(".text"), - "application/zip" = guess_mime(".zip"), - "application/zip" = guess_mime("x.zip"), - "text/html" = guess_mime("x.html"), - "application/xhtml+xml" = guess_mime("x.xhtml"), - ok. - -test_path_split() -> - {"", "foo/bar"} = path_split("/foo/bar"), - {"foo", "bar"} = path_split("foo/bar"), - {"bar", ""} = path_split("bar"), - ok. - -test_urlsplit() -> - {"", "", "/foo", "", "bar?baz"} = urlsplit("/foo#bar?baz"), - {"http", "host:port", "/foo", "", "bar?baz"} = - urlsplit("http://host:port/foo#bar?baz"), - ok. - -test_urlsplit_path() -> - {"/foo/bar", "", ""} = urlsplit_path("/foo/bar"), - {"/foo", "baz", ""} = urlsplit_path("/foo?baz"), - {"/foo", "", "bar?baz"} = urlsplit_path("/foo#bar?baz"), - {"/foo", "", "bar?baz#wibble"} = urlsplit_path("/foo#bar?baz#wibble"), - {"/foo", "bar", "baz"} = urlsplit_path("/foo?bar#baz"), - {"/foo", "bar?baz", "baz"} = urlsplit_path("/foo?bar?baz#baz"), - ok. - -test_urlunsplit() -> - "/foo#bar?baz" = urlunsplit({"", "", "/foo", "", "bar?baz"}), - "http://host:port/foo#bar?baz" = - urlunsplit({"http", "host:port", "/foo", "", "bar?baz"}), - ok. - -test_urlunsplit_path() -> - "/foo/bar" = urlunsplit_path({"/foo/bar", "", ""}), - "/foo?baz" = urlunsplit_path({"/foo", "baz", ""}), - "/foo#bar?baz" = urlunsplit_path({"/foo", "", "bar?baz"}), - "/foo#bar?baz#wibble" = urlunsplit_path({"/foo", "", "bar?baz#wibble"}), - "/foo?bar#baz" = urlunsplit_path({"/foo", "bar", "baz"}), - "/foo?bar?baz#baz" = urlunsplit_path({"/foo", "bar?baz", "baz"}), - ok. - -test_join() -> - "foo,bar,baz" = join(["foo", "bar", "baz"], $,), - "foo,bar,baz" = join(["foo", "bar", "baz"], ","), - "foo bar" = join([["foo", " bar"]], ","), - "foo bar,baz" = join([["foo", " bar"], "baz"], ","), - "foo" = join(["foo"], ","), - "foobarbaz" = join(["foo", "bar", "baz"], ""), - ok. - -test_quote_plus() -> - "foo" = quote_plus(foo), - "1" = quote_plus(1), - "1.1" = quote_plus(1.1), - "foo" = quote_plus("foo"), - "foo+bar" = quote_plus("foo bar"), - "foo%0A" = quote_plus("foo\n"), - "foo%0A" = quote_plus("foo\n"), - "foo%3B%26%3D" = quote_plus("foo;&="), - ok. - -test_unquote() -> - "foo bar" = unquote("foo+bar"), - "foo bar" = unquote("foo%20bar"), - "foo\r\n" = unquote("foo%0D%0A"), - ok. - -test_urlencode() -> - "foo=bar&baz=wibble+%0D%0A&z=1" = urlencode([{foo, "bar"}, - {"baz", "wibble \r\n"}, - {z, 1}]), - ok. - -test_parse_qs() -> - [{"foo", "bar"}, {"baz", "wibble \r\n"}, {"z", "1"}] = - parse_qs("foo=bar&baz=wibble+%0D%0A&z=1"), - ok. - -test_partition() -> - {"foo", "", ""} = partition("foo", "/"), - {"foo", "/", "bar"} = partition("foo/bar", "/"), - {"foo", "/", ""} = partition("foo/", "/"), - {"", "/", "bar"} = partition("/bar", "/"), - {"f", "oo/ba", "r"} = partition("foo/bar", "oo/ba"), - ok. - -test_safe_relative_path() -> - "foo" = safe_relative_path("foo"), - "foo/" = safe_relative_path("foo/"), - "foo" = safe_relative_path("foo/bar/.."), - "bar" = safe_relative_path("foo/../bar"), - "bar/" = safe_relative_path("foo/../bar/"), - "" = safe_relative_path("foo/.."), - "" = safe_relative_path("foo/../"), - undefined = safe_relative_path("/foo"), - undefined = safe_relative_path("../foo"), - undefined = safe_relative_path("foo/../.."), - undefined = safe_relative_path("foo//"), - ok. - -test_parse_qvalues() -> - [] = parse_qvalues(""), - [{"identity", 0.0}] = parse_qvalues("identity;q=0"), - [{"identity", 0.0}] = parse_qvalues("identity ;q=0"), - [{"identity", 0.0}] = parse_qvalues(" identity; q =0 "), - [{"identity", 0.0}] = parse_qvalues("identity ; q = 0"), - [{"identity", 0.0}] = parse_qvalues("identity ; q= 0.0"), - [{"gzip", 1.0}, {"deflate", 1.0}, {"identity", 0.0}] = parse_qvalues( - "gzip,deflate,identity;q=0.0" - ), - [{"deflate", 1.0}, {"gzip", 1.0}, {"identity", 0.0}] = parse_qvalues( - "deflate,gzip,identity;q=0.0" - ), - [{"gzip", 1.0}, {"deflate", 1.0}, {"gzip", 1.0}, {"identity", 0.0}] = - parse_qvalues("gzip,deflate,gzip,identity;q=0"), - [{"gzip", 1.0}, {"deflate", 1.0}, {"identity", 0.0}] = parse_qvalues( - "gzip, deflate , identity; q=0.0" - ), - [{"gzip", 1.0}, {"deflate", 1.0}, {"identity", 0.0}] = parse_qvalues( - "gzip; q=1, deflate;q=1.0, identity;q=0.0" - ), - [{"gzip", 0.5}, {"deflate", 1.0}, {"identity", 0.0}] = parse_qvalues( - "gzip; q=0.5, deflate;q=1.0, identity;q=0" - ), - [{"gzip", 0.5}, {"deflate", 1.0}, {"identity", 0.0}] = parse_qvalues( - "gzip; q=0.5, deflate , identity;q=0.0" - ), - [{"gzip", 0.5}, {"deflate", 0.8}, {"identity", 0.0}] = parse_qvalues( - "gzip; q=0.5, deflate;q=0.8, identity;q=0.0" - ), - [{"gzip", 0.5}, {"deflate", 1.0}, {"identity", 1.0}] = parse_qvalues( - "gzip; q=0.5,deflate,identity" - ), - [{"gzip", 0.5}, {"deflate", 1.0}, {"identity", 1.0}, {"identity", 1.0}] = - parse_qvalues("gzip; q=0.5,deflate,identity, identity "), - invalid_qvalue_string = parse_qvalues("gzip; q=1.1, deflate"), - invalid_qvalue_string = parse_qvalues("gzip; q=0.5, deflate;q=2"), - invalid_qvalue_string = parse_qvalues("gzip, deflate;q=AB"), - invalid_qvalue_string = parse_qvalues("gzip; q=2.1, deflate"), - ok. - -test_pick_accepted_encodings() -> - ["identity"] = pick_accepted_encodings( - [], - ["gzip", "identity"], - "identity" - ), - ["gzip", "identity"] = pick_accepted_encodings( - [{"gzip", 1.0}], - ["gzip", "identity"], - "identity" - ), - ["identity"] = pick_accepted_encodings( - [{"gzip", 0.0}], - ["gzip", "identity"], - "identity" - ), - ["gzip", "identity"] = pick_accepted_encodings( - [{"gzip", 1.0}, {"deflate", 1.0}], - ["gzip", "identity"], - "identity" - ), - ["gzip", "identity"] = pick_accepted_encodings( - [{"gzip", 0.5}, {"deflate", 1.0}], - ["gzip", "identity"], - "identity" - ), - ["identity"] = pick_accepted_encodings( - [{"gzip", 0.0}, {"deflate", 0.0}], - ["gzip", "identity"], - "identity" - ), - ["gzip"] = pick_accepted_encodings( - [{"gzip", 1.0}, {"deflate", 1.0}, {"identity", 0.0}], - ["gzip", "identity"], - "identity" - ), - ["gzip", "deflate", "identity"] = pick_accepted_encodings( - [{"gzip", 1.0}, {"deflate", 1.0}], - ["gzip", "deflate", "identity"], - "identity" - ), - ["gzip", "deflate"] = pick_accepted_encodings( - [{"gzip", 1.0}, {"deflate", 1.0}, {"identity", 0.0}], - ["gzip", "deflate", "identity"], - "identity" - ), - ["deflate", "gzip", "identity"] = pick_accepted_encodings( - [{"gzip", 0.2}, {"deflate", 1.0}], - ["gzip", "deflate", "identity"], - "identity" - ), - ["deflate", "deflate", "gzip", "identity"] = pick_accepted_encodings( - [{"gzip", 0.2}, {"deflate", 1.0}, {"deflate", 1.0}], - ["gzip", "deflate", "identity"], - "identity" - ), - ["deflate", "gzip", "gzip", "identity"] = pick_accepted_encodings( - [{"gzip", 0.2}, {"deflate", 1.0}, {"gzip", 1.0}], - ["gzip", "deflate", "identity"], - "identity" - ), - ["gzip", "deflate", "gzip", "identity"] = pick_accepted_encodings( - [{"gzip", 0.2}, {"deflate", 0.9}, {"gzip", 1.0}], - ["gzip", "deflate", "identity"], - "identity" - ), - [] = pick_accepted_encodings( - [{"*", 0.0}], - ["gzip", "deflate", "identity"], - "identity" - ), - ["gzip", "deflate", "identity"] = pick_accepted_encodings( - [{"*", 1.0}], - ["gzip", "deflate", "identity"], - "identity" - ), - ["gzip", "deflate", "identity"] = pick_accepted_encodings( - [{"*", 0.6}], - ["gzip", "deflate", "identity"], - "identity" - ), - ["gzip"] = pick_accepted_encodings( - [{"gzip", 1.0}, {"*", 0.0}], - ["gzip", "deflate", "identity"], - "identity" - ), - ["gzip", "deflate"] = pick_accepted_encodings( - [{"gzip", 1.0}, {"deflate", 0.6}, {"*", 0.0}], - ["gzip", "deflate", "identity"], - "identity" - ), - ["deflate", "gzip"] = pick_accepted_encodings( - [{"gzip", 0.5}, {"deflate", 1.0}, {"*", 0.0}], - ["gzip", "deflate", "identity"], - "identity" - ), - ["gzip", "identity"] = pick_accepted_encodings( - [{"deflate", 0.0}, {"*", 1.0}], - ["gzip", "deflate", "identity"], - "identity" - ), - ["gzip", "identity"] = pick_accepted_encodings( - [{"*", 1.0}, {"deflate", 0.0}], - ["gzip", "deflate", "identity"], - "identity" - ), - ok. |