~ubuntu-branches/ubuntu/trusty/rabbitmq-server/trusty

« back to all changes in this revision

Viewing changes to plugins-src/mochiweb-wrapper/mochiweb-git/src/mochiweb_request.erl.orig

  • Committer: Package Import Robot
  • Author(s): Emile Joubert
  • Date: 2013-08-15 15:24:15 UTC
  • mfrom: (0.5.6) (0.1.42 sid)
  • Revision ID: package-import@ubuntu.com-20130815152415-savq3fpv1vhou8eh
Tags: 3.1.5-1
New upstream release

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
%% @author Bob Ippolito <bob@mochimedia.com>
 
2
%% @copyright 2007 Mochi Media, Inc.
 
3
 
 
4
%% @doc MochiWeb HTTP Request abstraction.
 
5
 
 
6
-module(mochiweb_request).
 
7
-author('bob@mochimedia.com').
 
8
 
 
9
-include_lib("kernel/include/file.hrl").
 
10
-include("internal.hrl").
 
11
 
 
12
-define(QUIP, "Any of you quaids got a smint?").
 
13
 
 
14
-export([new/5]).
 
15
-export([get_header_value/2, get_primary_header_value/2, get_combined_header_value/2, get/2, dump/1]).
 
16
-export([send/2, recv/2, recv/3, recv_body/1, recv_body/2, stream_body/4]).
 
17
-export([start_response/2, start_response_length/2, start_raw_response/2]).
 
18
-export([respond/2, ok/2]).
 
19
-export([not_found/1, not_found/2]).
 
20
-export([parse_post/1, parse_qs/1]).
 
21
-export([should_close/1, cleanup/1]).
 
22
-export([parse_cookie/1, get_cookie_value/2]).
 
23
-export([serve_file/3, serve_file/4]).
 
24
-export([accepted_encodings/2]).
 
25
-export([accepts_content_type/2, accepted_content_types/2]).
 
26
 
 
27
-define(SAVE_QS, mochiweb_request_qs).
 
28
-define(SAVE_PATH, mochiweb_request_path).
 
29
-define(SAVE_RECV, mochiweb_request_recv).
 
30
-define(SAVE_BODY, mochiweb_request_body).
 
31
-define(SAVE_BODY_LENGTH, mochiweb_request_body_length).
 
32
-define(SAVE_POST, mochiweb_request_post).
 
33
-define(SAVE_COOKIE, mochiweb_request_cookie).
 
34
-define(SAVE_FORCE_CLOSE, mochiweb_request_force_close).
 
35
 
 
36
%% @type key() = atom() | string() | binary()
 
37
%% @type value() = atom() | string() | binary() | integer()
 
38
%% @type headers(). A mochiweb_headers structure.
 
39
%% @type request(). A mochiweb_request parameterized module instance.
 
40
%% @type response(). A mochiweb_response parameterized module instance.
 
41
%% @type ioheaders() = headers() | [{key(), value()}].
 
42
 
 
43
% 5 minute default idle timeout
 
44
-define(IDLE_TIMEOUT, 300000).
 
45
 
 
46
% Maximum recv_body() length of 1MB
 
47
-define(MAX_RECV_BODY, (1024*1024)).
 
48
 
 
49
%% @spec new(Socket, Method, RawPath, Version, headers()) -> request()
 
50
%% @doc Create a new request instance.
 
51
new(Socket, Method, RawPath, Version, Headers) ->
 
52
    {?MODULE, [Socket, Method, RawPath, Version, Headers]}.
 
53
 
 
54
%% @spec get_header_value(K, request()) -> undefined | Value
 
55
%% @doc Get the value of a given request header.
 
56
get_header_value(K, {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}) ->
 
57
    mochiweb_headers:get_value(K, Headers).
 
58
 
 
59
get_primary_header_value(K, {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}) ->
 
60
    mochiweb_headers:get_primary_value(K, Headers).
 
61
 
 
62
get_combined_header_value(K, {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}) ->
 
63
    mochiweb_headers:get_combined_value(K, Headers).
 
64
 
 
65
%% @type field() = socket | scheme | method | raw_path | version | headers | peer | path | body_length | range
 
66
 
 
67
%% @spec get(field(), request()) -> term()
 
68
%% @doc Return the internal representation of the given field. If
 
69
%%      <code>socket</code> is requested on a HTTPS connection, then
 
70
%%      an ssl socket will be returned as <code>{ssl, SslSocket}</code>.
 
71
%%      You can use <code>SslSocket</code> with the <code>ssl</code>
 
72
%%      application, eg: <code>ssl:peercert(SslSocket)</code>.
 
73
get(socket, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) ->
 
74
    Socket;
 
75
get(scheme, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) ->
 
76
    case mochiweb_socket:type(Socket) of
 
77
        plain ->
 
78
            http;
 
79
        ssl ->
 
80
            https
 
81
    end;
 
82
get(method, {?MODULE, [_Socket, Method, _RawPath, _Version, _Headers]}) ->
 
83
    Method;
 
84
get(raw_path, {?MODULE, [_Socket, _Method, RawPath, _Version, _Headers]}) ->
 
85
    RawPath;
 
86
get(version, {?MODULE, [_Socket, _Method, _RawPath, Version, _Headers]}) ->
 
87
    Version;
 
88
get(headers, {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}) ->
 
89
    Headers;
 
90
get(peer, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
91
    case mochiweb_socket:peername(Socket) of
 
92
        {ok, {Addr={10, _, _, _}, _Port}} ->
 
93
            case get_header_value("x-forwarded-for", THIS) of
 
94
                undefined ->
 
95
                    inet_parse:ntoa(Addr);
 
96
                Hosts ->
 
97
                    string:strip(lists:last(string:tokens(Hosts, ",")))
 
98
            end;
 
99
        {ok, {{127, 0, 0, 1}, _Port}} ->
 
100
            case get_header_value("x-forwarded-for", THIS) of
 
101
                undefined ->
 
102
                    "127.0.0.1";
 
103
                Hosts ->
 
104
                    string:strip(lists:last(string:tokens(Hosts, ",")))
 
105
            end;
 
106
        {ok, {Addr, _Port}} ->
 
107
            inet_parse:ntoa(Addr);
 
108
        {error, enotconn} ->
 
109
            exit(normal)
 
110
    end;
 
111
get(path, {?MODULE, [_Socket, _Method, RawPath, _Version, _Headers]}) ->
 
112
    case erlang:get(?SAVE_PATH) of
 
113
        undefined ->
 
114
            {Path0, _, _} = mochiweb_util:urlsplit_path(RawPath),
 
115
            Path = mochiweb_util:unquote(Path0),
 
116
            put(?SAVE_PATH, Path),
 
117
            Path;
 
118
        Cached ->
 
119
            Cached
 
120
    end;
 
121
get(body_length, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
122
    case erlang:get(?SAVE_BODY_LENGTH) of
 
123
        undefined ->
 
124
            BodyLength = body_length(THIS),
 
125
            put(?SAVE_BODY_LENGTH, {cached, BodyLength}),
 
126
            BodyLength;
 
127
        {cached, Cached} ->
 
128
            Cached
 
129
    end;
 
130
get(range, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
131
    case get_header_value(range, THIS) of
 
132
        undefined ->
 
133
            undefined;
 
134
        RawRange ->
 
135
            mochiweb_http:parse_range_request(RawRange)
 
136
    end.
 
137
 
 
138
%% @spec dump(request()) -> {mochiweb_request, [{atom(), term()}]}
 
139
%% @doc Dump the internal representation to a "human readable" set of terms
 
140
%%      for debugging/inspection purposes.
 
141
dump({?MODULE, [_Socket, Method, RawPath, Version, Headers]}) ->
 
142
    {?MODULE, [{method, Method},
 
143
               {version, Version},
 
144
               {raw_path, RawPath},
 
145
               {headers, mochiweb_headers:to_list(Headers)}]}.
 
146
 
 
147
%% @spec send(iodata(), request()) -> ok
 
148
%% @doc Send data over the socket.
 
149
send(Data, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) ->
 
150
    case mochiweb_socket:send(Socket, Data) of
 
151
        ok ->
 
152
            ok;
 
153
        _ ->
 
154
            exit(normal)
 
155
    end.
 
156
 
 
157
%% @spec recv(integer(), request()) -> binary()
 
158
%% @doc Receive Length bytes from the client as a binary, with the default
 
159
%%      idle timeout.
 
160
recv(Length, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
161
    recv(Length, ?IDLE_TIMEOUT, THIS).
 
162
 
 
163
%% @spec recv(integer(), integer(), request()) -> binary()
 
164
%% @doc Receive Length bytes from the client as a binary, with the given
 
165
%%      Timeout in msec.
 
166
recv(Length, Timeout, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) ->
 
167
    case mochiweb_socket:recv(Socket, Length, Timeout) of
 
168
        {ok, Data} ->
 
169
            put(?SAVE_RECV, true),
 
170
            Data;
 
171
        _ ->
 
172
            exit(normal)
 
173
    end.
 
174
 
 
175
%% @spec body_length(request()) -> undefined | chunked | unknown_transfer_encoding | integer()
 
176
%% @doc  Infer body length from transfer-encoding and content-length headers.
 
177
body_length({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
178
    case get_header_value("transfer-encoding", THIS) of
 
179
        undefined ->
 
180
            case get_combined_header_value("content-length", THIS) of
 
181
                undefined ->
 
182
                    undefined;
 
183
                Length ->
 
184
                    list_to_integer(Length)
 
185
            end;
 
186
        "chunked" ->
 
187
            chunked;
 
188
        Unknown ->
 
189
            {unknown_transfer_encoding, Unknown}
 
190
    end.
 
191
 
 
192
 
 
193
%% @spec recv_body(request()) -> binary()
 
194
%% @doc Receive the body of the HTTP request (defined by Content-Length).
 
195
%%      Will only receive up to the default max-body length of 1MB.
 
196
recv_body({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
197
    recv_body(?MAX_RECV_BODY, THIS).
 
198
 
 
199
%% @spec recv_body(integer(), request()) -> binary()
 
200
%% @doc Receive the body of the HTTP request (defined by Content-Length).
 
201
%%      Will receive up to MaxBody bytes.
 
202
recv_body(MaxBody, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
203
    case erlang:get(?SAVE_BODY) of
 
204
        undefined ->
 
205
            % we could use a sane constant for max chunk size
 
206
            Body = stream_body(?MAX_RECV_BODY, fun
 
207
                ({0, _ChunkedFooter}, {_LengthAcc, BinAcc}) ->
 
208
                    iolist_to_binary(lists:reverse(BinAcc));
 
209
                ({Length, Bin}, {LengthAcc, BinAcc}) ->
 
210
                    NewLength = Length + LengthAcc,
 
211
                    if NewLength > MaxBody ->
 
212
                        exit({body_too_large, chunked});
 
213
                    true ->
 
214
                        {NewLength, [Bin | BinAcc]}
 
215
                    end
 
216
                end, {0, []}, MaxBody, THIS),
 
217
            put(?SAVE_BODY, Body),
 
218
            Body;
 
219
        Cached -> Cached
 
220
    end.
 
221
 
 
222
stream_body(MaxChunkSize, ChunkFun, FunState, {?MODULE,[_Socket,_Method,_RawPath,_Version,_Headers]}=THIS) ->
 
223
    stream_body(MaxChunkSize, ChunkFun, FunState, undefined, THIS).
 
224
 
 
225
stream_body(MaxChunkSize, ChunkFun, FunState, MaxBodyLength,
 
226
            {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
227
    Expect = case get_header_value("expect", THIS) of
 
228
                 undefined ->
 
229
                     undefined;
 
230
                 Value when is_list(Value) ->
 
231
                     string:to_lower(Value)
 
232
             end,
 
233
    case Expect of
 
234
        "100-continue" ->
 
235
            _ = start_raw_response({100, gb_trees:empty()}, THIS),
 
236
            ok;
 
237
        _Else ->
 
238
            ok
 
239
    end,
 
240
    case body_length(THIS) of
 
241
        undefined ->
 
242
            undefined;
 
243
        {unknown_transfer_encoding, Unknown} ->
 
244
            exit({unknown_transfer_encoding, Unknown});
 
245
        chunked ->
 
246
            % In this case the MaxBody is actually used to
 
247
            % determine the maximum allowed size of a single
 
248
            % chunk.
 
249
            stream_chunked_body(MaxChunkSize, ChunkFun, FunState, THIS);
 
250
        0 ->
 
251
            <<>>;
 
252
        Length when is_integer(Length) ->
 
253
            case MaxBodyLength of
 
254
            MaxBodyLength when is_integer(MaxBodyLength), MaxBodyLength < Length ->
 
255
                exit({body_too_large, content_length});
 
256
            _ ->
 
257
                stream_unchunked_body(Length, ChunkFun, FunState, THIS)
 
258
            end
 
259
    end.
 
260
 
 
261
 
 
262
%% @spec start_response({integer(), ioheaders()}, request()) -> response()
 
263
%% @doc Start the HTTP response by sending the Code HTTP response and
 
264
%%      ResponseHeaders. The server will set header defaults such as Server
 
265
%%      and Date if not present in ResponseHeaders.
 
266
start_response({Code, ResponseHeaders}, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
267
    HResponse = mochiweb_headers:make(ResponseHeaders),
 
268
    HResponse1 = mochiweb_headers:default_from_list(server_headers(),
 
269
                                                    HResponse),
 
270
    start_raw_response({Code, HResponse1}, THIS).
 
271
 
 
272
%% @spec start_raw_response({integer(), headers()}, request()) -> response()
 
273
%% @doc Start the HTTP response by sending the Code HTTP response and
 
274
%%      ResponseHeaders.
 
275
start_raw_response({Code, ResponseHeaders}, {?MODULE, [_Socket, _Method, _RawPath, Version, _Headers]}=THIS) ->
 
276
    F = fun ({K, V}, Acc) ->
 
277
                [mochiweb_util:make_io(K), <<": ">>, V, <<"\r\n">> | Acc]
 
278
        end,
 
279
    End = lists:foldl(F, [<<"\r\n">>],
 
280
                      mochiweb_headers:to_list(ResponseHeaders)),
 
281
    send([make_version(Version), make_code(Code), <<"\r\n">> | End], THIS),
 
282
    mochiweb:new_response({THIS, Code, ResponseHeaders}).
 
283
 
 
284
 
 
285
%% @spec start_response_length({integer(), ioheaders(), integer()}, request()) -> response()
 
286
%% @doc Start the HTTP response by sending the Code HTTP response and
 
287
%%      ResponseHeaders including a Content-Length of Length. The server
 
288
%%      will set header defaults such as Server
 
289
%%      and Date if not present in ResponseHeaders.
 
290
start_response_length({Code, ResponseHeaders, Length},
 
291
                      {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
292
    HResponse = mochiweb_headers:make(ResponseHeaders),
 
293
    HResponse1 = mochiweb_headers:enter("Content-Length", Length, HResponse),
 
294
    start_response({Code, HResponse1}, THIS).
 
295
 
 
296
%% @spec respond({integer(), ioheaders(), iodata() | chunked | {file, IoDevice}}, request()) -> response()
 
297
%% @doc Start the HTTP response with start_response, and send Body to the
 
298
%%      client (if the get(method) /= 'HEAD'). The Content-Length header
 
299
%%      will be set by the Body length, and the server will insert header
 
300
%%      defaults.
 
301
respond({Code, ResponseHeaders, {file, IoDevice}},
 
302
        {?MODULE, [_Socket, Method, _RawPath, _Version, _Headers]}=THIS) ->
 
303
    Length = mochiweb_io:iodevice_size(IoDevice),
 
304
    Response = start_response_length({Code, ResponseHeaders, Length}, THIS),
 
305
    case Method of
 
306
        'HEAD' ->
 
307
            ok;
 
308
        _ ->
 
309
            mochiweb_io:iodevice_stream(
 
310
              fun (Body) -> send(Body, THIS) end,
 
311
              IoDevice)
 
312
    end,
 
313
    Response;
 
314
respond({Code, ResponseHeaders, chunked}, {?MODULE, [_Socket, Method, _RawPath, Version, _Headers]}=THIS) ->
 
315
    HResponse = mochiweb_headers:make(ResponseHeaders),
 
316
    HResponse1 = case Method of
 
317
                     'HEAD' ->
 
318
                         %% This is what Google does, http://www.google.com/
 
319
                         %% is chunked but HEAD gets Content-Length: 0.
 
320
                         %% The RFC is ambiguous so emulating Google is smart.
 
321
                         mochiweb_headers:enter("Content-Length", "0",
 
322
                                                HResponse);
 
323
                     _ when Version >= {1, 1} ->
 
324
                         %% Only use chunked encoding for HTTP/1.1
 
325
                         mochiweb_headers:enter("Transfer-Encoding", "chunked",
 
326
                                                HResponse);
 
327
                     _ ->
 
328
                         %% For pre-1.1 clients we send the data as-is
 
329
                         %% without a Content-Length header and without
 
330
                         %% chunk delimiters. Since the end of the document
 
331
                         %% is now ambiguous we must force a close.
 
332
                         put(?SAVE_FORCE_CLOSE, true),
 
333
                         HResponse
 
334
                 end,
 
335
    start_response({Code, HResponse1}, THIS);
 
336
respond({Code, ResponseHeaders, Body}, {?MODULE, [_Socket, Method, _RawPath, _Version, _Headers]}=THIS) ->
 
337
    Response = start_response_length({Code, ResponseHeaders, iolist_size(Body)}, THIS),
 
338
    case Method of
 
339
        'HEAD' ->
 
340
            ok;
 
341
        _ ->
 
342
            send(Body, THIS)
 
343
    end,
 
344
    Response.
 
345
 
 
346
%% @spec not_found(request()) -> response()
 
347
%% @doc Alias for <code>not_found([])</code>.
 
348
not_found({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
349
    not_found([], THIS).
 
350
 
 
351
%% @spec not_found(ExtraHeaders, request()) -> response()
 
352
%% @doc Alias for <code>respond({404, [{"Content-Type", "text/plain"}
 
353
%% | ExtraHeaders], &lt;&lt;"Not found."&gt;&gt;})</code>.
 
354
not_found(ExtraHeaders, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
355
    respond({404, [{"Content-Type", "text/plain"} | ExtraHeaders],
 
356
             <<"Not found.">>}, THIS).
 
357
 
 
358
%% @spec ok({value(), iodata()} | {value(), ioheaders(), iodata() | {file, IoDevice}}, request()) ->
 
359
%%           response()
 
360
%% @doc respond({200, [{"Content-Type", ContentType} | Headers], Body}).
 
361
ok({ContentType, Body}, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
362
    ok({ContentType, [], Body}, THIS);
 
363
ok({ContentType, ResponseHeaders, Body}, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
364
    HResponse = mochiweb_headers:make(ResponseHeaders),
 
365
    case THIS:get(range) of
 
366
        X when (X =:= undefined orelse X =:= fail) orelse Body =:= chunked ->
 
367
            %% http://code.google.com/p/mochiweb/issues/detail?id=54
 
368
            %% Range header not supported when chunked, return 200 and provide
 
369
            %% full response.
 
370
            HResponse1 = mochiweb_headers:enter("Content-Type", ContentType,
 
371
                                                HResponse),
 
372
            respond({200, HResponse1, Body}, THIS);
 
373
        Ranges ->
 
374
            {PartList, Size} = range_parts(Body, Ranges),
 
375
            case PartList of
 
376
                [] -> %% no valid ranges
 
377
                    HResponse1 = mochiweb_headers:enter("Content-Type",
 
378
                                                        ContentType,
 
379
                                                        HResponse),
 
380
                    %% could be 416, for now we'll just return 200
 
381
                    respond({200, HResponse1, Body}, THIS);
 
382
                PartList ->
 
383
                    {RangeHeaders, RangeBody} =
 
384
                        mochiweb_multipart:parts_to_body(PartList, ContentType, Size),
 
385
                    HResponse1 = mochiweb_headers:enter_from_list(
 
386
                                   [{"Accept-Ranges", "bytes"} |
 
387
                                    RangeHeaders],
 
388
                                   HResponse),
 
389
                    respond({206, HResponse1, RangeBody}, THIS)
 
390
            end
 
391
    end.
 
392
 
 
393
%% @spec should_close(request()) -> bool()
 
394
%% @doc Return true if the connection must be closed. If false, using
 
395
%%      Keep-Alive should be safe.
 
396
should_close({?MODULE, [_Socket, _Method, _RawPath, Version, _Headers]}=THIS) ->
 
397
    ForceClose = erlang:get(?SAVE_FORCE_CLOSE) =/= undefined,
 
398
    DidNotRecv = erlang:get(?SAVE_RECV) =:= undefined,
 
399
    ForceClose orelse Version < {1, 0}
 
400
        %% Connection: close
 
401
        orelse is_close(get_header_value("connection", THIS))
 
402
        %% HTTP 1.0 requires Connection: Keep-Alive
 
403
        orelse (Version =:= {1, 0}
 
404
                andalso get_header_value("connection", THIS) =/= "Keep-Alive")
 
405
        %% unread data left on the socket, can't safely continue
 
406
        orelse (DidNotRecv
 
407
                andalso get_combined_header_value("content-length", THIS) =/= undefined
 
408
                andalso list_to_integer(get_combined_header_value("content-length", THIS)) > 0)
 
409
        orelse (DidNotRecv
 
410
                andalso get_header_value("transfer-encoding", THIS) =:= "chunked").
 
411
 
 
412
is_close("close") ->
 
413
    true;
 
414
is_close(S=[_C, _L, _O, _S, _E]) ->
 
415
    string:to_lower(S) =:= "close";
 
416
is_close(_) ->
 
417
    false.
 
418
 
 
419
%% @spec cleanup(request()) -> ok
 
420
%% @doc Clean up any junk in the process dictionary, required before continuing
 
421
%%      a Keep-Alive request.
 
422
cleanup({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}) ->
 
423
    L = [?SAVE_QS, ?SAVE_PATH, ?SAVE_RECV, ?SAVE_BODY, ?SAVE_BODY_LENGTH,
 
424
         ?SAVE_POST, ?SAVE_COOKIE, ?SAVE_FORCE_CLOSE],
 
425
    lists:foreach(fun(K) ->
 
426
                          erase(K)
 
427
                  end, L),
 
428
    ok.
 
429
 
 
430
%% @spec parse_qs(request()) -> [{Key::string(), Value::string()}]
 
431
%% @doc Parse the query string of the URL.
 
432
parse_qs({?MODULE, [_Socket, _Method, RawPath, _Version, _Headers]}) ->
 
433
    case erlang:get(?SAVE_QS) of
 
434
        undefined ->
 
435
            {_, QueryString, _} = mochiweb_util:urlsplit_path(RawPath),
 
436
            Parsed = mochiweb_util:parse_qs(QueryString),
 
437
            put(?SAVE_QS, Parsed),
 
438
            Parsed;
 
439
        Cached ->
 
440
            Cached
 
441
    end.
 
442
 
 
443
%% @spec get_cookie_value(Key::string, request()) -> string() | undefined
 
444
%% @doc Get the value of the given cookie.
 
445
get_cookie_value(Key, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
446
    proplists:get_value(Key, parse_cookie(THIS)).
 
447
 
 
448
%% @spec parse_cookie(request()) -> [{Key::string(), Value::string()}]
 
449
%% @doc Parse the cookie header.
 
450
parse_cookie({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
451
    case erlang:get(?SAVE_COOKIE) of
 
452
        undefined ->
 
453
            Cookies = case get_header_value("cookie", THIS) of
 
454
                          undefined ->
 
455
                              [];
 
456
                          Value ->
 
457
                              mochiweb_cookies:parse_cookie(Value)
 
458
                      end,
 
459
            put(?SAVE_COOKIE, Cookies),
 
460
            Cookies;
 
461
        Cached ->
 
462
            Cached
 
463
    end.
 
464
 
 
465
%% @spec parse_post(request()) -> [{Key::string(), Value::string()}]
 
466
%% @doc Parse an application/x-www-form-urlencoded form POST. This
 
467
%%      has the side-effect of calling recv_body().
 
468
parse_post({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
469
    case erlang:get(?SAVE_POST) of
 
470
        undefined ->
 
471
            Parsed = case recv_body(THIS) of
 
472
                         undefined ->
 
473
                             [];
 
474
                         Binary ->
 
475
                             case get_primary_header_value("content-type",THIS) of
 
476
                                 "application/x-www-form-urlencoded" ++ _ ->
 
477
                                     mochiweb_util:parse_qs(Binary);
 
478
                                 _ ->
 
479
                                     []
 
480
                             end
 
481
                     end,
 
482
            put(?SAVE_POST, Parsed),
 
483
            Parsed;
 
484
        Cached ->
 
485
            Cached
 
486
    end.
 
487
 
 
488
%% @spec stream_chunked_body(integer(), fun(), term(), request()) -> term()
 
489
%% @doc The function is called for each chunk.
 
490
%%      Used internally by read_chunked_body.
 
491
stream_chunked_body(MaxChunkSize, Fun, FunState,
 
492
                    {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
493
    case read_chunk_length(THIS) of
 
494
        0 ->
 
495
            Fun({0, read_chunk(0, THIS)}, FunState);
 
496
        Length when Length > MaxChunkSize ->
 
497
            NewState = read_sub_chunks(Length, MaxChunkSize, Fun, FunState, THIS),
 
498
            stream_chunked_body(MaxChunkSize, Fun, NewState, THIS);
 
499
        Length ->
 
500
            NewState = Fun({Length, read_chunk(Length, THIS)}, FunState),
 
501
            stream_chunked_body(MaxChunkSize, Fun, NewState, THIS)
 
502
    end.
 
503
 
 
504
stream_unchunked_body(0, Fun, FunState, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}) ->
 
505
    Fun({0, <<>>}, FunState);
 
506
stream_unchunked_body(Length, Fun, FunState,
 
507
                      {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) when Length > 0 ->
 
508
    PktSize = case Length > ?RECBUF_SIZE of
 
509
        true ->
 
510
            ?RECBUF_SIZE;
 
511
        false ->
 
512
            Length
 
513
    end,
 
514
    Bin = recv(PktSize, THIS),
 
515
    NewState = Fun({PktSize, Bin}, FunState),
 
516
    stream_unchunked_body(Length - PktSize, Fun, NewState, THIS).
 
517
 
 
518
%% @spec read_chunk_length(request()) -> integer()
 
519
%% @doc Read the length of the next HTTP chunk.
 
520
read_chunk_length({?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) ->
 
521
    ok = mochiweb_socket:setopts(Socket, [{packet, line}]),
 
522
    case mochiweb_socket:recv(Socket, 0, ?IDLE_TIMEOUT) of
 
523
        {ok, Header} ->
 
524
            ok = mochiweb_socket:setopts(Socket, [{packet, raw}]),
 
525
            Splitter = fun (C) ->
 
526
                               C =/= $\r andalso C =/= $\n andalso C =/= $
 
527
                       end,
 
528
            {Hex, _Rest} = lists:splitwith(Splitter, binary_to_list(Header)),
 
529
            mochihex:to_int(Hex);
 
530
        _ ->
 
531
            exit(normal)
 
532
    end.
 
533
 
 
534
%% @spec read_chunk(integer(), request()) -> Chunk::binary() | [Footer::binary()]
 
535
%% @doc Read in a HTTP chunk of the given length. If Length is 0, then read the
 
536
%%      HTTP footers (as a list of binaries, since they're nominal).
 
537
read_chunk(0, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) ->
 
538
    ok = mochiweb_socket:setopts(Socket, [{packet, line}]),
 
539
    F = fun (F1, Acc) ->
 
540
                case mochiweb_socket:recv(Socket, 0, ?IDLE_TIMEOUT) of
 
541
                    {ok, <<"\r\n">>} ->
 
542
                        Acc;
 
543
                    {ok, Footer} ->
 
544
                        F1(F1, [Footer | Acc]);
 
545
                    _ ->
 
546
                        exit(normal)
 
547
                end
 
548
        end,
 
549
    Footers = F(F, []),
 
550
    ok = mochiweb_socket:setopts(Socket, [{packet, raw}]),
 
551
    put(?SAVE_RECV, true),
 
552
    Footers;
 
553
read_chunk(Length, {?MODULE, [Socket, _Method, _RawPath, _Version, _Headers]}) ->
 
554
    case mochiweb_socket:recv(Socket, 2 + Length, ?IDLE_TIMEOUT) of
 
555
        {ok, <<Chunk:Length/binary, "\r\n">>} ->
 
556
            Chunk;
 
557
        _ ->
 
558
            exit(normal)
 
559
    end.
 
560
 
 
561
read_sub_chunks(Length, MaxChunkSize, Fun, FunState,
 
562
                {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) when Length > MaxChunkSize ->
 
563
    Bin = recv(MaxChunkSize, THIS),
 
564
    NewState = Fun({size(Bin), Bin}, FunState),
 
565
    read_sub_chunks(Length - MaxChunkSize, MaxChunkSize, Fun, NewState, THIS);
 
566
 
 
567
read_sub_chunks(Length, _MaxChunkSize, Fun, FunState,
 
568
                {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
569
    Fun({Length, read_chunk(Length, THIS)}, FunState).
 
570
 
 
571
%% @spec serve_file(Path, DocRoot, request()) -> Response
 
572
%% @doc Serve a file relative to DocRoot.
 
573
serve_file(Path, DocRoot, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
574
    serve_file(Path, DocRoot, [], THIS).
 
575
 
 
576
%% @spec serve_file(Path, DocRoot, ExtraHeaders, request()) -> Response
 
577
%% @doc Serve a file relative to DocRoot.
 
578
serve_file(Path, DocRoot, ExtraHeaders, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
579
    case mochiweb_util:safe_relative_path(Path) of
 
580
        undefined ->
 
581
            not_found(ExtraHeaders, THIS);
 
582
        RelPath ->
 
583
            FullPath = filename:join([DocRoot, RelPath]),
 
584
            case filelib:is_dir(FullPath) of
 
585
                true ->
 
586
                    maybe_redirect(RelPath, FullPath, ExtraHeaders, THIS);
 
587
                false ->
 
588
                    maybe_serve_file(FullPath, ExtraHeaders, THIS)
 
589
            end
 
590
    end.
 
591
 
 
592
%% Internal API
 
593
 
 
594
%% This has the same effect as the DirectoryIndex directive in httpd
 
595
directory_index(FullPath) ->
 
596
    filename:join([FullPath, "index.html"]).
 
597
 
 
598
maybe_redirect([], FullPath, ExtraHeaders, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
599
    maybe_serve_file(directory_index(FullPath), ExtraHeaders, THIS);
 
600
 
 
601
maybe_redirect(RelPath, FullPath, ExtraHeaders,
 
602
               {?MODULE, [_Socket, _Method, _RawPath, _Version, Headers]}=THIS) ->
 
603
    case string:right(RelPath, 1) of
 
604
        "/" ->
 
605
            maybe_serve_file(directory_index(FullPath), ExtraHeaders, THIS);
 
606
        _   ->
 
607
            Host = mochiweb_headers:get_value("host", Headers),
 
608
            Location = "http://" ++ Host  ++ "/" ++ RelPath ++ "/",
 
609
            LocationBin = list_to_binary(Location),
 
610
            MoreHeaders = [{"Location", Location},
 
611
                           {"Content-Type", "text/html"} | ExtraHeaders],
 
612
            Top = <<"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">"
 
613
            "<html><head>"
 
614
            "<title>301 Moved Permanently</title>"
 
615
            "</head><body>"
 
616
            "<h1>Moved Permanently</h1>"
 
617
            "<p>The document has moved <a href=\"">>,
 
618
            Bottom = <<">here</a>.</p></body></html>\n">>,
 
619
            Body = <<Top/binary, LocationBin/binary, Bottom/binary>>,
 
620
            respond({301, MoreHeaders, Body}, THIS)
 
621
    end.
 
622
 
 
623
maybe_serve_file(File, ExtraHeaders, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
624
    case file:read_file_info(File) of
 
625
        {ok, FileInfo} ->
 
626
            LastModified = httpd_util:rfc1123_date(FileInfo#file_info.mtime),
 
627
            case get_header_value("if-modified-since", THIS) of
 
628
                LastModified ->
 
629
                    respond({304, ExtraHeaders, ""}, THIS);
 
630
                _ ->
 
631
                    case file:open(File, [raw, binary]) of
 
632
                        {ok, IoDevice} ->
 
633
                            ContentType = mochiweb_util:guess_mime(File),
 
634
                            Res = ok({ContentType,
 
635
                                      [{"last-modified", LastModified}
 
636
                                       | ExtraHeaders],
 
637
                                      {file, IoDevice}}, THIS),
 
638
                            ok = file:close(IoDevice),
 
639
                            Res;
 
640
                        _ ->
 
641
                            not_found(ExtraHeaders, THIS)
 
642
                    end
 
643
            end;
 
644
        {error, _} ->
 
645
            not_found(ExtraHeaders, THIS)
 
646
    end.
 
647
 
 
648
server_headers() ->
 
649
    [{"Server", "MochiWeb/1.0 (" ++ ?QUIP ++ ")"},
 
650
     {"Date", httpd_util:rfc1123_date()}].
 
651
 
 
652
make_code(X) when is_integer(X) ->
 
653
    [integer_to_list(X), [" " | httpd_util:reason_phrase(X)]];
 
654
make_code(Io) when is_list(Io); is_binary(Io) ->
 
655
    Io.
 
656
 
 
657
make_version({1, 0}) ->
 
658
    <<"HTTP/1.0 ">>;
 
659
make_version(_) ->
 
660
    <<"HTTP/1.1 ">>.
 
661
 
 
662
range_parts({file, IoDevice}, Ranges) ->
 
663
    Size = mochiweb_io:iodevice_size(IoDevice),
 
664
    F = fun (Spec, Acc) ->
 
665
                case mochiweb_http:range_skip_length(Spec, Size) of
 
666
                    invalid_range ->
 
667
                        Acc;
 
668
                    V ->
 
669
                        [V | Acc]
 
670
                end
 
671
        end,
 
672
    LocNums = lists:foldr(F, [], Ranges),
 
673
    {ok, Data} = file:pread(IoDevice, LocNums),
 
674
    Bodies = lists:zipwith(fun ({Skip, Length}, PartialBody) ->
 
675
                                   {Skip, Skip + Length - 1, PartialBody}
 
676
                           end,
 
677
                           LocNums, Data),
 
678
    {Bodies, Size};
 
679
range_parts(Body0, Ranges) ->
 
680
    Body = iolist_to_binary(Body0),
 
681
    Size = size(Body),
 
682
    F = fun(Spec, Acc) ->
 
683
                case mochiweb_http:range_skip_length(Spec, Size) of
 
684
                    invalid_range ->
 
685
                        Acc;
 
686
                    {Skip, Length} ->
 
687
                        <<_:Skip/binary, PartialBody:Length/binary, _/binary>> = Body,
 
688
                        [{Skip, Skip + Length - 1, PartialBody} | Acc]
 
689
                end
 
690
        end,
 
691
    {lists:foldr(F, [], Ranges), Size}.
 
692
 
 
693
%% @spec accepted_encodings([encoding()], request()) -> [encoding()] | bad_accept_encoding_value
 
694
%% @type encoding() = string().
 
695
%%
 
696
%% @doc Returns a list of encodings accepted by a request. Encodings that are
 
697
%%      not supported by the server will not be included in the return list.
 
698
%%      This list is computed from the "Accept-Encoding" header and
 
699
%%      its elements are ordered, descendingly, according to their Q values.
 
700
%%
 
701
%%      Section 14.3 of the RFC 2616 (HTTP 1.1) describes the "Accept-Encoding"
 
702
%%      header and the process of determining which server supported encodings
 
703
%%      can be used for encoding the body for the request's response.
 
704
%%
 
705
%%      Examples
 
706
%%
 
707
%%      1) For a missing "Accept-Encoding" header:
 
708
%%         accepted_encodings(["gzip", "identity"]) -> ["identity"]
 
709
%%
 
710
%%      2) For an "Accept-Encoding" header with value "gzip, deflate":
 
711
%%         accepted_encodings(["gzip", "identity"]) -> ["gzip", "identity"]
 
712
%%
 
713
%%      3) For an "Accept-Encoding" header with value "gzip;q=0.5, deflate":
 
714
%%         accepted_encodings(["gzip", "deflate", "identity"]) ->
 
715
%%            ["deflate", "gzip", "identity"]
 
716
%%
 
717
accepted_encodings(SupportedEncodings, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
718
    AcceptEncodingHeader = case get_header_value("Accept-Encoding", THIS) of
 
719
        undefined ->
 
720
            "";
 
721
        Value ->
 
722
            Value
 
723
    end,
 
724
    case mochiweb_util:parse_qvalues(AcceptEncodingHeader) of
 
725
        invalid_qvalue_string ->
 
726
            bad_accept_encoding_value;
 
727
        QList ->
 
728
            mochiweb_util:pick_accepted_encodings(
 
729
                QList, SupportedEncodings, "identity"
 
730
            )
 
731
    end.
 
732
 
 
733
%% @spec accepts_content_type(string() | binary(), request()) -> boolean() | bad_accept_header
 
734
%%
 
735
%% @doc Determines whether a request accepts a given media type by analyzing its
 
736
%%      "Accept" header.
 
737
%%
 
738
%%      Examples
 
739
%%
 
740
%%      1) For a missing "Accept" header:
 
741
%%         accepts_content_type("application/json") -> true
 
742
%%
 
743
%%      2) For an "Accept" header with value "text/plain, application/*":
 
744
%%         accepts_content_type("application/json") -> true
 
745
%%
 
746
%%      3) For an "Accept" header with value "text/plain, */*; q=0.0":
 
747
%%         accepts_content_type("application/json") -> false
 
748
%%
 
749
%%      4) For an "Accept" header with value "text/plain; q=0.5, */*; q=0.1":
 
750
%%         accepts_content_type("application/json") -> true
 
751
%%
 
752
%%      5) For an "Accept" header with value "text/*; q=0.0, */*":
 
753
%%         accepts_content_type("text/plain") -> false
 
754
%%
 
755
accepts_content_type(ContentType1, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
756
    ContentType = re:replace(ContentType1, "\\s", "", [global, {return, list}]),
 
757
    AcceptHeader = accept_header(THIS),
 
758
    case mochiweb_util:parse_qvalues(AcceptHeader) of
 
759
        invalid_qvalue_string ->
 
760
            bad_accept_header;
 
761
        QList ->
 
762
            [MainType, _SubType] = string:tokens(ContentType, "/"),
 
763
            SuperType = MainType ++ "/*",
 
764
            lists:any(
 
765
                fun({"*/*", Q}) when Q > 0.0 ->
 
766
                        true;
 
767
                    ({Type, Q}) when Q > 0.0 ->
 
768
                        Type =:= ContentType orelse Type =:= SuperType;
 
769
                    (_) ->
 
770
                        false
 
771
                end,
 
772
                QList
 
773
            ) andalso
 
774
            (not lists:member({ContentType, 0.0}, QList)) andalso
 
775
            (not lists:member({SuperType, 0.0}, QList))
 
776
    end.
 
777
 
 
778
%% @spec accepted_content_types([string() | binary()], request()) -> [string()] | bad_accept_header
 
779
%%
 
780
%% @doc Filters which of the given media types this request accepts. This filtering
 
781
%%      is performed by analyzing the "Accept" header. The returned list is sorted
 
782
%%      according to the preferences specified in the "Accept" header (higher Q values
 
783
%%      first). If two or more types have the same preference (Q value), they're order
 
784
%%      in the returned list is the same as they're order in the input list.
 
785
%%
 
786
%%      Examples
 
787
%%
 
788
%%      1) For a missing "Accept" header:
 
789
%%         accepted_content_types(["text/html", "application/json"]) ->
 
790
%%             ["text/html", "application/json"]
 
791
%%
 
792
%%      2) For an "Accept" header with value "text/html, application/*":
 
793
%%         accepted_content_types(["application/json", "text/html"]) ->
 
794
%%             ["application/json", "text/html"]
 
795
%%
 
796
%%      3) For an "Accept" header with value "text/html, */*; q=0.0":
 
797
%%         accepted_content_types(["text/html", "application/json"]) ->
 
798
%%             ["text/html"]
 
799
%%
 
800
%%      4) For an "Accept" header with value "text/html; q=0.5, */*; q=0.1":
 
801
%%         accepts_content_types(["application/json", "text/html"]) ->
 
802
%%             ["text/html", "application/json"]
 
803
%%
 
804
accepted_content_types(Types1, {?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
805
    Types = lists:map(
 
806
        fun(T) -> re:replace(T, "\\s", "", [global, {return, list}]) end,
 
807
        Types1),
 
808
    AcceptHeader = accept_header(THIS),
 
809
    case mochiweb_util:parse_qvalues(AcceptHeader) of
 
810
        invalid_qvalue_string ->
 
811
            bad_accept_header;
 
812
        QList ->
 
813
            TypesQ = lists:foldr(
 
814
                fun(T, Acc) ->
 
815
                    case proplists:get_value(T, QList) of
 
816
                        undefined ->
 
817
                            [MainType, _SubType] = string:tokens(T, "/"),
 
818
                            case proplists:get_value(MainType ++ "/*", QList) of
 
819
                                undefined ->
 
820
                                    case proplists:get_value("*/*", QList) of
 
821
                                        Q when is_float(Q), Q > 0.0 ->
 
822
                                            [{Q, T} | Acc];
 
823
                                        _ ->
 
824
                                            Acc
 
825
                                    end;
 
826
                                Q when Q > 0.0 ->
 
827
                                    [{Q, T} | Acc];
 
828
                                _ ->
 
829
                                    Acc
 
830
                            end;
 
831
                        Q when Q > 0.0 ->
 
832
                            [{Q, T} | Acc];
 
833
                        _ ->
 
834
                            Acc
 
835
                    end
 
836
                end,
 
837
                [], Types),
 
838
            % Note: Stable sort. If 2 types have the same Q value we leave them in the
 
839
            % same order as in the input list.
 
840
            SortFun = fun({Q1, _}, {Q2, _}) -> Q1 >= Q2 end,
 
841
            [Type || {_Q, Type} <- lists:sort(SortFun, TypesQ)]
 
842
    end.
 
843
 
 
844
accept_header({?MODULE, [_Socket, _Method, _RawPath, _Version, _Headers]}=THIS) ->
 
845
    case get_header_value("Accept", THIS) of
 
846
        undefined ->
 
847
            "*/*";
 
848
        Value ->
 
849
            Value
 
850
    end.
 
851
 
 
852
%%
 
853
%% Tests
 
854
%%
 
855
-ifdef(TEST).
 
856
-include_lib("eunit/include/eunit.hrl").
 
857
-endif.