PageRenderTime 309ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/src/mochiweb_request.erl

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