Subversion Repositories SE.SVN

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
12 7u83 1
%% @author Bob Ippolito <bob@mochimedia.com>
2
%% @copyright 2007 Mochi Media, Inc.
3
 
4
%% @doc Utilities for parsing and quoting.
5
 
6
-module(mochiweb_util).
7
-author('bob@mochimedia.com').
8
-export([join/2, quote_plus/1, urlencode/1, parse_qs/1, unquote/1]).
9
-export([path_split/1]).
10
-export([urlsplit/1, urlsplit_path/1, urlunsplit/1, urlunsplit_path/1]).
11
-export([guess_mime/1, parse_header/1]).
12
-export([shell_quote/1, cmd/1, cmd_string/1, cmd_port/2, cmd_status/1, cmd_status/2]).
13
-export([record_to_proplist/2, record_to_proplist/3]).
14
-export([safe_relative_path/1, partition/2]).
15
-export([parse_qvalues/1, pick_accepted_encodings/3]).
16
-export([make_io/1]).
17
-export([normalize_path/1]).
18
-export([rand_uniform/2]).
19
 
20
-define(PERCENT, 37).  % $\%
21
-define(FULLSTOP, 46). % $\.
22
-define(IS_HEX(C), ((C >= $0 andalso C =< $9) orelse
23
                    (C >= $a andalso C =< $f) orelse
24
                    (C >= $A andalso C =< $F))).
25
-define(QS_SAFE(C), ((C >= $a andalso C =< $z) orelse
26
                     (C >= $A andalso C =< $Z) orelse
27
                     (C >= $0 andalso C =< $9) orelse
28
                     (C =:= ?FULLSTOP orelse C =:= $- orelse C =:= $~ orelse
29
                      C =:= $_))).
30
 
31
hexdigit(C) when C < 10 -> $0 + C;
32
hexdigit(C) when C < 16 -> $A + (C - 10).
33
 
34
unhexdigit(C) when C >= $0, C =< $9 -> C - $0;
35
unhexdigit(C) when C >= $a, C =< $f -> C - $a + 10;
36
unhexdigit(C) when C >= $A, C =< $F -> C - $A + 10.
37
 
38
%% @spec partition(String, Sep) -> {String, [], []} | {Prefix, Sep, Postfix}
39
%% @doc Inspired by Python 2.5's str.partition:
40
%%      partition("foo/bar", "/") = {"foo", "/", "bar"},
41
%%      partition("foo", "/") = {"foo", "", ""}.
42
partition(String, Sep) ->
43
    case partition(String, Sep, []) of
44
        undefined ->
45
            {String, "", ""};
46
        Result ->
47
            Result
48
    end.
49
 
50
partition("", _Sep, _Acc) ->
51
    undefined;
52
partition(S, Sep, Acc) ->
53
    case partition2(S, Sep) of
54
        undefined ->
55
            [C | Rest] = S,
56
            partition(Rest, Sep, [C | Acc]);
57
        Rest ->
58
            {lists:reverse(Acc), Sep, Rest}
59
    end.
60
 
61
partition2(Rest, "") ->
62
    Rest;
63
partition2([C | R1], [C | R2]) ->
64
    partition2(R1, R2);
65
partition2(_S, _Sep) ->
66
    undefined.
67
 
68
 
69
 
70
%% @spec safe_relative_path(string()) -> string() | undefined
71
%% @doc Return the reduced version of a relative path or undefined if it
72
%%      is not safe. safe relative paths can be joined with an absolute path
73
%%      and will result in a subdirectory of the absolute path. Safe paths
74
%%      never contain a backslash character.
75
safe_relative_path("/" ++ _) ->
76
    undefined;
77
safe_relative_path(P) ->
78
    case string:chr(P, $\\) of
79
 
80
           safe_relative_path(P, []);
81
        _ ->
82
           undefined
83
    end.
84
 
85
safe_relative_path("", Acc) ->
86
    case Acc of
87
        [] ->
88
            "";
89
        _ ->
90
            string:join(lists:reverse(Acc), "/")
91
    end;
92
safe_relative_path(P, Acc) ->
93
    case partition(P, "/") of
94
        {"", "/", _} ->
95
            %% /foo or foo//bar
96
            undefined;
97
        {"..", _, _} when Acc =:= [] ->
98
            undefined;
99
        {"..", _, Rest} ->
100
            safe_relative_path(Rest, tl(Acc));
101
        {Part, "/", ""} ->
102
            safe_relative_path("", ["", Part | Acc]);
103
        {Part, _, Rest} ->
104
            safe_relative_path(Rest, [Part | Acc])
105
    end.
106
 
107
%% @spec shell_quote(string()) -> string()
108
%% @doc Quote a string according to UNIX shell quoting rules, returns a string
109
%%      surrounded by double quotes.
110
shell_quote(L) ->
111
    shell_quote(L, [$\"]).
112
 
113
%% @spec cmd_port([string()], Options) -> port()
114
%% @doc open_port({spawn, mochiweb_util:cmd_string(Argv)}, Options).
115
cmd_port(Argv, Options) ->
116
    open_port({spawn, cmd_string(Argv)}, Options).
117
 
118
%% @spec cmd([string()]) -> string()
119
%% @doc os:cmd(cmd_string(Argv)).
120
cmd(Argv) ->
121
    os:cmd(cmd_string(Argv)).
122
 
123
%% @spec cmd_string([string()]) -> string()
124
%% @doc Create a shell quoted command string from a list of arguments.
125
cmd_string(Argv) ->
126
    string:join([shell_quote(X) || X <- Argv], " ").
127
 
128
%% @spec cmd_status([string()]) -> {ExitStatus::integer(), Stdout::binary()}
129
%% @doc Accumulate the output and exit status from the given application,
130
%%      will be spawned with cmd_port/2.
131
cmd_status(Argv) ->
132
    cmd_status(Argv, []).
133
 
134
%% @spec cmd_status([string()], [atom()]) -> {ExitStatus::integer(), Stdout::binary()}
135
%% @doc Accumulate the output and exit status from the given application,
136
%%      will be spawned with cmd_port/2.
137
cmd_status(Argv, Options) ->
138
    Port = cmd_port(Argv, [exit_status, stderr_to_stdout,
139
                           use_stdio, binary | Options]),
140
    try cmd_loop(Port, [])
141
    after catch port_close(Port)
142
    end.
143
 
144
%% @spec cmd_loop(port(), list()) -> {ExitStatus::integer(), Stdout::binary()}
145
%% @doc Accumulate the output and exit status from a port.
146
cmd_loop(Port, Acc) ->
147
    receive
148
        {Port, {exit_status, Status}} ->
149
            {Status, iolist_to_binary(lists:reverse(Acc))};
150
        {Port, {data, Data}} ->
151
            cmd_loop(Port, [Data | Acc])
152
    end.
153
 
154
%% @spec join([iolist()], iolist()) -> iolist()
155
%% @doc Join a list of strings or binaries together with the given separator
156
%%      string or char or binary. The output is flattened, but may be an
157
%%      iolist() instead of a string() if any of the inputs are binary().
158
join([], _Separator) ->
159
    [];
160
join([S], _Separator) ->
161
    lists:flatten(S);
162
join(Strings, Separator) ->
163
    lists:flatten(revjoin(lists:reverse(Strings), Separator, [])).
164
 
165
revjoin([], _Separator, Acc) ->
166
    Acc;
167
revjoin([S | Rest], Separator, []) ->
168
    revjoin(Rest, Separator, [S]);
169
revjoin([S | Rest], Separator, Acc) ->
170
    revjoin(Rest, Separator, [S, Separator | Acc]).
171
 
172
%% @spec quote_plus(atom() | integer() | float() | string() | binary()) -> string()
173
%% @doc URL safe encoding of the given term.
174
quote_plus(Atom) when is_atom(Atom) ->
175
    quote_plus(atom_to_list(Atom));
176
quote_plus(Int) when is_integer(Int) ->
177
    quote_plus(integer_to_list(Int));
178
quote_plus(Binary) when is_binary(Binary) ->
179
    quote_plus(binary_to_list(Binary));
180
quote_plus(Float) when is_float(Float) ->
181
    quote_plus(mochinum:digits(Float));
182
quote_plus(String) ->
183
    quote_plus(String, []).
184
 
185
quote_plus([], Acc) ->
186
    lists:reverse(Acc);
187
quote_plus([C | Rest], Acc) when ?QS_SAFE(C) ->
188
    quote_plus(Rest, [C | Acc]);
189
quote_plus([$\s | Rest], Acc) ->
190
    quote_plus(Rest, [$+ | Acc]);
191
quote_plus([C | Rest], Acc) ->
192
    <<Hi:4, Lo:4>> = <<C>>,
193
    quote_plus(Rest, [hexdigit(Lo), hexdigit(Hi), ?PERCENT | Acc]).
194
 
195
%% @spec urlencode([{Key, Value}]) -> string()
196
%% @doc URL encode the property list.
197
urlencode(Props) ->
198
    Pairs = lists:foldr(
199
              fun ({K, V}, Acc) ->
200
                      [quote_plus(K) ++ "=" ++ quote_plus(V) | Acc]
201
              end, [], Props),
202
    string:join(Pairs, "&").
203
 
204
%% @spec parse_qs(string() | binary()) -> [{Key, Value}]
205
%% @doc Parse a query string or application/x-www-form-urlencoded.
206
parse_qs(Binary) when is_binary(Binary) ->
207
    parse_qs(binary_to_list(Binary));
208
parse_qs(String) ->
209
    parse_qs(String, []).
210
 
211
parse_qs([], Acc) ->
212
    lists:reverse(Acc);
213
parse_qs(String, Acc) ->
214
    {Key, Rest} = parse_qs_key(String),
215
    {Value, Rest1} = parse_qs_value(Rest),
216
    parse_qs(Rest1, [{Key, Value} | Acc]).
217
 
218
parse_qs_key(String) ->
219
    parse_qs_key(String, []).
220
 
221
parse_qs_key([], Acc) ->
222
    {qs_revdecode(Acc), ""};
223
parse_qs_key([$= | Rest], Acc) ->
224
    {qs_revdecode(Acc), Rest};
225
parse_qs_key(Rest=[$; | _], Acc) ->
226
    {qs_revdecode(Acc), Rest};
227
parse_qs_key(Rest=[$& | _], Acc) ->
228
    {qs_revdecode(Acc), Rest};
229
parse_qs_key([C | Rest], Acc) ->
230
    parse_qs_key(Rest, [C | Acc]).
231
 
232
parse_qs_value(String) ->
233
    parse_qs_value(String, []).
234
 
235
parse_qs_value([], Acc) ->
236
    {qs_revdecode(Acc), ""};
237
parse_qs_value([$; | Rest], Acc) ->
238
    {qs_revdecode(Acc), Rest};
239
parse_qs_value([$& | Rest], Acc) ->
240
    {qs_revdecode(Acc), Rest};
241
parse_qs_value([C | Rest], Acc) ->
242
    parse_qs_value(Rest, [C | Acc]).
243
 
244
%% @spec unquote(string() | binary()) -> string()
245
%% @doc Unquote a URL encoded string.
246
unquote(Binary) when is_binary(Binary) ->
247
    unquote(binary_to_list(Binary));
248
unquote(String) ->
249
    qs_revdecode(lists:reverse(String)).
250
 
251
qs_revdecode(S) ->
252
    qs_revdecode(S, []).
253
 
254
qs_revdecode([], Acc) ->
255
    Acc;
256
qs_revdecode([$+ | Rest], Acc) ->
257
    qs_revdecode(Rest, [$\s | Acc]);
258
qs_revdecode([Lo, Hi, ?PERCENT | Rest], Acc) when ?IS_HEX(Lo), ?IS_HEX(Hi) ->
259
    qs_revdecode(Rest, [(unhexdigit(Lo) bor (unhexdigit(Hi) bsl 4)) | Acc]);
260
qs_revdecode([C | Rest], Acc) ->
261
    qs_revdecode(Rest, [C | Acc]).
262
 
263
%% @spec urlsplit(Url) -> {Scheme, Netloc, Path, Query, Fragment}
264
%% @doc Return a 5-tuple, does not expand % escapes. Only supports HTTP style
265
%%      URLs.
266
urlsplit(Url) ->
267
    {Scheme, Url1} = urlsplit_scheme(Url),
268
    {Netloc, Url2} = urlsplit_netloc(Url1),
269
    {Path, Query, Fragment} = urlsplit_path(Url2),
270
    {Scheme, Netloc, Path, Query, Fragment}.
271
 
272
urlsplit_scheme(Url) ->
273
    case urlsplit_scheme(Url, []) of
274
        no_scheme ->
275
            {"", Url};
276
        Res ->
277
            Res
278
    end.
279
 
280
urlsplit_scheme([C | Rest], Acc) when ((C >= $a andalso C =< $z) orelse
281
                                       (C >= $A andalso C =< $Z) orelse
282
                                       (C >= $0 andalso C =< $9) orelse
283
                                       C =:= $+ orelse C =:= $- orelse
284
                                       C =:= $.) ->
285
    urlsplit_scheme(Rest, [C | Acc]);
286
urlsplit_scheme([$: | Rest], Acc=[_ | _]) ->
287
    {string:to_lower(lists:reverse(Acc)), Rest};
288
urlsplit_scheme(_Rest, _Acc) ->
289
    no_scheme.
290
 
291
urlsplit_netloc("//" ++ Rest) ->
292
    urlsplit_netloc(Rest, []);
293
urlsplit_netloc(Path) ->
294
    {"", Path}.
295
 
296
urlsplit_netloc("", Acc) ->
297
    {lists:reverse(Acc), ""};
298
urlsplit_netloc(Rest=[C | _], Acc) when C =:= $/; C =:= $?; C =:= $# ->
299
    {lists:reverse(Acc), Rest};
300
urlsplit_netloc([C | Rest], Acc) ->
301
    urlsplit_netloc(Rest, [C | Acc]).
302
 
303
 
304
%% @spec path_split(string()) -> {Part, Rest}
305
%% @doc Split a path starting from the left, as in URL traversal.
306
%%      path_split("foo/bar") = {"foo", "bar"},
307
%%      path_split("/foo/bar") = {"", "foo/bar"}.
308
path_split(S) ->
309
    path_split(S, []).
310
 
311
path_split("", Acc) ->
312
    {lists:reverse(Acc), ""};
313
path_split("/" ++ Rest, Acc) ->
314
    {lists:reverse(Acc), Rest};
315
path_split([C | Rest], Acc) ->
316
    path_split(Rest, [C | Acc]).
317
 
318
 
319
%% @spec urlunsplit({Scheme, Netloc, Path, Query, Fragment}) -> string()
320
%% @doc Assemble a URL from the 5-tuple. Path must be absolute.
321
urlunsplit({Scheme, Netloc, Path, Query, Fragment}) ->
322
    lists:flatten([case Scheme of "" -> "";  _ -> [Scheme, "://"] end,
323
                   Netloc,
324
                   urlunsplit_path({Path, Query, Fragment})]).
325
 
326
%% @spec urlunsplit_path({Path, Query, Fragment}) -> string()
327
%% @doc Assemble a URL path from the 3-tuple.
328
urlunsplit_path({Path, Query, Fragment}) ->
329
    lists:flatten([Path,
330
                   case Query of "" -> ""; _ -> [$? | Query] end,
331
                   case Fragment of "" -> ""; _ -> [$# | Fragment] end]).
332
 
333
%% @spec urlsplit_path(Url) -> {Path, Query, Fragment}
334
%% @doc Return a 3-tuple, does not expand % escapes. Only supports HTTP style
335
%%      paths.
336
urlsplit_path(Path) ->
337
    urlsplit_path(Path, []).
338
 
339
urlsplit_path("", Acc) ->
340
    {lists:reverse(Acc), "", ""};
341
urlsplit_path("?" ++ Rest, Acc) ->
342
    {Query, Fragment} = urlsplit_query(Rest),
343
    {lists:reverse(Acc), Query, Fragment};
344
urlsplit_path("#" ++ Rest, Acc) ->
345
    {lists:reverse(Acc), "", Rest};
346
urlsplit_path([C | Rest], Acc) ->
347
    urlsplit_path(Rest, [C | Acc]).
348
 
349
urlsplit_query(Query) ->
350
    urlsplit_query(Query, []).
351
 
352
urlsplit_query("", Acc) ->
353
    {lists:reverse(Acc), ""};
354
urlsplit_query("#" ++ Rest, Acc) ->
355
    {lists:reverse(Acc), Rest};
356
urlsplit_query([C | Rest], Acc) ->
357
    urlsplit_query(Rest, [C | Acc]).
358
 
359
%% @spec guess_mime(string()) -> string()
360
%% @doc  Guess the mime type of a file by the extension of its filename.
361
guess_mime(File) ->
362
    case filename:basename(File) of
363
        "crossdomain.xml" ->
364
            "text/x-cross-domain-policy";
365
        Name ->
366
            case mochiweb_mime:from_extension(filename:extension(Name)) of
367
                undefined ->
368
                    "text/plain";
369
                Mime ->
370
                    Mime
371
            end
372
    end.
373
 
374
%% @spec parse_header(string()) -> {Type, [{K, V}]}
375
%% @doc  Parse a Content-Type like header, return the main Content-Type
376
%%       and a property list of options.
377
parse_header(String) ->
378
    %% TODO: This is exactly as broken as Python's cgi module.
379
    %%       Should parse properly like mochiweb_cookies.
380
    [Type | Parts] = [string:strip(S) || S <- string:tokens(String, ";")],
381
    F = fun (S, Acc) ->
382
                case lists:splitwith(fun (C) -> C =/= $= end, S) of
383
                    {"", _} ->
384
                        %% Skip anything with no name
385
                        Acc;
386
                    {_, ""} ->
387
                        %% Skip anything with no value
388
                        Acc;
389
                    {Name, [$\= | Value]} ->
390
                        [{string:to_lower(string:strip(Name)),
391
                          unquote_header(string:strip(Value))} | Acc]
392
                end
393
        end,
394
    {string:to_lower(Type),
395
     lists:foldr(F, [], Parts)}.
396
 
397
unquote_header("\"" ++ Rest) ->
398
    unquote_header(Rest, []);
399
unquote_header(S) ->
400
    S.
401
 
402
unquote_header("", Acc) ->
403
    lists:reverse(Acc);
404
unquote_header("\"", Acc) ->
405
    lists:reverse(Acc);
406
unquote_header([$\\, C | Rest], Acc) ->
407
    unquote_header(Rest, [C | Acc]);
408
unquote_header([C | Rest], Acc) ->
409
    unquote_header(Rest, [C | Acc]).
410
 
411
%% @spec record_to_proplist(Record, Fields) -> proplist()
412
%% @doc calls record_to_proplist/3 with a default TypeKey of '__record'
413
record_to_proplist(Record, Fields) ->
414
    record_to_proplist(Record, Fields, '__record').
415
 
416
%% @spec record_to_proplist(Record, Fields, TypeKey) -> proplist()
417
%% @doc Return a proplist of the given Record with each field in the
418
%%      Fields list set as a key with the corresponding value in the Record.
419
%%      TypeKey is the key that is used to store the record type
420
%%      Fields should be obtained by calling record_info(fields, record_type)
421
%%      where record_type is the record type of Record
422
record_to_proplist(Record, Fields, TypeKey)
423
  when tuple_size(Record) - 1 =:= length(Fields) ->
424
    lists:zip([TypeKey | Fields], tuple_to_list(Record)).
425
 
426
 
427
shell_quote([], Acc) ->
428
    lists:reverse([$\" | Acc]);
429
shell_quote([C | Rest], Acc) when C =:= $\" orelse C =:= $\` orelse
430
                                  C =:= $\\ orelse C =:= $\$ ->
431
    shell_quote(Rest, [C, $\\ | Acc]);
432
shell_quote([C | Rest], Acc) ->
433
    shell_quote(Rest, [C | Acc]).
434
 
435
%% @spec parse_qvalues(string()) -> [qvalue()] | invalid_qvalue_string
436
%% @type qvalue() = {media_type() | encoding() , float()}.
437
%% @type media_type() = string().
438
%% @type encoding() = string().
439
%%
440
%% @doc Parses a list (given as a string) of elements with Q values associated
441
%%      to them. Elements are separated by commas and each element is separated
442
%%      from its Q value by a semicolon. Q values are optional but when missing
443
%%      the value of an element is considered as 1.0. A Q value is always in the
444
%%      range [0.0, 1.0]. A Q value list is used for example as the value of the
445
%%      HTTP "Accept" and "Accept-Encoding" headers.
446
%%
447
%%      Q values are described in section 2.9 of the RFC 2616 (HTTP 1.1).
448
%%
449
%%      Example:
450
%%
451
%%      parse_qvalues("gzip; q=0.5, deflate, identity;q=0.0") ->
452
%%          [{"gzip", 0.5}, {"deflate", 1.0}, {"identity", 0.0}]
453
%%
454
parse_qvalues(QValuesStr) ->
455
    try
456
        lists:map(
457
            fun(Pair) ->
458
                [Type | Params] = string:tokens(Pair, ";"),
459
                NormParams = normalize_media_params(Params),
460
                {Q, NonQParams} = extract_q(NormParams),
461
                {string:join([string:strip(Type) | NonQParams], ";"), Q}
462
            end,
463
            string:tokens(string:to_lower(QValuesStr), ",")
464
        )
465
    catch
466
        _Type:_Error ->
467
            invalid_qvalue_string
468
    end.
469
 
470
normalize_media_params(Params) ->
471
    {ok, Re} = re:compile("\\s"),
472
    normalize_media_params(Re, Params, []).
473
 
474
normalize_media_params(_Re, [], Acc) ->
475
    lists:reverse(Acc);
476
normalize_media_params(Re, [Param | Rest], Acc) ->
477
    NormParam = re:replace(Param, Re, "", [global, {return, list}]),
478
    normalize_media_params(Re, Rest, [NormParam | Acc]).
479
 
480
extract_q(NormParams) ->
481
    {ok, KVRe} = re:compile("^([^=]+)=([^=]+)$"),
482
    {ok, QRe} = re:compile("^((?:0|1)(?:\\.\\d{1,3})?)$"),
483
    extract_q(KVRe, QRe, NormParams, []).
484
 
485
extract_q(_KVRe, _QRe, [], Acc) ->
486
    {1.0, lists:reverse(Acc)};
487
extract_q(KVRe, QRe, [Param | Rest], Acc) ->
488
    case re:run(Param, KVRe, [{capture, [1, 2], list}]) of
489
        {match, [Name, Value]} ->
490
            case Name of
491
            "q" ->
492
                {match, [Q]} = re:run(Value, QRe, [{capture, [1], list}]),
493
                QVal = case Q of
494
                    "0" ->
495
                        0.0;
496
                    "1" ->
497
                        1.0;
498
                    Else ->
499
                        list_to_float(Else)
500
                end,
501
                case QVal < 0.0 orelse QVal > 1.0 of
502
                false ->
503
                    {QVal, lists:reverse(Acc) ++ Rest}
504
                end;
505
            _ ->
506
                extract_q(KVRe, QRe, Rest, [Param | Acc])
507
            end
508
    end.
509
 
510
%% @spec pick_accepted_encodings([qvalue()], [encoding()], encoding()) ->
511
%%    [encoding()]
512
%%
513
%% @doc Determines which encodings specified in the given Q values list are
514
%%      valid according to a list of supported encodings and a default encoding.
515
%%
516
%%      The returned list of encodings is sorted, descendingly, according to the
517
%%      Q values of the given list. The last element of this list is the given
518
%%      default encoding unless this encoding is explicitily or implicitily
519
%%      marked with a Q value of 0.0 in the given Q values list.
520
%%      Note: encodings with the same Q value are kept in the same order as
521
%%            found in the input Q values list.
522
%%
523
%%      This encoding picking process is described in section 14.3 of the
524
%%      RFC 2616 (HTTP 1.1).
525
%%
526
%%      Example:
527
%%
528
%%      pick_accepted_encodings(
529
%%          [{"gzip", 0.5}, {"deflate", 1.0}],
530
%%          ["gzip", "identity"],
531
%%          "identity"
532
%%      ) ->
533
%%          ["gzip", "identity"]
534
%%
535
pick_accepted_encodings(AcceptedEncs, SupportedEncs, DefaultEnc) ->
536
    SortedQList = lists:reverse(
537
        lists:sort(fun({_, Q1}, {_, Q2}) -> Q1 < Q2 end, AcceptedEncs)
538
    ),
539
    {Accepted, Refused} = lists:foldr(
540
        fun({E, Q}, {A, R}) ->
541
            case Q > 0.0 of
542
                true ->
543
                    {[E | A], R};
544
                false ->
545
                    {A, [E | R]}
546
            end
547
        end,
548
        {[], []},
549
        SortedQList
550
    ),
551
    Refused1 = lists:foldr(
552
        fun(Enc, Acc) ->
553
            case Enc of
554
                "*" ->
555
                    lists:subtract(SupportedEncs, Accepted) ++ Acc;
556
                _ ->
557
                    [Enc | Acc]
558
            end
559
        end,
560
        [],
561
        Refused
562
    ),
563
    Accepted1 = lists:foldr(
564
        fun(Enc, Acc) ->
565
            case Enc of
566
                "*" ->
567
                    lists:subtract(SupportedEncs, Accepted ++ Refused1) ++ Acc;
568
                _ ->
569
                    [Enc | Acc]
570
            end
571
        end,
572
        [],
573
        Accepted
574
    ),
575
    Accepted2 = case lists:member(DefaultEnc, Accepted1) of
576
        true ->
577
            Accepted1;
578
        false ->
579
            Accepted1 ++ [DefaultEnc]
580
    end,
581
    [E || E <- Accepted2, lists:member(E, SupportedEncs),
582
        not lists:member(E, Refused1)].
583
 
584
make_io(Atom) when is_atom(Atom) ->
585
    atom_to_list(Atom);
586
make_io(Integer) when is_integer(Integer) ->
587
    integer_to_list(Integer);
588
make_io(Io) when is_list(Io); is_binary(Io) ->
589
    Io.
590
 
591
%% @spec normalize_path(string()) -> string()
592
%% @doc Remove duplicate slashes from an uri path ("//foo///bar////" becomes
593
%%      "/foo/bar/").
594
%%      Per RFC 3986, all but the last path segment must be non-empty.
595
normalize_path(Path) ->
596
	normalize_path(Path, []).
597
 
598
normalize_path([], Acc) ->
599
        lists:reverse(Acc);
600
normalize_path("/" ++ Path, "/" ++ _ = Acc) ->
601
        normalize_path(Path, Acc);
602
normalize_path([C|Path], Acc) ->
603
        normalize_path(Path, [C|Acc]).
604
 
605
-ifdef(rand_mod_unavailable).
606
rand_uniform(Start, End) ->
607
    crypto:rand_uniform(Start, End).
608
-else.
609
rand_uniform(Start, End) ->
610
    Start + rand:uniform(End - Start) - 1.
611
-endif.
612
 
613
%%
614
%% Tests
615
%%
616
-ifdef(TEST).
617
-include_lib("eunit/include/eunit.hrl").
618
 
619
make_io_test() ->
620
    ?assertEqual(
621
       <<"atom">>,
622
       iolist_to_binary(make_io(atom))),
623
    ?assertEqual(
624
       <<"20">>,
625
       iolist_to_binary(make_io(20))),
626
    ?assertEqual(
627
       <<"list">>,
628
       iolist_to_binary(make_io("list"))),
629
    ?assertEqual(
630
       <<"binary">>,
631
       iolist_to_binary(make_io(<<"binary">>))),
632
    ok.
633
 
634
-record(test_record, {field1=f1, field2=f2}).
635
record_to_proplist_test() ->
636
    ?assertEqual(
637
       [{'__record', test_record},
638
        {field1, f1},
639
        {field2, f2}],
640
       record_to_proplist(#test_record{}, record_info(fields, test_record))),
641
    ?assertEqual(
642
       [{'typekey', test_record},
643
        {field1, f1},
644
        {field2, f2}],
645
       record_to_proplist(#test_record{},
646
                          record_info(fields, test_record),
647
                          typekey)),
648
    ok.
649
 
650
shell_quote_test() ->
651
    ?assertEqual(
652
       "\"foo \\$bar\\\"\\`' baz\"",
653
       shell_quote("foo $bar\"`' baz")),
654
    ok.
655
 
656
cmd_port_test_spool(Port, Acc) ->
657
    receive
658
        {Port, eof} ->
659
            Acc;
660
        {Port, {data, {eol, Data}}} ->
661
            cmd_port_test_spool(Port, ["\n", Data | Acc]);
662
        {Port, Unknown} ->
663
            throw({unknown, Unknown})
664
    after 1000 ->
665
            throw(timeout)
666
    end.
667
 
668
cmd_port_test() ->
669
    Port = cmd_port(["echo", "$bling$ `word`!"],
670
                    [eof, stream, {line, 4096}]),
671
    Res = try lists:append(lists:reverse(cmd_port_test_spool(Port, [])))
672
          after catch port_close(Port)
673
          end,
674
    self() ! {Port, wtf},
675
    try cmd_port_test_spool(Port, [])
676
    catch throw:{unknown, wtf} -> ok
677
    end,
678
    try cmd_port_test_spool(Port, [])
679
    catch throw:timeout -> ok
680
    end,
681
    ?assertEqual(
682
       "$bling$ `word`!\n",
683
       Res).
684
 
685
cmd_test() ->
686
    ?assertEqual(
687
       "$bling$ `word`!\n",
688
       cmd(["echo", "$bling$ `word`!"])),
689
    ok.
690
 
691
cmd_string_test() ->
692
    ?assertEqual(
693
       "\"echo\" \"\\$bling\\$ \\`word\\`!\"",
694
       cmd_string(["echo", "$bling$ `word`!"])),
695
    ok.
696
 
697
cmd_status_test() ->
698
    ?assertEqual(
699
       {0, <<"$bling$ `word`!\n">>},
700
       cmd_status(["echo", "$bling$ `word`!"])),
701
    ok.
702
 
703
 
704
parse_header_test() ->
705
    ?assertEqual(
706
       {"multipart/form-data", [{"boundary", "AaB03x"}]},
707
       parse_header("multipart/form-data; boundary=AaB03x")),
708
    %% This tests (currently) intentionally broken behavior
709
    ?assertEqual(
710
       {"multipart/form-data",
711
        [{"b", ""},
712
         {"cgi", "is"},
713
         {"broken", "true\"e"}]},
714
       parse_header("multipart/form-data;b=;cgi=\"i\\s;broken=true\"e;=z;z")),
715
    ok.
716
 
717
guess_mime_test() ->
718
    ?assertEqual("text/plain", guess_mime("")),
719
    ?assertEqual("text/plain", guess_mime(".text")),
720
    ?assertEqual("application/zip", guess_mime(".zip")),
721
    ?assertEqual("application/zip", guess_mime("x.zip")),
722
    ?assertEqual("text/html", guess_mime("x.html")),
723
    ?assertEqual("application/xhtml+xml", guess_mime("x.xhtml")),
724
    ?assertEqual("text/x-cross-domain-policy", guess_mime("crossdomain.xml")),
725
    ?assertEqual("text/x-cross-domain-policy", guess_mime("www/crossdomain.xml")),
726
    ok.
727
 
728
path_split_test() ->
729
    {"", "foo/bar"} = path_split("/foo/bar"),
730
    {"foo", "bar"} = path_split("foo/bar"),
731
    {"bar", ""} = path_split("bar"),
732
    ok.
733
 
734
urlsplit_test() ->
735
    {"", "", "/foo", "", "bar?baz"} = urlsplit("/foo#bar?baz"),
736
    {"http", "host:port", "/foo", "", "bar?baz"} =
737
        urlsplit("http://host:port/foo#bar?baz"),
738
    {"http", "host", "", "", ""} = urlsplit("http://host"),
739
    {"", "", "/wiki/Category:Fruit", "", ""} =
740
        urlsplit("/wiki/Category:Fruit"),
741
    ok.
742
 
743
urlsplit_path_test() ->
744
    {"/foo/bar", "", ""} = urlsplit_path("/foo/bar"),
745
    {"/foo", "baz", ""} = urlsplit_path("/foo?baz"),
746
    {"/foo", "", "bar?baz"} = urlsplit_path("/foo#bar?baz"),
747
    {"/foo", "", "bar?baz#wibble"} = urlsplit_path("/foo#bar?baz#wibble"),
748
    {"/foo", "bar", "baz"} = urlsplit_path("/foo?bar#baz"),
749
    {"/foo", "bar?baz", "baz"} = urlsplit_path("/foo?bar?baz#baz"),
750
    ok.
751
 
752
urlunsplit_test() ->
753
    "/foo#bar?baz" = urlunsplit({"", "", "/foo", "", "bar?baz"}),
754
    "http://host:port/foo#bar?baz" =
755
        urlunsplit({"http", "host:port", "/foo", "", "bar?baz"}),
756
    ok.
757
 
758
urlunsplit_path_test() ->
759
    "/foo/bar" = urlunsplit_path({"/foo/bar", "", ""}),
760
    "/foo?baz" = urlunsplit_path({"/foo", "baz", ""}),
761
    "/foo#bar?baz" = urlunsplit_path({"/foo", "", "bar?baz"}),
762
    "/foo#bar?baz#wibble" = urlunsplit_path({"/foo", "", "bar?baz#wibble"}),
763
    "/foo?bar#baz" = urlunsplit_path({"/foo", "bar", "baz"}),
764
    "/foo?bar?baz#baz" = urlunsplit_path({"/foo", "bar?baz", "baz"}),
765
    ok.
766
 
767
join_test() ->
768
    ?assertEqual("foo,bar,baz",
769
                  join(["foo", "bar", "baz"], $,)),
770
    ?assertEqual("foo,bar,baz",
771
                  join(["foo", "bar", "baz"], ",")),
772
    ?assertEqual("foo bar",
773
                  join([["foo", " bar"]], ",")),
774
    ?assertEqual("foo bar,baz",
775
                  join([["foo", " bar"], "baz"], ",")),
776
    ?assertEqual("foo",
777
                  join(["foo"], ",")),
778
    ?assertEqual("foobarbaz",
779
                  join(["foo", "bar", "baz"], "")),
780
    ?assertEqual("foo" ++ [<<>>] ++ "bar" ++ [<<>>] ++ "baz",
781
                 join(["foo", "bar", "baz"], <<>>)),
782
    ?assertEqual("foobar" ++ [<<"baz">>],
783
                 join(["foo", "bar", <<"baz">>], "")),
784
    ?assertEqual("",
785
                 join([], "any")),
786
    ok.
787
 
788
quote_plus_test() ->
789
    "foo" = quote_plus(foo),
790
    "1" = quote_plus(1),
791
    "1.1" = quote_plus(1.1),
792
    "foo" = quote_plus("foo"),
793
    "foo+bar" = quote_plus("foo bar"),
794
    "foo%0A" = quote_plus("foo\n"),
795
    "foo%0A" = quote_plus("foo\n"),
796
    "foo%3B%26%3D" = quote_plus("foo;&="),
797
    "foo%3B%26%3D" = quote_plus(<<"foo;&=">>),
798
    ok.
799
 
800
unquote_test() ->
801
    ?assertEqual("foo bar",
802
                 unquote("foo+bar")),
803
    ?assertEqual("foo bar",
804
                 unquote("foo%20bar")),
805
    ?assertEqual("foo\r\n",
806
                 unquote("foo%0D%0A")),
807
    ?assertEqual("foo\r\n",
808
                 unquote(<<"foo%0D%0A">>)),
809
    ok.
810
 
811
urlencode_test() ->
812
    "foo=bar&baz=wibble+%0D%0A&z=1" = urlencode([{foo, "bar"},
813
                                                 {"baz", "wibble \r\n"},
814
                                                 {z, 1}]),
815
    ok.
816
 
817
parse_qs_test() ->
818
    ?assertEqual(
819
       [{"foo", "bar"}, {"baz", "wibble \r\n"}, {"z", "1"}],
820
       parse_qs("foo=bar&baz=wibble+%0D%0a&z=1")),
821
    ?assertEqual(
822
       [{"", "bar"}, {"baz", "wibble \r\n"}, {"z", ""}],
823
       parse_qs("=bar&baz=wibble+%0D%0a&z=")),
824
    ?assertEqual(
825
       [{"foo", "bar"}, {"baz", "wibble \r\n"}, {"z", "1"}],
826
       parse_qs(<<"foo=bar&baz=wibble+%0D%0a&z=1">>)),
827
    ?assertEqual(
828
       [],
829
       parse_qs("")),
830
    ?assertEqual(
831
       [{"foo", ""}, {"bar", ""}, {"baz", ""}],
832
       parse_qs("foo;bar&baz")),
833
    ok.
834
 
835
partition_test() ->
836
    {"foo", "", ""} = partition("foo", "/"),
837
    {"foo", "/", "bar"} = partition("foo/bar", "/"),
838
    {"foo", "/", ""} = partition("foo/", "/"),
839
    {"", "/", "bar"} = partition("/bar", "/"),
840
    {"f", "oo/ba", "r"} = partition("foo/bar", "oo/ba"),
841
    ok.
842
 
843
safe_relative_path_test() ->
844
    "foo" = safe_relative_path("foo"),
845
    "foo/" = safe_relative_path("foo/"),
846
    "foo" = safe_relative_path("foo/bar/.."),
847
    "bar" = safe_relative_path("foo/../bar"),
848
    "bar/" = safe_relative_path("foo/../bar/"),
849
    "" = safe_relative_path("foo/.."),
850
    "" = safe_relative_path("foo/../"),
851
    undefined = safe_relative_path("/foo"),
852
    undefined = safe_relative_path("../foo"),
853
    undefined = safe_relative_path("foo/../.."),
854
    undefined = safe_relative_path("foo//"),
855
    undefined = safe_relative_path("foo\\bar"),
856
    ok.
857
 
858
parse_qvalues_test() ->
859
    [] = parse_qvalues(""),
860
    [{"identity", 0.0}] = parse_qvalues("identity;q=0"),
861
    [{"identity", 0.0}] = parse_qvalues("identity ;q=0"),
862
    [{"identity", 0.0}] = parse_qvalues(" identity; q =0 "),
863
    [{"identity", 0.0}] = parse_qvalues("identity ; q = 0"),
864
    [{"identity", 0.0}] = parse_qvalues("identity ; q= 0.0"),
865
    [{"gzip", 1.0}, {"deflate", 1.0}, {"identity", 0.0}] = parse_qvalues(
866
        "gzip,deflate,identity;q=0.0"
867
    ),
868
    [{"deflate", 1.0}, {"gzip", 1.0}, {"identity", 0.0}] = parse_qvalues(
869
        "deflate,gzip,identity;q=0.0"
870
    ),
871
    [{"gzip", 1.0}, {"deflate", 1.0}, {"gzip", 1.0}, {"identity", 0.0}] =
872
        parse_qvalues("gzip,deflate,gzip,identity;q=0"),
873
    [{"gzip", 1.0}, {"deflate", 1.0}, {"identity", 0.0}] = parse_qvalues(
874
        "gzip, deflate , identity; q=0.0"
875
    ),
876
    [{"gzip", 1.0}, {"deflate", 1.0}, {"identity", 0.0}] = parse_qvalues(
877
        "gzip; q=1, deflate;q=1.0, identity;q=0.0"
878
    ),
879
    [{"gzip", 0.5}, {"deflate", 1.0}, {"identity", 0.0}] = parse_qvalues(
880
        "gzip; q=0.5, deflate;q=1.0, identity;q=0"
881
    ),
882
    [{"gzip", 0.5}, {"deflate", 1.0}, {"identity", 0.0}] = parse_qvalues(
883
        "gzip; q=0.5, deflate , identity;q=0.0"
884
    ),
885
    [{"gzip", 0.5}, {"deflate", 0.8}, {"identity", 0.0}] = parse_qvalues(
886
        "gzip; q=0.5, deflate;q=0.8, identity;q=0.0"
887
    ),
888
    [{"gzip", 0.5}, {"deflate", 1.0}, {"identity", 1.0}] = parse_qvalues(
889
        "gzip; q=0.5,deflate,identity"
890
    ),
891
    [{"gzip", 0.5}, {"deflate", 1.0}, {"identity", 1.0}, {"identity", 1.0}] =
892
        parse_qvalues("gzip; q=0.5,deflate,identity, identity "),
893
    [{"text/html;level=1", 1.0}, {"text/plain", 0.5}] =
894
        parse_qvalues("text/html;level=1, text/plain;q=0.5"),
895
    [{"text/html;level=1", 0.3}, {"text/plain", 1.0}] =
896
        parse_qvalues("text/html;level=1;q=0.3, text/plain"),
897
    [{"text/html;level=1", 0.3}, {"text/plain", 1.0}] =
898
        parse_qvalues("text/html; level = 1; q = 0.3, text/plain"),
899
    [{"text/html;level=1", 0.3}, {"text/plain", 1.0}] =
900
        parse_qvalues("text/html;q=0.3;level=1, text/plain"),
901
    invalid_qvalue_string = parse_qvalues("gzip; q=1.1, deflate"),
902
    invalid_qvalue_string = parse_qvalues("gzip; q=0.5, deflate;q=2"),
903
    invalid_qvalue_string = parse_qvalues("gzip, deflate;q=AB"),
904
    invalid_qvalue_string = parse_qvalues("gzip; q=2.1, deflate"),
905
    invalid_qvalue_string = parse_qvalues("gzip; q=0.1234, deflate"),
906
    invalid_qvalue_string = parse_qvalues("text/html;level=1;q=0.3, text/html;level"),
907
    ok.
908
 
909
pick_accepted_encodings_test() ->
910
    ["identity"] = pick_accepted_encodings(
911
        [],
912
        ["gzip", "identity"],
913
        "identity"
914
    ),
915
    ["gzip", "identity"] = pick_accepted_encodings(
916
        [{"gzip", 1.0}],
917
        ["gzip", "identity"],
918
        "identity"
919
    ),
920
    ["identity"] = pick_accepted_encodings(
921
        [{"gzip", 0.0}],
922
        ["gzip", "identity"],
923
        "identity"
924
    ),
925
    ["gzip", "identity"] = pick_accepted_encodings(
926
        [{"gzip", 1.0}, {"deflate", 1.0}],
927
        ["gzip", "identity"],
928
        "identity"
929
    ),
930
    ["gzip", "identity"] = pick_accepted_encodings(
931
        [{"gzip", 0.5}, {"deflate", 1.0}],
932
        ["gzip", "identity"],
933
        "identity"
934
    ),
935
    ["identity"] = pick_accepted_encodings(
936
        [{"gzip", 0.0}, {"deflate", 0.0}],
937
        ["gzip", "identity"],
938
        "identity"
939
    ),
940
    ["gzip"] = pick_accepted_encodings(
941
        [{"gzip", 1.0}, {"deflate", 1.0}, {"identity", 0.0}],
942
        ["gzip", "identity"],
943
        "identity"
944
    ),
945
    ["gzip", "deflate", "identity"] = pick_accepted_encodings(
946
        [{"gzip", 1.0}, {"deflate", 1.0}],
947
        ["gzip", "deflate", "identity"],
948
        "identity"
949
    ),
950
    ["gzip", "deflate"] = pick_accepted_encodings(
951
        [{"gzip", 1.0}, {"deflate", 1.0}, {"identity", 0.0}],
952
        ["gzip", "deflate", "identity"],
953
        "identity"
954
    ),
955
    ["deflate", "gzip", "identity"] = pick_accepted_encodings(
956
        [{"gzip", 0.2}, {"deflate", 1.0}],
957
        ["gzip", "deflate", "identity"],
958
        "identity"
959
    ),
960
    ["deflate", "deflate", "gzip", "identity"] = pick_accepted_encodings(
961
        [{"gzip", 0.2}, {"deflate", 1.0}, {"deflate", 1.0}],
962
        ["gzip", "deflate", "identity"],
963
        "identity"
964
    ),
965
    ["deflate", "gzip", "gzip", "identity"] = pick_accepted_encodings(
966
        [{"gzip", 0.2}, {"deflate", 1.0}, {"gzip", 1.0}],
967
        ["gzip", "deflate", "identity"],
968
        "identity"
969
    ),
970
    ["gzip", "deflate", "gzip", "identity"] = pick_accepted_encodings(
971
        [{"gzip", 0.2}, {"deflate", 0.9}, {"gzip", 1.0}],
972
        ["gzip", "deflate", "identity"],
973
        "identity"
974
    ),
975
    [] = pick_accepted_encodings(
976
        [{"*", 0.0}],
977
        ["gzip", "deflate", "identity"],
978
        "identity"
979
    ),
980
    ["gzip", "deflate", "identity"] = pick_accepted_encodings(
981
        [{"*", 1.0}],
982
        ["gzip", "deflate", "identity"],
983
        "identity"
984
    ),
985
    ["gzip", "deflate", "identity"] = pick_accepted_encodings(
986
        [{"*", 0.6}],
987
        ["gzip", "deflate", "identity"],
988
        "identity"
989
    ),
990
    ["gzip"] = pick_accepted_encodings(
991
        [{"gzip", 1.0}, {"*", 0.0}],
992
        ["gzip", "deflate", "identity"],
993
        "identity"
994
    ),
995
    ["gzip", "deflate"] = pick_accepted_encodings(
996
        [{"gzip", 1.0}, {"deflate", 0.6}, {"*", 0.0}],
997
        ["gzip", "deflate", "identity"],
998
        "identity"
999
    ),
1000
    ["deflate", "gzip"] = pick_accepted_encodings(
1001
        [{"gzip", 0.5}, {"deflate", 1.0}, {"*", 0.0}],
1002
        ["gzip", "deflate", "identity"],
1003
        "identity"
1004
    ),
1005
    ["gzip", "identity"] = pick_accepted_encodings(
1006
        [{"deflate", 0.0}, {"*", 1.0}],
1007
        ["gzip", "deflate", "identity"],
1008
        "identity"
1009
    ),
1010
    ["gzip", "identity"] = pick_accepted_encodings(
1011
        [{"*", 1.0}, {"deflate", 0.0}],
1012
        ["gzip", "deflate", "identity"],
1013
        "identity"
1014
    ),
1015
    ok.
1016
 
1017
normalize_path_test() ->
1018
	"" = normalize_path(""),
1019
	"/" = normalize_path("/"),
1020
	"/" = normalize_path("//"),
1021
	"/" = normalize_path("///"),
1022
	"foo" = normalize_path("foo"),
1023
	"/foo" = normalize_path("/foo"),
1024
	"/foo" = normalize_path("//foo"),
1025
	"/foo" = normalize_path("///foo"),
1026
	"foo/" = normalize_path("foo/"),
1027
	"foo/" = normalize_path("foo//"),
1028
	"foo/" = normalize_path("foo///"),
1029
	"foo/bar" = normalize_path("foo/bar"),
1030
	"foo/bar" = normalize_path("foo//bar"),
1031
	"foo/bar" = normalize_path("foo///bar"),
1032
	"foo/bar" = normalize_path("foo////bar"),
1033
	"/foo/bar" = normalize_path("/foo/bar"),
1034
	"/foo/bar" = normalize_path("/foo////bar"),
1035
	"/foo/bar" = normalize_path("////foo/bar"),
1036
	"/foo/bar" = normalize_path("////foo///bar"),
1037
	"/foo/bar" = normalize_path("////foo////bar"),
1038
	"/foo/bar/" = normalize_path("/foo/bar/"),
1039
	"/foo/bar/" = normalize_path("////foo/bar/"),
1040
	"/foo/bar/" = normalize_path("/foo////bar/"),
1041
	"/foo/bar/" = normalize_path("/foo/bar////"),
1042
	"/foo/bar/" = normalize_path("///foo////bar/"),
1043
	"/foo/bar/" = normalize_path("////foo/bar////"),
1044
	"/foo/bar/" = normalize_path("/foo///bar////"),
1045
	"/foo/bar/" = normalize_path("////foo///bar////"),
1046
	ok.
1047
 
1048
-endif.