PageRenderTime 298ms CodeModel.GetById 63ms app.highlight 95ms RepoModel.GetById 129ms app.codeStats 1ms

/src/mochiweb_request.erl

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