%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2007 Mochi Media, Inc.
%%
%% Permission is hereby granted, free of charge, to any person obtaining a
%% copy of this software and associated documentation files (the "Software"),
%% to deal in the Software without restriction, including without limitation
%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
%% and/or sell copies of the Software, and to permit persons to whom the
%% Software is furnished to do so, subject to the following conditions:
%%
%% The above copyright notice and this permission notice shall be included in
%% all copies or substantial portions of the Software.
%%
%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
%% DEALINGS IN THE SOFTWARE.

%% @doc MochiWeb HTTP Request abstraction.

-module(mochiweb_request).

-author('bob@mochimedia.com').

-include_lib("kernel/include/file.hrl").

-include("internal.hrl").

-define(QUIP, "Any of you quaids got a smint?").

-export([new/5, new/6]).

-export([dump/1, get/2, get_combined_header_value/2,
	 get_header_value/2, get_primary_header_value/2]).

-export([recv/2, recv/3, recv_body/1, recv_body/2,
	 send/2, stream_body/4, stream_body/5]).

-export([start_raw_response/2, start_response/2,
	 start_response_length/2]).

-export([ok/2, respond/2]).

-export([not_found/1, not_found/2]).

-export([parse_post/1, parse_qs/1]).

-export([cleanup/1, should_close/1]).

-export([get_cookie_value/2, parse_cookie/1]).

-export([serve_file/3, serve_file/4]).

-export([accepted_encodings/2]).

-export([accepted_content_types/2,
	 accepts_content_type/2]).

-export([is_closed/1]).

-define(SAVE_QS, mochiweb_request_qs).

-define(SAVE_PATH, mochiweb_request_path).

-define(SAVE_RECV, mochiweb_request_recv).

-define(SAVE_BODY, mochiweb_request_body).

-define(SAVE_BODY_LENGTH, mochiweb_request_body_length).

-define(SAVE_POST, mochiweb_request_post).

-define(SAVE_COOKIE, mochiweb_request_cookie).

-define(SAVE_FORCE_CLOSE, mochiweb_request_force_close).

%% @type key() = atom() | string() | binary()
%% @type value() = atom() | string() | binary() | integer()
%% @type headers(). A mochiweb_headers structure.
%% @type request(). A mochiweb_request parameterized module instance.
%% @type response(). A mochiweb_response parameterized module instance.
%% @type ioheaders() = headers() | [{key(), value()}].

% 5 minute default idle timeout
-define(IDLE_TIMEOUT, 300000).

% Maximum recv_body() length of 1MB
-define(MAX_RECV_BODY, 1024 * 1024).

%% @spec new(Socket, Method, RawPath, Version, headers()) -> request()
%% @doc Create a new request instance.
new(Socket, Method, RawPath, Version, Headers) ->
    new(Socket, [], Method, RawPath, Version, Headers).

%% @spec new(Socket, Opts, Method, RawPath, Version, headers()) -> request()
%% @doc Create a new request instance.
new(Socket, Opts, Method, RawPath, Version, Headers) ->
    {?MODULE,
     [Socket, Opts, Method, RawPath, Version, Headers]}.

%% @spec get_header_value(K, request()) -> undefined | Value
%% @doc Get the value of a given request header.
get_header_value(K,
		 {?MODULE,
		  [_Socket, _Opts, _Method, _RawPath, _Version,
		   Headers]}) ->
    mochiweb_headers:get_value(K, Headers).

get_primary_header_value(K,
			 {?MODULE,
			  [_Socket, _Opts, _Method, _RawPath, _Version,
			   Headers]}) ->
    mochiweb_headers:get_primary_value(K, Headers).

get_combined_header_value(K,
			  {?MODULE,
			   [_Socket, _Opts, _Method, _RawPath, _Version,
			    Headers]}) ->
    mochiweb_headers:get_combined_value(K, Headers).

%% @type field() = socket | scheme | method | raw_path | version | headers | peer | path | body_length | range

%% @spec get(field(), request()) -> term()
%% @doc Return the internal representation of the given field. If
%%      <code>socket</code> is requested on a HTTPS connection, then
%%      an ssl socket will be returned as <code>{ssl, SslSocket}</code>.
%%      You can use <code>SslSocket</code> with the <code>ssl</code>
%%      application, eg: <code>ssl:peercert(SslSocket)</code>.
get(socket,
    {?MODULE,
     [Socket, _Opts, _Method, _RawPath, _Version,
      _Headers]}) ->
    Socket;
get(scheme,
    {?MODULE,
     [Socket, _Opts, _Method, _RawPath, _Version,
      _Headers]}) ->
    case mochiweb_socket:type(Socket) of
      plain -> http;
      ssl -> https
    end;
get(method,
    {?MODULE,
     [_Socket, _Opts, Method, _RawPath, _Version,
      _Headers]}) ->
    Method;
get(raw_path,
    {?MODULE,
     [_Socket, _Opts, _Method, RawPath, _Version,
      _Headers]}) ->
    RawPath;
get(version,
    {?MODULE,
     [_Socket, _Opts, _Method, _RawPath, Version,
      _Headers]}) ->
    Version;
get(headers,
    {?MODULE,
     [_Socket, _Opts, _Method, _RawPath, _Version,
      Headers]}) ->
    Headers;
get(peer,
    {?MODULE,
     [Socket, _Opts, _Method, _RawPath, _Version,
      _Headers]} =
	THIS) ->
    case mochiweb_socket:peername(Socket) of
      {ok, {Addr = {10, _, _, _}, _Port}} ->
	  case get_header_value("x-forwarded-for", THIS) of
	    undefined -> inet_parse:ntoa(Addr);
	    Hosts ->
		string:strip(lists:last(string:tokens(Hosts, ",")))
	  end;
      %% Copied this syntax from webmachine contributor Steve Vinoski
      {ok, {Addr = {172, Second, _, _}, _Port}}
	  when Second > 15 andalso Second < 32 ->
	  case get_header_value("x-forwarded-for", THIS) of
	    undefined -> inet_parse:ntoa(Addr);
	    Hosts ->
		string:strip(lists:last(string:tokens(Hosts, ",")))
	  end;
      %% According to RFC 6598, contributor Gerald Xv
      {ok, {Addr = {100, Second, _, _}, _Port}}
	  when Second > 63 andalso Second < 128 ->
	  case get_header_value("x-forwarded-for", THIS) of
	    undefined -> inet_parse:ntoa(Addr);
	    Hosts ->
		string:strip(lists:last(string:tokens(Hosts, ",")))
	  end;
      {ok, {Addr = {192, 168, _, _}, _Port}} ->
	  case get_header_value("x-forwarded-for", THIS) of
	    undefined -> inet_parse:ntoa(Addr);
	    Hosts ->
		string:strip(lists:last(string:tokens(Hosts, ",")))
	  end;
      {ok, {{127, 0, 0, 1}, _Port}} ->
	  case get_header_value("x-forwarded-for", THIS) of
	    undefined -> "127.0.0.1";
	    Hosts ->
		string:strip(lists:last(string:tokens(Hosts, ",")))
	  end;
      {ok, {Addr, _Port}} -> inet_parse:ntoa(Addr);
      {error, enotconn = Error} -> exit({shutdown, Error})
    end;
get(path,
    {?MODULE,
     [_Socket, _Opts, _Method, RawPath, _Version,
      _Headers]}) ->
    case erlang:get(?SAVE_PATH) of
      undefined ->
	  {Path0, _, _} = mochiweb_util:urlsplit_path(RawPath),
	  Path =
	      mochiweb_util:normalize_path(mochiweb_util:unquote(Path0)),
	  put(?SAVE_PATH, Path),
	  Path;
      Cached -> Cached
    end;
get(body_length,
    {?MODULE,
     [_Socket, _Opts, _Method, _RawPath, _Version,
      _Headers]} =
	THIS) ->
    case erlang:get(?SAVE_BODY_LENGTH) of
      undefined ->
	  BodyLength = body_length(THIS),
	  put(?SAVE_BODY_LENGTH, {cached, BodyLength}),
	  BodyLength;
      {cached, Cached} -> Cached
    end;
get(range,
    {?MODULE,
     [_Socket, _Opts, _Method, _RawPath, _Version,
      _Headers]} =
	THIS) ->
    case get_header_value(range, THIS) of
      undefined -> undefined;
      RawRange -> mochiweb_http:parse_range_request(RawRange)
    end;
get(opts,
    {?MODULE,
     [_Socket, Opts, _Method, _RawPath, _Version,
      _Headers]}) ->
    Opts.

%% @spec dump(request()) -> {mochiweb_request, [{atom(), term()}]}
%% @doc Dump the internal representation to a "human readable" set of terms
%%      for debugging/inspection purposes.
dump({?MODULE,
      [_Socket, Opts, Method, RawPath, Version, Headers]}) ->
    {?MODULE,
     [{method, Method}, {version, Version},
      {raw_path, RawPath}, {opts, Opts},
      {headers, mochiweb_headers:to_list(Headers)}]}.

%% @spec send(iodata(), request()) -> ok
%% @doc Send data over the socket.
send(Data,
     {?MODULE,
      [Socket, _Opts, _Method, _RawPath, _Version,
       _Headers]}) ->
    case mochiweb_socket:send(Socket, Data) of
      ok -> ok;
      _ -> exit({shutdown, send_error})
    end.

%% @spec recv(integer(), request()) -> binary()
%% @doc Receive Length bytes from the client as a binary, with the default
%%      idle timeout.
recv(Length,
     {?MODULE,
      [_Socket, _Opts, _Method, _RawPath, _Version,
       _Headers]} =
	 THIS) ->
    recv(Length, ?IDLE_TIMEOUT, THIS).

%% @spec recv(integer(), integer(), request()) -> binary()
%% @doc Receive Length bytes from the client as a binary, with the given
%%      Timeout in msec.
recv(Length, Timeout,
     {?MODULE,
      [Socket, _Opts, _Method, _RawPath, _Version,
       _Headers]}) ->
    case mochiweb_socket:recv(Socket, Length, Timeout) of
      {ok, Data} -> put(?SAVE_RECV, true), Data;
      _ -> exit({shutdown, recv_error})
    end.

%% @spec body_length(request()) -> undefined | chunked | unknown_transfer_encoding | integer()
%% @doc  Infer body length from transfer-encoding and content-length headers.
body_length({?MODULE,
	     [_Socket, _Opts, _Method, _RawPath, _Version,
	      _Headers]} =
		THIS) ->
    case get_header_value("transfer-encoding", THIS) of
      undefined ->
	  case get_combined_header_value("content-length", THIS)
	      of
	    undefined -> undefined;
	    Length -> list_to_integer(Length)
	  end;
      "chunked" -> chunked;
      Unknown -> {unknown_transfer_encoding, Unknown}
    end.

%% @spec recv_body(request()) -> binary()
%% @doc Receive the body of the HTTP request (defined by Content-Length).
%%      Will only receive up to the default max-body length of 1MB.
recv_body({?MODULE,
	   [_Socket, _Opts, _Method, _RawPath, _Version,
	    _Headers]} =
	      THIS) ->
    recv_body(?MAX_RECV_BODY, THIS).

%% @spec recv_body(integer(), request()) -> binary()
%% @doc Receive the body of the HTTP request (defined by Content-Length).
%%      Will receive up to MaxBody bytes.
recv_body(MaxBody,
	  {?MODULE,
	   [_Socket, _Opts, _Method, _RawPath, _Version,
	    _Headers]} =
	      THIS) ->
    case erlang:get(?SAVE_BODY) of
      undefined ->
	  % we could use a sane constant for max chunk size
	  Body = stream_body(?MAX_RECV_BODY,
			     fun ({0, _ChunkedFooter}, {_LengthAcc, BinAcc}) ->
				     iolist_to_binary(lists:reverse(BinAcc));
				 ({Length, Bin}, {LengthAcc, BinAcc}) ->
				     NewLength = Length + LengthAcc,
				     if NewLength > MaxBody ->
					    exit({body_too_large, chunked});
					true -> {NewLength, [Bin | BinAcc]}
				     end
			     end,
			     {0, []}, MaxBody, THIS),
	  put(?SAVE_BODY, Body),
	  Body;
      Cached -> Cached
    end.

stream_body(MaxChunkSize, ChunkFun, FunState,
	    {?MODULE,
	     [_Socket, _Opts, _Method, _RawPath, _Version,
	      _Headers]} =
		THIS) ->
    stream_body(MaxChunkSize, ChunkFun, FunState, undefined,
		THIS).

stream_body(MaxChunkSize, ChunkFun, FunState,
	    MaxBodyLength,
	    {?MODULE,
	     [_Socket, _Opts, _Method, _RawPath, _Version,
	      _Headers]} =
		THIS) ->
    Expect = case get_header_value("expect", THIS) of
	       undefined -> undefined;
	       Value when is_list(Value) -> string:to_lower(Value)
	     end,
    case Expect of
      "100-continue" ->
	  _ = start_raw_response({100, gb_trees:empty()}, THIS),
	  ok;
      _Else -> ok
    end,
    case body_length(THIS) of
      undefined -> undefined;
      {unknown_transfer_encoding, Unknown} ->
	  exit({unknown_transfer_encoding, Unknown});
      chunked ->
	  % In this case the MaxBody is actually used to
	  % determine the maximum allowed size of a single
	  % chunk.
	  stream_chunked_body(MaxChunkSize, ChunkFun, FunState,
			      THIS);
      0 -> <<>>;
      Length when is_integer(Length) ->
	  case MaxBodyLength of
	    MaxBodyLength
		when is_integer(MaxBodyLength),
		     MaxBodyLength < Length ->
		exit({body_too_large, content_length});
	    _ ->
		stream_unchunked_body(MaxChunkSize, Length, ChunkFun,
				      FunState, THIS)
	  end
    end.

%% @spec start_response({integer(), ioheaders()}, request()) -> response()
%% @doc Start the HTTP response by sending the Code HTTP response and
%%      ResponseHeaders. The server will set header defaults such as Server
%%      and Date if not present in ResponseHeaders.
start_response({Code, ResponseHeaders},
	       {?MODULE,
		[_Socket, _Opts, _Method, _RawPath, _Version,
		 _Headers]} =
		   THIS) ->
    start_raw_response({Code, ResponseHeaders}, THIS).

%% @spec start_raw_response({integer(), headers()}, request()) -> response()
%% @doc Start the HTTP response by sending the Code HTTP response and
%%      ResponseHeaders.
start_raw_response({Code, ResponseHeaders},
		   {?MODULE,
		    [_Socket, _Opts, _Method, _RawPath, _Version,
		     _Headers]} =
		       THIS) ->
    {Header, Response} = format_response_header({Code,
						 ResponseHeaders},
						THIS),
    send(Header, THIS),
    Response.

%% @spec start_response_length({integer(), ioheaders(), integer()}, request()) -> response()
%% @doc Start the HTTP response by sending the Code HTTP response and
%%      ResponseHeaders including a Content-Length of Length. The server
%%      will set header defaults such as Server
%%      and Date if not present in ResponseHeaders.
start_response_length({Code, ResponseHeaders, Length},
		      {?MODULE,
		       [_Socket, _Opts, _Method, _RawPath, _Version,
			_Headers]} =
			  THIS) ->
    HResponse = mochiweb_headers:make(ResponseHeaders),
    HResponse1 = mochiweb_headers:enter("Content-Length",
					Length, HResponse),
    start_response({Code, HResponse1}, THIS).

%% @spec format_response_header({integer(), ioheaders()} | {integer(), ioheaders(), integer()}, request()) -> iolist()
%% @doc Format the HTTP response header, including the Code HTTP response and
%%      ResponseHeaders including an optional Content-Length of Length. The server
%%      will set header defaults such as Server
%%      and Date if not present in ResponseHeaders.
format_response_header({Code, ResponseHeaders},
		       {?MODULE,
			[_Socket, _Opts, _Method, _RawPath, Version,
			 _Headers]} =
			   THIS) ->
    HResponse = mochiweb_headers:make(ResponseHeaders),
    HResponse1 =
	mochiweb_headers:default_from_list(server_headers(),
					   HResponse),
    HResponse2 = case should_close(THIS) of
		   true ->
		       mochiweb_headers:enter("Connection", "close",
					      HResponse1);
		   false -> HResponse1
		 end,
    End = [[mochiweb_util:make_io(K), <<": ">>, V,
	    <<"\r\n">>]
	   || {K, V} <- mochiweb_headers:to_list(HResponse2)],
    Response = mochiweb:new_response({THIS, Code,
				      HResponse2}),
    {[make_version(Version), make_code(Code), <<"\r\n">>,
      End, <<"\r\n">>],
     Response};
format_response_header({Code, ResponseHeaders, Length},
		       {?MODULE,
			[_Socket, _Opts, _Method, _RawPath, _Version,
			 _Headers]} =
			   THIS) ->
    HResponse = mochiweb_headers:make(ResponseHeaders),
    HResponse1 = mochiweb_headers:enter("Content-Length",
					Length, HResponse),
    format_response_header({Code, HResponse1}, THIS).

%% @spec respond({integer(), ioheaders(), iodata() | chunked | {file, IoDevice}}, request()) -> response()
%% @doc Start the HTTP response with start_response, and send Body to the
%%      client (if the get(method) /= 'HEAD'). The Content-Length header
%%      will be set by the Body length, and the server will insert header
%%      defaults.
respond({Code, ResponseHeaders, {file, IoDevice}},
	{?MODULE,
	 [_Socket, _Opts, Method, _RawPath, _Version,
	  _Headers]} =
	    THIS) ->
    Length = mochiweb_io:iodevice_size(IoDevice),
    Response = start_response_length({Code, ResponseHeaders,
				      Length},
				     THIS),
    case Method of
      'HEAD' -> ok;
      _ ->
	  mochiweb_io:iodevice_stream(fun (Body) ->
					      send(Body, THIS)
				      end,
				      IoDevice)
    end,
    Response;
respond({Code, ResponseHeaders, chunked},
	{?MODULE,
	 [_Socket, _Opts, Method, _RawPath, Version, _Headers]} =
	    THIS) ->
    HResponse = mochiweb_headers:make(ResponseHeaders),
    HResponse1 = case Method of
		   'HEAD' ->
		       %% This is what Google does, http://www.google.com/
		       %% is chunked but HEAD gets Content-Length: 0.
		       %% The RFC is ambiguous so emulating Google is smart.
		       mochiweb_headers:enter("Content-Length", "0",
					      HResponse);
		   _ when Version >= {1, 1} ->
		       %% Only use chunked encoding for HTTP/1.1
		       mochiweb_headers:enter("Transfer-Encoding", "chunked",
					      HResponse);
		   _ ->
		       %% For pre-1.1 clients we send the data as-is
		       %% without a Content-Length header and without
		       %% chunk delimiters. Since the end of the document
		       %% is now ambiguous we must force a close.
		       put(?SAVE_FORCE_CLOSE, true),
		       HResponse
		 end,
    start_response({Code, HResponse1}, THIS);
respond({Code, ResponseHeaders, Body},
	{?MODULE,
	 [_Socket, _Opts, Method, _RawPath, _Version,
	  _Headers]} =
	    THIS) ->
    {Header, Response} = format_response_header({Code,
						 ResponseHeaders,
						 iolist_size(Body)},
						THIS),
    case Method of
      'HEAD' -> send(Header, THIS);
      _ -> send([Header, Body], THIS)
    end,
    Response.

%% @spec not_found(request()) -> response()
%% @doc Alias for <code>not_found([])</code>.
not_found({?MODULE,
	   [_Socket, _Opts, _Method, _RawPath, _Version,
	    _Headers]} =
	      THIS) ->
    not_found([], THIS).

%% @spec not_found(ExtraHeaders, request()) -> response()
%% @doc Alias for <code>respond({404, [{"Content-Type", "text/plain"}
%% | ExtraHeaders], &lt;&lt;"Not found."&gt;&gt;})</code>.
not_found(ExtraHeaders,
	  {?MODULE,
	   [_Socket, _Opts, _Method, _RawPath, _Version,
	    _Headers]} =
	      THIS) ->
    respond({404,
	     [{"Content-Type", "text/plain"} | ExtraHeaders],
	     <<"Not found.">>},
	    THIS).

%% @spec ok({value(), iodata()} | {value(), ioheaders(), iodata() | {file, IoDevice}}, request()) ->
%%           response()
%% @doc respond({200, [{"Content-Type", ContentType} | Headers], Body}).
ok({ContentType, Body},
   {?MODULE,
    [_Socket, _Opts, _Method, _RawPath, _Version,
     _Headers]} =
       THIS) ->
    ok({ContentType, [], Body}, THIS);
ok({ContentType, ResponseHeaders, Body},
   {?MODULE,
    [_Socket, _Opts, _Method, _RawPath, _Version,
     _Headers]} =
       THIS) ->
    HResponse = mochiweb_headers:make(ResponseHeaders),
    case get(range, THIS) of
      X
	  when (X =:= undefined orelse X =:= fail) orelse
		 Body =:= chunked ->
	  %% http://code.google.com/p/mochiweb/issues/detail?id=54
	  %% Range header not supported when chunked, return 200 and provide
	  %% full response.
	  HResponse1 = mochiweb_headers:enter("Content-Type",
					      ContentType, HResponse),
	  respond({200, HResponse1, Body}, THIS);
      Ranges ->
	  {PartList, Size} = range_parts(Body, Ranges),
	  case PartList of
	    [] -> %% no valid ranges
		HResponse1 = mochiweb_headers:enter("Content-Type",
						    ContentType, HResponse),
		%% could be 416, for now we'll just return 200
		respond({200, HResponse1, Body}, THIS);
	    PartList ->
		{RangeHeaders, RangeBody} =
		    mochiweb_multipart:parts_to_body(PartList, ContentType,
						     Size),
		HResponse1 =
		    mochiweb_headers:enter_from_list([{"Accept-Ranges",
						       "bytes"}
						      | RangeHeaders],
						     HResponse),
		respond({206, HResponse1, RangeBody}, THIS)
	  end
    end.

%% @spec should_close(request()) -> bool()
%% @doc Return true if the connection must be closed. If false, using
%%      Keep-Alive should be safe.
should_close({?MODULE,
	      [_Socket, _Opts, _Method, _RawPath, Version,
	       _Headers]} =
		 THIS) ->
    ForceClose = erlang:get(?SAVE_FORCE_CLOSE) =/=
		   undefined,
    DidNotRecv = erlang:get(?SAVE_RECV) =:= undefined,
    ForceClose orelse
      Version < {1, 0}
	%% Connection: close
	orelse
	is_close(get_header_value("connection", THIS))
	  %% HTTP 1.0 requires Connection: Keep-Alive
	  orelse
	  Version =:= {1, 0} andalso
	    get_header_value("connection", THIS) =/= "Keep-Alive"
	    %% unread data left on the socket, can't safely continue
	    orelse
	    DidNotRecv andalso
	      get_combined_header_value("content-length", THIS) =/=
		undefined
		andalso
		list_to_integer(get_combined_header_value("content-length",
							  THIS))
		  > 0
	      orelse
	      DidNotRecv andalso
		get_header_value("transfer-encoding", THIS) =:=
		  "chunked".

is_close("close") -> true;
is_close(S = [_C, _L, _O, _S, _E]) ->
    string:to_lower(S) =:= "close";
is_close(_) -> false.

%% @spec cleanup(request()) -> ok
%% @doc Clean up any junk in the process dictionary, required before continuing
%%      a Keep-Alive request.
cleanup({?MODULE,
	 [_Socket, _Opts, _Method, _RawPath, _Version,
	  _Headers]}) ->
    L = [?SAVE_QS, ?SAVE_PATH, ?SAVE_RECV, ?SAVE_BODY,
	 ?SAVE_BODY_LENGTH, ?SAVE_POST, ?SAVE_COOKIE,
	 ?SAVE_FORCE_CLOSE],
    lists:foreach(fun (K) -> erase(K) end, L),
    ok.

%% @spec parse_qs(request()) -> [{Key::string(), Value::string()}]
%% @doc Parse the query string of the URL.
parse_qs({?MODULE,
	  [_Socket, _Opts, _Method, RawPath, _Version,
	   _Headers]}) ->
    case erlang:get(?SAVE_QS) of
      undefined ->
	  {_, QueryString, _} =
	      mochiweb_util:urlsplit_path(RawPath),
	  Parsed = mochiweb_util:parse_qs(QueryString),
	  put(?SAVE_QS, Parsed),
	  Parsed;
      Cached -> Cached
    end.

%% @spec get_cookie_value(Key::string, request()) -> string() | undefined
%% @doc Get the value of the given cookie.
get_cookie_value(Key,
		 {?MODULE,
		  [_Socket, _Opts, _Method, _RawPath, _Version,
		   _Headers]} =
		     THIS) ->
    proplists:get_value(Key, parse_cookie(THIS)).

%% @spec parse_cookie(request()) -> [{Key::string(), Value::string()}]
%% @doc Parse the cookie header.
parse_cookie({?MODULE,
	      [_Socket, _Opts, _Method, _RawPath, _Version,
	       _Headers]} =
		 THIS) ->
    case erlang:get(?SAVE_COOKIE) of
      undefined ->
	  Cookies = case get_header_value("cookie", THIS) of
		      undefined -> [];
		      Value -> mochiweb_cookies:parse_cookie(Value)
		    end,
	  put(?SAVE_COOKIE, Cookies),
	  Cookies;
      Cached -> Cached
    end.

%% @spec parse_post(request()) -> [{Key::string(), Value::string()}]
%% @doc Parse an application/x-www-form-urlencoded form POST. This
%%      has the side-effect of calling recv_body().
parse_post({?MODULE,
	    [_Socket, _Opts, _Method, _RawPath, _Version,
	     _Headers]} =
	       THIS) ->
    case erlang:get(?SAVE_POST) of
      undefined ->
	  Parsed = case recv_body(THIS) of
		     undefined -> [];
		     Binary ->
			 case get_primary_header_value("content-type", THIS) of
			   "application/x-www-form-urlencoded" ++ _ ->
			       mochiweb_util:parse_qs(Binary);
			   _ -> []
			 end
		   end,
	  put(?SAVE_POST, Parsed),
	  Parsed;
      Cached -> Cached
    end.

%% @spec stream_chunked_body(integer(), fun(), term(), request()) -> term()
%% @doc The function is called for each chunk.
%%      Used internally by stream_body.
stream_chunked_body(MaxChunkSize, Fun, FunState,
		    {?MODULE,
		     [_Socket, _Opts, _Method, _RawPath, _Version,
		      _Headers]} =
			THIS) ->
    case read_chunk_length(THIS) of
      0 -> Fun({0, read_chunk(0, THIS)}, FunState);
      Length when Length > MaxChunkSize ->
	  NewState = read_sub_chunks(Length, MaxChunkSize, Fun,
				     FunState, THIS),
	  stream_chunked_body(MaxChunkSize, Fun, NewState, THIS);
      Length ->
	  NewState = Fun({Length, read_chunk(Length, THIS)},
			 FunState),
	  stream_chunked_body(MaxChunkSize, Fun, NewState, THIS)
    end.

stream_unchunked_body(_MaxChunkSize, 0, Fun, FunState,
		      {?MODULE,
		       [_Socket, _Opts, _Method, _RawPath, _Version,
			_Headers]}) ->
    Fun({0, <<>>}, FunState);
stream_unchunked_body(MaxChunkSize, Length, Fun,
		      FunState,
		      {?MODULE,
		       [_Socket, Opts, _Method, _RawPath, _Version,
			_Headers]} =
			  THIS)
    when Length > 0 ->
    RecBuf = case mochilists:get_value(recbuf, Opts,
				       ?RECBUF_SIZE)
		 of
	       undefined -> %os controlled buffer size
		   MaxChunkSize;
	       Val -> Val
	     end,
    PktSize = min(Length, RecBuf),
    Bin = recv(PktSize, THIS),
    NewState = Fun({PktSize, Bin}, FunState),
    stream_unchunked_body(MaxChunkSize, Length - PktSize,
			  Fun, NewState, THIS).

%% @spec read_chunk_length(request()) -> integer()
%% @doc Read the length of the next HTTP chunk.
read_chunk_length({?MODULE,
		   [Socket, _Opts, _Method, _RawPath, _Version,
		    _Headers]}) ->
    ok =
	mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket,
							       [{packet,
								 line}])),
    case mochiweb_socket:recv(Socket, 0, ?IDLE_TIMEOUT) of
      {ok, Header} ->
	  ok =
	      mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket,
								     [{packet,
								       raw}])),
	  Splitter = fun (C) ->
			     C =/= $\r andalso C =/= $\n andalso C =/= $\s
		     end,
	  {Hex, _Rest} = lists:splitwith(Splitter,
					 binary_to_list(Header)),
	  mochihex:to_int(Hex);
      _ -> exit({shutdown, read_chunk_length_recv_error})
    end.

%% @spec read_chunk(integer(), request()) -> Chunk::binary() | [Footer::binary()]
%% @doc Read in a HTTP chunk of the given length. If Length is 0, then read the
%%      HTTP footers (as a list of binaries, since they're nominal).
read_chunk(0,
	   {?MODULE,
	    [Socket, _Opts, _Method, _RawPath, _Version,
	     _Headers]}) ->
    ok =
	mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket,
							       [{packet,
								 line}])),
    F = fun (F1, Acc) ->
		case mochiweb_socket:recv(Socket, 0, ?IDLE_TIMEOUT) of
		  {ok, <<"\r\n">>} -> Acc;
		  {ok, Footer} -> F1(F1, [Footer | Acc]);
		  _ -> exit({shutdown, read_chunk_recv_error})
		end
	end,
    Footers = F(F, []),
    ok =
	mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket,
							       [{packet,
								 raw}])),
    put(?SAVE_RECV, true),
    Footers;
read_chunk(Length,
	   {?MODULE,
	    [Socket, _Opts, _Method, _RawPath, _Version,
	     _Headers]}) ->
    case mochiweb_socket:recv(Socket, 2 + Length,
			      ?IDLE_TIMEOUT)
	of
      {ok, <<Chunk:Length/binary, "\r\n">>} -> Chunk;
      _ -> exit({shutdown, read_chunk_recv_error})
    end.

read_sub_chunks(Length, MaxChunkSize, Fun, FunState,
		{?MODULE,
		 [_Socket, _Opts, _Method, _RawPath, _Version,
		  _Headers]} =
		    THIS)
    when Length > MaxChunkSize ->
    Bin = recv(MaxChunkSize, THIS),
    NewState = Fun({size(Bin), Bin}, FunState),
    read_sub_chunks(Length - MaxChunkSize, MaxChunkSize,
		    Fun, NewState, THIS);
read_sub_chunks(Length, _MaxChunkSize, Fun, FunState,
		{?MODULE,
		 [_Socket, _Opts, _Method, _RawPath, _Version,
		  _Headers]} =
		    THIS) ->
    Fun({Length, read_chunk(Length, THIS)}, FunState).

%% @spec serve_file(Path, DocRoot, request()) -> Response
%% @doc Serve a file relative to DocRoot.
serve_file(Path, DocRoot,
	   {?MODULE,
	    [_Socket, _Opts, _Method, _RawPath, _Version,
	     _Headers]} =
	       THIS) ->
    serve_file(Path, DocRoot, [], THIS).

%% @spec serve_file(Path, DocRoot, ExtraHeaders, request()) -> Response
%% @doc Serve a file relative to DocRoot.
serve_file(Path, DocRoot, ExtraHeaders,
	   {?MODULE,
	    [_Socket, _Opts, _Method, _RawPath, _Version,
	     _Headers]} =
	       THIS) ->
    case mochiweb_util:safe_relative_path(Path) of
      undefined -> not_found(ExtraHeaders, THIS);
      RelPath ->
	  FullPath = filename:join([DocRoot, RelPath]),
	  case filelib:is_dir(FullPath) of
	    true ->
		maybe_redirect(RelPath, FullPath, ExtraHeaders, THIS);
	    false -> maybe_serve_file(FullPath, ExtraHeaders, THIS)
	  end
    end.

%% Internal API

%% This has the same effect as the DirectoryIndex directive in httpd
directory_index(FullPath) ->
    filename:join([FullPath, "index.html"]).

maybe_redirect([], FullPath, ExtraHeaders,
	       {?MODULE,
		[_Socket, _Opts, _Method, _RawPath, _Version,
		 _Headers]} =
		   THIS) ->
    maybe_serve_file(directory_index(FullPath),
		     ExtraHeaders, THIS);
maybe_redirect(RelPath, FullPath, ExtraHeaders,
	       {?MODULE,
		[_Socket, _Opts, _Method, _RawPath, _Version,
		 Headers]} =
		   THIS) ->
    case string:right(RelPath, 1) of
      "/" ->
	  maybe_serve_file(directory_index(FullPath),
			   ExtraHeaders, THIS);
      _ ->
	  Host = mochiweb_headers:get_value("host", Headers),
	  Location = "http://" ++ Host ++ "/" ++ RelPath ++ "/",
	  LocationBin = list_to_binary(Location),
	  MoreHeaders = [{"Location", Location},
			 {"Content-Type", "text/html"}
			 | ExtraHeaders],
	  Top = <<"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD "
		  "HTML 2.0//EN\"><html><head><title>301 "
		  "Moved Permanently</title></head><body><h1>Mov"
		  "ed Permanently</h1><p>The document has "
		  "moved <a href=\"">>,
	  Bottom = <<">here</a>.</p></body></html>\n">>,
	  Body = <<Top/binary, LocationBin/binary,
		   Bottom/binary>>,
	  respond({301, MoreHeaders, Body}, THIS)
    end.

maybe_serve_file(File, ExtraHeaders,
		 {?MODULE,
		  [_Socket, _Opts, _Method, _RawPath, _Version,
		   _Headers]} =
		     THIS) ->
    case file:read_file_info(File) of
      {ok, FileInfo} ->
	  LastModified =
	      httpd_util:rfc1123_date(FileInfo#file_info.mtime),
	  case get_header_value("if-modified-since", THIS) of
	    LastModified -> respond({304, ExtraHeaders, ""}, THIS);
	    _ ->
		case file:open(File, [raw, binary]) of
		  {ok, IoDevice} ->
		      ContentType = mochiweb_util:guess_mime(File),
		      Res = ok({ContentType,
				[{"last-modified", LastModified}
				 | ExtraHeaders],
				{file, IoDevice}},
			       THIS),
		      ok = file:close(IoDevice),
		      Res;
		  _ -> not_found(ExtraHeaders, THIS)
		end
	  end;
      {error, _} -> not_found(ExtraHeaders, THIS)
    end.

server_headers() ->
    [{"Server", "MochiWeb/1.0 (" ++ (?QUIP) ++ ")"},
     {"Date", mochiweb_clock:rfc1123()}].

make_code(X) when is_integer(X) ->
    [integer_to_list(X),
     [" " | httpd_util:reason_phrase(X)]];
make_code(Io) when is_list(Io); is_binary(Io) -> Io.

make_version({1, 0}) -> <<"HTTP/1.0 ">>;
make_version(_) -> <<"HTTP/1.1 ">>.

range_parts({file, IoDevice}, Ranges) ->
    Size = mochiweb_io:iodevice_size(IoDevice),
    F = fun (Spec, Acc) ->
		case mochiweb_http:range_skip_length(Spec, Size) of
		  invalid_range -> Acc;
		  V -> [V | Acc]
		end
	end,
    LocNums = lists:foldr(F, [], Ranges),
    {ok, Data} = file:pread(IoDevice, LocNums),
    Bodies = lists:zipwith(fun ({Skip, Length},
				PartialBody) ->
				   case Length of
				     0 -> {Skip, Skip, <<>>};
				     _ -> {Skip, Skip + Length - 1, PartialBody}
				   end
			   end,
			   LocNums, Data),
    {Bodies, Size};
range_parts(Body0, Ranges) ->
    Body = iolist_to_binary(Body0),
    Size = size(Body),
    F = fun (Spec, Acc) ->
		case mochiweb_http:range_skip_length(Spec, Size) of
		  invalid_range -> Acc;
		  {Skip, Length} ->
		      <<_:Skip/binary, PartialBody:Length/binary, _/binary>> =
			  Body,
		      [{Skip, Skip + Length - 1, PartialBody} | Acc]
		end
	end,
    {lists:foldr(F, [], Ranges), Size}.

%% @spec accepted_encodings([encoding()], request()) -> [encoding()] | bad_accept_encoding_value
%% @type encoding() = string().
%%
%% @doc Returns a list of encodings accepted by a request. Encodings that are
%%      not supported by the server will not be included in the return list.
%%      This list is computed from the "Accept-Encoding" header and
%%      its elements are ordered, descendingly, according to their Q values.
%%
%%      Section 14.3 of the RFC 2616 (HTTP 1.1) describes the "Accept-Encoding"
%%      header and the process of determining which server supported encodings
%%      can be used for encoding the body for the request's response.
%%
%%      Examples
%%
%%      1) For a missing "Accept-Encoding" header:
%%         accepted_encodings(["gzip", "identity"]) -> ["identity"]
%%
%%      2) For an "Accept-Encoding" header with value "gzip, deflate":
%%         accepted_encodings(["gzip", "identity"]) -> ["gzip", "identity"]
%%
%%      3) For an "Accept-Encoding" header with value "gzip;q=0.5, deflate":
%%         accepted_encodings(["gzip", "deflate", "identity"]) ->
%%            ["deflate", "gzip", "identity"]
%%
accepted_encodings(SupportedEncodings,
		   {?MODULE,
		    [_Socket, _Opts, _Method, _RawPath, _Version,
		     _Headers]} =
		       THIS) ->
    AcceptEncodingHeader = case
			     get_header_value("Accept-Encoding", THIS)
			       of
			     undefined -> "";
			     Value -> Value
			   end,
    case mochiweb_util:parse_qvalues(AcceptEncodingHeader)
	of
      invalid_qvalue_string -> bad_accept_encoding_value;
      QList ->
	  mochiweb_util:pick_accepted_encodings(QList,
						SupportedEncodings, "identity")
    end.

%% @spec accepts_content_type(string() | binary(), request()) -> boolean() | bad_accept_header
%%
%% @doc Determines whether a request accepts a given media type by analyzing its
%%      "Accept" header.
%%
%%      Examples
%%
%%      1) For a missing "Accept" header:
%%         accepts_content_type("application/json") -> true
%%
%%      2) For an "Accept" header with value "text/plain, application/*":
%%         accepts_content_type("application/json") -> true
%%
%%      3) For an "Accept" header with value "text/plain, */*; q=0.0":
%%         accepts_content_type("application/json") -> false
%%
%%      4) For an "Accept" header with value "text/plain; q=0.5, */*; q=0.1":
%%         accepts_content_type("application/json") -> true
%%
%%      5) For an "Accept" header with value "text/*; q=0.0, */*":
%%         accepts_content_type("text/plain") -> false
%%
accepts_content_type(ContentType1,
		     {?MODULE,
		      [_Socket, _Opts, _Method, _RawPath, _Version,
		       _Headers]} =
			 THIS) ->
    ContentType = re:replace(ContentType1, "\\s", "",
			     [global, {return, list}]),
    AcceptHeader = accept_header(THIS),
    case mochiweb_util:parse_qvalues(AcceptHeader) of
      invalid_qvalue_string -> bad_accept_header;
      QList ->
	  [MainType, _SubType] = string:tokens(ContentType, "/"),
	  SuperType = MainType ++ "/*",
	  lists:any(fun ({"*/*", Q}) when Q > 0.0 -> true;
			({Type, Q}) when Q > 0.0 ->
			    Type =:= ContentType orelse Type =:= SuperType;
			(_) -> false
		    end,
		    QList)
	    andalso
	    not lists:member({ContentType, 0.0}, QList) andalso
	      not lists:member({SuperType, 0.0}, QList)
    end.

%% @spec accepted_content_types([string() | binary()], request()) -> [string()] | bad_accept_header
%%
%% @doc Filters which of the given media types this request accepts. This filtering
%%      is performed by analyzing the "Accept" header. The returned list is sorted
%%      according to the preferences specified in the "Accept" header (higher Q values
%%      first). If two or more types have the same preference (Q value), they're order
%%      in the returned list is the same as they're order in the input list.
%%
%%      Examples
%%
%%      1) For a missing "Accept" header:
%%         accepted_content_types(["text/html", "application/json"]) ->
%%             ["text/html", "application/json"]
%%
%%      2) For an "Accept" header with value "text/html, application/*":
%%         accepted_content_types(["application/json", "text/html"]) ->
%%             ["application/json", "text/html"]
%%
%%      3) For an "Accept" header with value "text/html, */*; q=0.0":
%%         accepted_content_types(["text/html", "application/json"]) ->
%%             ["text/html"]
%%
%%      4) For an "Accept" header with value "text/html; q=0.5, */*; q=0.1":
%%         accepts_content_types(["application/json", "text/html"]) ->
%%             ["text/html", "application/json"]
%%
accepted_content_types(Types1,
		       {?MODULE,
			[_Socket, _Opts, _Method, _RawPath, _Version,
			 _Headers]} =
			   THIS) ->
    Types = [accepted_content_types_1(V1) || V1 <- Types1],
    AcceptHeader = accept_header(THIS),
    case mochiweb_util:parse_qvalues(AcceptHeader) of
      invalid_qvalue_string -> bad_accept_header;
      QList ->
	  TypesQ = lists:foldr(fun (T, Acc) ->
				       case proplists:get_value(T, QList) of
					 undefined ->
					     [MainType, _SubType] =
						 string:tokens(T, "/"),
					     case proplists:get_value(MainType
									++ "/*",
								      QList)
						 of
					       undefined ->
						   case
						     proplists:get_value("*/*",
									 QList)
						       of
						     Q
							 when is_float(Q),
							      Q > 0.0 ->
							 [{Q, T} | Acc];
						     _ -> Acc
						   end;
					       Q when Q > 0.0 -> [{Q, T} | Acc];
					       _ -> Acc
					     end;
					 Q when Q > 0.0 -> [{Q, T} | Acc];
					 _ -> Acc
				       end
			       end,
			       [], Types),
	  % Note: Stable sort. If 2 types have the same Q value we leave them in the
	  % same order as in the input list.
	  SortFun = fun ({Q1, _}, {Q2, _}) -> Q1 >= Q2 end,
	  [Type || {_Q, Type} <- lists:sort(SortFun, TypesQ)]
    end.

accepted_content_types_1(T) ->
    re:replace(T, "\\s", "", [global, {return, list}]).

accept_header({?MODULE,
	       [_Socket, _Opts, _Method, _RawPath, _Version,
		_Headers]} =
		  THIS) ->
    case get_header_value("Accept", THIS) of
      undefined -> "*/*";
      Value -> Value
    end.

%% @spec is_closed(request()) -> true | false | undefined
%% @doc Check if a request connection is closing or already closed. This may be
%% useful when processing long running request callbacks, when the client
%% disconnects after a short timeout. This function works on Linux, NetBSD,
%% OpenBSD, FreeBSD and MacOS. On other operating systems, like Windows for
%% instance, it will return undefined.
is_closed({?MODULE,
        [Socket, _Opts, _Method, _RawPath, _Version, _Headers]}) ->
    mochiweb_socket:is_closed(Socket).
