You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1938 lines
78 KiB

14 years ago
13 years ago
13 years ago
13 years ago
14 years ago
14 years ago
13 years ago
14 years ago
14 years ago
14 years ago
13 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
13 years ago
13 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
Use Erlang's OTP base64 module (available since R12B02) and avoid duplicated base64 encoding/decoding code in ibrowse_lib.erl and ibrowse_http_client.erl. OTP's base64 module is also more efficient (C implementation): 1> Data = crypto:rand_bytes(4096). <<205,174,13,169,97,159,110,161,71,43,226,153,42,101,243, 83,11,96,23,161,253,251,129,240,163,216,58,175,190,...>> 2> 2> timer:tc(ibrowse_lib, encode_base64, [Data]). {2920, <<"za4NqWGfbqFHK+KZKmXzUwtgF6H9+4Hwo9g6r77h2EF1/Xk1oKOIOmnAkgtv41LPXg37fp2dlr45C8qCA9/8zrcc9F5zr2JT0eVPTrh5aahl"...>>} 3> timer:tc(ibrowse_lib, encode_base64, [Data]). {1221, <<"za4NqWGfbqFHK+KZKmXzUwtgF6H9+4Hwo9g6r77h2EF1/Xk1oKOIOmnAkgtv41LPXg37fp2dlr45C8qCA9/8zrcc9F5zr2JT0eVPTrh5aahl"...>>} 4> timer:tc(ibrowse_lib, encode_base64, [Data]). {1436, <<"za4NqWGfbqFHK+KZKmXzUwtgF6H9+4Hwo9g6r77h2EF1/Xk1oKOIOmnAkgtv41LPXg37fp2dlr45C8qCA9/8zrcc9F5zr2JT0eVPTrh5aahl"...>>} 5> timer:tc(ibrowse_lib, encode_base64, [Data]). {1195, <<"za4NqWGfbqFHK+KZKmXzUwtgF6H9+4Hwo9g6r77h2EF1/Xk1oKOIOmnAkgtv41LPXg37fp2dlr45C8qCA9/8zrcc9F5zr2JT0eVPTrh5aahl"...>>} 6> 6> timer:tc(base64, encode, [Data]). {1846, <<"za4NqWGfbqFHK+KZKmXzUwtgF6H9+4Hwo9g6r77h2EF1/Xk1oKOIOmnAkgtv41LPXg37fp2dlr45C8qCA9/8zrcc9F5zr2JT0eVPTrh5aahl"...>>} 7> timer:tc(base64, encode, [Data]). {743, <<"za4NqWGfbqFHK+KZKmXzUwtgF6H9+4Hwo9g6r77h2EF1/Xk1oKOIOmnAkgtv41LPXg37fp2dlr45C8qCA9/8zrcc9F5zr2JT0eVPTrh5aahl"...>>} 8> timer:tc(base64, encode, [Data]). {737, <<"za4NqWGfbqFHK+KZKmXzUwtgF6H9+4Hwo9g6r77h2EF1/Xk1oKOIOmnAkgtv41LPXg37fp2dlr45C8qCA9/8zrcc9F5zr2JT0eVPTrh5aahl"...>>} 9> timer:tc(base64, encode, [Data]). {656, <<"za4NqWGfbqFHK+KZKmXzUwtgF6H9+4Hwo9g6r77h2EF1/Xk1oKOIOmnAkgtv41LPXg37fp2dlr45C8qCA9/8zrcc9F5zr2JT0eVPTrh5aahl"...>>}
15 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
13 years ago
14 years ago
14 years ago
13 years ago
13 years ago
13 years ago
14 years ago
13 years ago
13 years ago
13 years ago
13 years ago
13 years ago
14 years ago
14 years ago
14 years ago
14 years ago
13 years ago
14 years ago
14 years ago
13 years ago
14 years ago
14 years ago
14 years ago
13 years ago
14 years ago
  1. %%%-------------------------------------------------------------------
  2. %%% File : ibrowse_http_client.erl
  3. %%% Author : Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk>
  4. %%% Description : The name says it all
  5. %%%
  6. %%% Created : 11 Oct 2003 by Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk>
  7. %%%-------------------------------------------------------------------
  8. -module(ibrowse_http_client).
  9. -behaviour(gen_server).
  10. %%--------------------------------------------------------------------
  11. %% Include files
  12. %%--------------------------------------------------------------------
  13. %%--------------------------------------------------------------------
  14. %% External exports
  15. -export([
  16. start_link/1,
  17. start_link/2,
  18. start/1,
  19. start/2,
  20. stop/1,
  21. send_req/7
  22. ]).
  23. -ifdef(debug).
  24. -compile(export_all).
  25. -endif.
  26. %% gen_server callbacks
  27. -export([
  28. init/1,
  29. handle_call/3,
  30. handle_cast/2,
  31. handle_info/2,
  32. terminate/2,
  33. code_change/3
  34. ]).
  35. -include("ibrowse.hrl").
  36. -include_lib("kernel/include/inet.hrl").
  37. -record(state, {host, port, connect_timeout,
  38. inactivity_timer_ref,
  39. use_proxy = false, proxy_auth_digest,
  40. ssl_options = [], is_ssl = false, socket,
  41. proxy_tunnel_setup = false,
  42. tunnel_setup_queue = [],
  43. reqs=queue:new(), cur_req, status=idle, http_status_code,
  44. reply_buffer = <<>>, rep_buf_size=0, streamed_size = 0,
  45. recvd_headers=[],
  46. status_line, raw_headers,
  47. is_closing, content_length,
  48. deleted_crlf = false, transfer_encoding,
  49. chunk_size, chunk_size_buffer = <<>>,
  50. recvd_chunk_size, interim_reply_sent = false,
  51. lb_ets_tid, cur_pipeline_size = 0, prev_req_id
  52. }).
  53. -record(request, {url, method, options, from,
  54. stream_to, caller_controls_socket = false,
  55. caller_socket_options = [],
  56. req_id,
  57. stream_chunk_size,
  58. save_response_to_file = false,
  59. tmp_file_name, tmp_file_fd, preserve_chunked_encoding,
  60. response_format, timer_ref}).
  61. -import(ibrowse_lib, [
  62. get_value/2,
  63. get_value/3,
  64. do_trace/2
  65. ]).
  66. -define(DEFAULT_STREAM_CHUNK_SIZE, 1024*1024).
  67. -define(dec2hex(X), erlang:integer_to_list(X, 16)).
  68. %%====================================================================
  69. %% External functions
  70. %%====================================================================
  71. %%--------------------------------------------------------------------
  72. %% Function: start_link/0
  73. %% Description: Starts the server
  74. %%--------------------------------------------------------------------
  75. start(Args) ->
  76. start(Args, []).
  77. start(Args, Options) ->
  78. gen_server:start(?MODULE, Args, Options).
  79. start_link(Args) ->
  80. start_link(Args, []).
  81. start_link(Args, Options) ->
  82. gen_server:start_link(?MODULE, Args, Options).
  83. stop(Conn_pid) ->
  84. case catch gen_server:call(Conn_pid, stop) of
  85. {'EXIT', {timeout, _}} ->
  86. exit(Conn_pid, kill),
  87. ok;
  88. _ ->
  89. ok
  90. end.
  91. send_req(Conn_Pid, Url, Headers, Method, Body, Options, Timeout) ->
  92. gen_server:call(
  93. Conn_Pid,
  94. {send_req, {Url, Headers, Method, Body, Options, Timeout}}, Timeout).
  95. %%====================================================================
  96. %% Server functions
  97. %%====================================================================
  98. %%--------------------------------------------------------------------
  99. %% Function: init/1
  100. %% Description: Initiates the server
  101. %% Returns: {ok, State} |
  102. %% {ok, State, Timeout} |
  103. %% ignore |
  104. %% {stop, Reason}
  105. %%--------------------------------------------------------------------
  106. init({Lb_Tid, #url{host = Host, port = Port}, {SSLOptions, Is_ssl}}) ->
  107. State = #state{host = Host,
  108. port = Port,
  109. ssl_options = SSLOptions,
  110. is_ssl = Is_ssl,
  111. lb_ets_tid = Lb_Tid},
  112. put(ibrowse_trace_token, [Host, $:, integer_to_list(Port)]),
  113. put(my_trace_flag, ibrowse_lib:get_trace_status(Host, Port)),
  114. {ok, set_inac_timer(State)};
  115. init(Url) when is_list(Url) ->
  116. case catch ibrowse_lib:parse_url(Url) of
  117. #url{protocol = Protocol} = Url_rec ->
  118. init({undefined, Url_rec, {[], Protocol == https}});
  119. {'EXIT', _} ->
  120. {error, invalid_url}
  121. end;
  122. init({Host, Port}) ->
  123. State = #state{host = Host,
  124. port = Port},
  125. put(ibrowse_trace_token, [Host, $:, integer_to_list(Port)]),
  126. put(my_trace_flag, ibrowse_lib:get_trace_status(Host, Port)),
  127. {ok, set_inac_timer(State)}.
  128. %%--------------------------------------------------------------------
  129. %% Function: handle_call/3
  130. %% Description: Handling call messages
  131. %% Returns: {reply, Reply, State} |
  132. %% {reply, Reply, State, Timeout} |
  133. %% {noreply, State} |
  134. %% {noreply, State, Timeout} |
  135. %% {stop, Reason, Reply, State} | (terminate/2 is called)
  136. %% {stop, Reason, State} (terminate/2 is called)
  137. %%--------------------------------------------------------------------
  138. %% Received a request when the remote server has already sent us a
  139. %% Connection: Close header
  140. handle_call({send_req, _}, _From, #state{is_closing = true} = State) ->
  141. {reply, {error, connection_closing}, State};
  142. handle_call({send_req, {Url, Headers, Method, Body, Options, Timeout}},
  143. From, State) ->
  144. send_req_1(From, Url, Headers, Method, Body, Options, Timeout, State);
  145. handle_call(stop, _From, State) ->
  146. do_close(State),
  147. do_error_reply(State, closing_on_request),
  148. {stop, normal, ok, State};
  149. handle_call(Request, _From, State) ->
  150. Reply = {unknown_request, Request},
  151. {reply, Reply, State}.
  152. %%--------------------------------------------------------------------
  153. %% Function: handle_cast/2
  154. %% Description: Handling cast messages
  155. %% Returns: {noreply, State} |
  156. %% {noreply, State, Timeout} |
  157. %% {stop, Reason, State} (terminate/2 is called)
  158. %%--------------------------------------------------------------------
  159. handle_cast(_Msg, State) ->
  160. {noreply, State}.
  161. %%--------------------------------------------------------------------
  162. %% Function: handle_info/2
  163. %% Description: Handling all non call/cast messages
  164. %% Returns: {noreply, State} |
  165. %% {noreply, State, Timeout} |
  166. %% {stop, Reason, State} (terminate/2 is called)
  167. %%--------------------------------------------------------------------
  168. handle_info({tcp, _Sock, Data}, #state{status = Status} = State) ->
  169. do_trace("Data recvd in state: ~p. Size: ~p. ~p~n~n", [Status, size(Data), Data]),
  170. handle_sock_data(Data, State);
  171. handle_info({ssl, _Sock, Data}, State) ->
  172. handle_sock_data(Data, State);
  173. handle_info({stream_next, Req_id}, #state{socket = Socket,
  174. cur_req = #request{req_id = Req_id}} = State) ->
  175. do_setopts(Socket, [{active, once}], State),
  176. {noreply, set_inac_timer(State)};
  177. handle_info({stream_next, _Req_id}, State) ->
  178. _Cur_req_id = case State#state.cur_req of
  179. #request{req_id = Cur} ->
  180. Cur;
  181. _ ->
  182. undefined
  183. end,
  184. {noreply, State};
  185. handle_info({stream_close, _Req_id}, State) ->
  186. shutting_down(State),
  187. do_close(State),
  188. do_error_reply(State, closing_on_request),
  189. {stop, normal, State};
  190. handle_info({tcp_closed, _Sock}, State) ->
  191. do_trace("TCP connection closed by peer!~n", []),
  192. handle_sock_closed(State),
  193. {stop, normal, State};
  194. handle_info({ssl_closed, _Sock}, State) ->
  195. do_trace("SSL connection closed by peer!~n", []),
  196. handle_sock_closed(State),
  197. {stop, normal, State};
  198. handle_info({tcp_error, _Sock, Reason}, State) ->
  199. do_trace("Error on connection to ~1000.p:~1000.p -> ~1000.p~n",
  200. [State#state.host, State#state.port, Reason]),
  201. handle_sock_closed(State),
  202. {stop, normal, State};
  203. handle_info({ssl_error, _Sock, Reason}, State) ->
  204. do_trace("Error on SSL connection to ~1000.p:~1000.p -> ~1000.p~n",
  205. [State#state.host, State#state.port, Reason]),
  206. handle_sock_closed(State),
  207. {stop, normal, State};
  208. handle_info({req_timedout, From}, State) ->
  209. case lists:keysearch(From, #request.from, queue:to_list(State#state.reqs)) of
  210. false ->
  211. {noreply, State};
  212. {value, #request{stream_to = StreamTo, req_id = ReqId}} ->
  213. catch StreamTo ! {ibrowse_async_response_timeout, ReqId},
  214. shutting_down(State),
  215. {stop, normal, State}
  216. end;
  217. handle_info(timeout, State) ->
  218. do_trace("Inactivity timeout triggered. Shutting down connection~n", []),
  219. shutting_down(State),
  220. do_error_reply(State, req_timedout),
  221. {stop, normal, State};
  222. handle_info({trace, Bool}, State) ->
  223. put(my_trace_flag, Bool),
  224. {noreply, State};
  225. handle_info(Info, State) ->
  226. io:format("Unknown message recvd for ~1000.p:~1000.p -> ~p~n",
  227. [State#state.host, State#state.port, Info]),
  228. io:format("Recvd unknown message ~p when in state: ~p~n", [Info, State]),
  229. {noreply, State}.
  230. %%--------------------------------------------------------------------
  231. %% Function: terminate/2
  232. %% Description: Shutdown the server
  233. %% Returns: any (ignored by gen_server)
  234. %%--------------------------------------------------------------------
  235. terminate(_Reason, State) ->
  236. do_close(State),
  237. ok.
  238. %%--------------------------------------------------------------------
  239. %% Func: code_change/3
  240. %% Purpose: Convert process state when code is changed
  241. %% Returns: {ok, NewState}
  242. %%--------------------------------------------------------------------
  243. code_change(_OldVsn, State, _Extra) ->
  244. {ok, State}.
  245. %%--------------------------------------------------------------------
  246. %%% Internal functions
  247. %%--------------------------------------------------------------------
  248. %%--------------------------------------------------------------------
  249. %% Handles data recvd on the socket
  250. %%--------------------------------------------------------------------
  251. handle_sock_data(Data, #state{status=idle}=State) ->
  252. do_trace("Data recvd on socket in state idle!. ~1000.p~n", [Data]),
  253. shutting_down(State),
  254. do_error_reply(State, data_in_status_idle),
  255. do_close(State),
  256. {stop, normal, State};
  257. handle_sock_data(Data, #state{status = get_header}=State) ->
  258. case parse_response(Data, State) of
  259. {error, _Reason} ->
  260. shutting_down(State),
  261. {stop, normal, State};
  262. #state{socket = Socket, status = Status, cur_req = CurReq} = State_1 ->
  263. case {Status, CurReq} of
  264. {get_header, #request{caller_controls_socket = true}} ->
  265. do_setopts(Socket, [{active, once}], State_1);
  266. _ ->
  267. active_once(State_1)
  268. end,
  269. {noreply, set_inac_timer(State_1)}
  270. end;
  271. handle_sock_data(Data, #state{status = get_body,
  272. socket = Socket,
  273. content_length = CL,
  274. http_status_code = StatCode,
  275. recvd_headers = Headers,
  276. chunk_size = CSz} = State) ->
  277. case (CL == undefined) and (CSz == undefined) of
  278. true ->
  279. case accumulate_response(Data, State) of
  280. {error, Reason} ->
  281. shutting_down(State),
  282. fail_pipelined_requests(State,
  283. {error, {Reason, {stat_code, StatCode}, Headers}}),
  284. {stop, normal, State};
  285. State_1 ->
  286. active_once(State_1),
  287. State_2 = set_inac_timer(State_1),
  288. {noreply, State_2}
  289. end;
  290. _ ->
  291. case parse_11_response(Data, State) of
  292. {error, Reason} ->
  293. shutting_down(State),
  294. fail_pipelined_requests(State,
  295. {error, {Reason, {stat_code, StatCode}, Headers}}),
  296. {stop, normal, State};
  297. #state{cur_req = #request{caller_controls_socket = Ccs},
  298. interim_reply_sent = Irs} = State_1 ->
  299. case Irs of
  300. true ->
  301. active_once(State_1);
  302. false when Ccs == true ->
  303. do_setopts(Socket, [{active, once}], State);
  304. false ->
  305. active_once(State_1)
  306. end,
  307. State_2 = State_1#state{interim_reply_sent = false},
  308. case Ccs of
  309. true ->
  310. cancel_timer(State_2#state.inactivity_timer_ref, {eat_message, timeout}),
  311. {noreply, State_2#state{inactivity_timer_ref = undefined}};
  312. _ ->
  313. {noreply, set_inac_timer(State_2)}
  314. end;
  315. State_1 ->
  316. active_once(State_1),
  317. State_2 = set_inac_timer(State_1),
  318. {noreply, State_2}
  319. end
  320. end.
  321. accumulate_response(Data,
  322. #state{
  323. cur_req = #request{save_response_to_file = Srtf,
  324. tmp_file_fd = undefined} = CurReq,
  325. http_status_code=[$2 | _]}=State) when Srtf /= false ->
  326. TmpFilename = make_tmp_filename(Srtf),
  327. Mode = file_mode(Srtf),
  328. case file:open(TmpFilename, [Mode, delayed_write, raw]) of
  329. {ok, Fd} ->
  330. accumulate_response(Data, State#state{
  331. cur_req = CurReq#request{
  332. tmp_file_fd = Fd,
  333. tmp_file_name = TmpFilename}});
  334. {error, Reason} ->
  335. {error, {file_open_error, Reason}}
  336. end;
  337. accumulate_response(Data, #state{cur_req = #request{save_response_to_file = Srtf,
  338. tmp_file_fd = Fd},
  339. transfer_encoding=chunked,
  340. reply_buffer = Reply_buf,
  341. http_status_code=[$2 | _]
  342. } = State) when Srtf /= false ->
  343. case file:write(Fd, [Reply_buf, Data]) of
  344. ok ->
  345. State#state{reply_buffer = <<>>};
  346. {error, Reason} ->
  347. {error, {file_write_error, Reason}}
  348. end;
  349. accumulate_response(Data, #state{cur_req = #request{save_response_to_file = Srtf,
  350. tmp_file_fd = Fd},
  351. reply_buffer = RepBuf,
  352. http_status_code=[$2 | _]
  353. } = State) when Srtf /= false ->
  354. case file:write(Fd, [RepBuf, Data]) of
  355. ok ->
  356. State#state{reply_buffer = <<>>};
  357. {error, Reason} ->
  358. {error, {file_write_error, Reason}}
  359. end;
  360. accumulate_response(Data, #state{reply_buffer = RepBuf,
  361. rep_buf_size = RepBufSize,
  362. streamed_size = Streamed_size,
  363. cur_req = CurReq}=State) ->
  364. #request{stream_to = StreamTo,
  365. req_id = ReqId,
  366. stream_chunk_size = Stream_chunk_size,
  367. response_format = Response_format,
  368. caller_controls_socket = Caller_controls_socket} = CurReq,
  369. RepBuf_1 = <<RepBuf/binary, Data/binary>>,
  370. New_data_size = RepBufSize - Streamed_size,
  371. case StreamTo of
  372. undefined ->
  373. State#state{reply_buffer = RepBuf_1};
  374. _ when Caller_controls_socket == true ->
  375. do_interim_reply(StreamTo, Response_format, ReqId, RepBuf_1),
  376. State#state{reply_buffer = <<>>,
  377. interim_reply_sent = true,
  378. streamed_size = Streamed_size + size(RepBuf_1)};
  379. _ when New_data_size >= Stream_chunk_size ->
  380. {Stream_chunk, Rem_data} = split_binary(RepBuf_1, Stream_chunk_size),
  381. do_interim_reply(StreamTo, Response_format, ReqId, Stream_chunk),
  382. State_1 = State#state{
  383. reply_buffer = <<>>,
  384. interim_reply_sent = true,
  385. streamed_size = Streamed_size + Stream_chunk_size},
  386. case Rem_data of
  387. <<>> ->
  388. State_1;
  389. _ ->
  390. accumulate_response(Rem_data, State_1)
  391. end;
  392. _ ->
  393. State#state{reply_buffer = RepBuf_1}
  394. end.
  395. make_tmp_filename(true) ->
  396. DownloadDir = ibrowse:get_config_value(download_dir, filename:absname("./")),
  397. {A,B,C} = now(),
  398. filename:join([DownloadDir,
  399. "ibrowse_tmp_file_"++
  400. integer_to_list(A) ++
  401. integer_to_list(B) ++
  402. integer_to_list(C)]);
  403. make_tmp_filename(File) when is_list(File) ->
  404. File;
  405. make_tmp_filename({append, File}) when is_list(File) ->
  406. File.
  407. file_mode({append, _File}) -> append;
  408. file_mode(_Srtf) -> write.
  409. %%--------------------------------------------------------------------
  410. %% Handles the case when the server closes the socket
  411. %%--------------------------------------------------------------------
  412. handle_sock_closed(#state{status=get_header} = State) ->
  413. shutting_down(State),
  414. do_error_reply(State, connection_closed);
  415. handle_sock_closed(#state{cur_req=undefined} = State) ->
  416. shutting_down(State);
  417. %% We check for IsClosing because this the server could have sent a
  418. %% Connection-Close header and has closed the socket to indicate end
  419. %% of response. There maybe requests pipelined which need a response.
  420. handle_sock_closed(#state{reply_buffer = Buf, reqs = Reqs, http_status_code = SC,
  421. is_closing = IsClosing,
  422. cur_req = #request{tmp_file_name=TmpFilename,
  423. tmp_file_fd=Fd} = CurReq,
  424. status = get_body,
  425. recvd_headers = Headers,
  426. status_line = Status_line,
  427. raw_headers = Raw_headers
  428. }=State) ->
  429. #request{from=From, stream_to=StreamTo, req_id=ReqId,
  430. response_format = Resp_format,
  431. options = Options} = CurReq,
  432. case IsClosing of
  433. true ->
  434. {_, Reqs_1} = queue:out(Reqs),
  435. Body = case TmpFilename of
  436. undefined ->
  437. Buf;
  438. _ ->
  439. ok = file:close(Fd),
  440. {file, TmpFilename}
  441. end,
  442. Reply = case get_value(give_raw_headers, Options, false) of
  443. true ->
  444. {ok, Status_line, Raw_headers, Body};
  445. false ->
  446. {ok, SC, Headers, Buf}
  447. end,
  448. State_1 = do_reply(State, From, StreamTo, ReqId, Resp_format, Reply),
  449. ok = do_error_reply(State_1#state{reqs = Reqs_1}, connection_closed),
  450. State_1;
  451. _ ->
  452. ok = do_error_reply(State, connection_closed),
  453. State
  454. end.
  455. do_connect(Host, Port, Options, #state{is_ssl = true,
  456. use_proxy = false,
  457. ssl_options = SSLOptions},
  458. Timeout) ->
  459. ssl:connect(Host, Port, get_sock_options(Host, Options, SSLOptions), Timeout);
  460. do_connect(Host, Port, Options, _State, Timeout) ->
  461. Socks5Host = get_value(socks5_host, Options, undefined),
  462. case Socks5Host of
  463. undefined ->
  464. gen_tcp:connect(Host, Port, get_sock_options(Host, Options, []), Timeout);
  465. _ ->
  466. catch ibrowse_socks5:connect(Host, Port, Options)
  467. end.
  468. get_sock_options(Host, Options, SSLOptions) ->
  469. Caller_socket_options = get_value(socket_options, Options, []),
  470. Ipv6Options = case is_ipv6_host(Host) of
  471. true ->
  472. [inet6];
  473. false ->
  474. []
  475. end,
  476. Other_sock_options = filter_sock_options(SSLOptions ++ Caller_socket_options ++ Ipv6Options),
  477. case lists:keysearch(nodelay, 1, Other_sock_options) of
  478. false ->
  479. [{nodelay, true}, binary, {active, false} | Other_sock_options];
  480. {value, _} ->
  481. [binary, {active, false} | Other_sock_options]
  482. end.
  483. is_ipv6_host(Host) ->
  484. case inet_parse:address(Host) of
  485. {ok, {_, _, _, _, _, _, _, _}} ->
  486. true;
  487. {ok, {_, _, _, _}} ->
  488. false;
  489. _ ->
  490. case inet:gethostbyname(Host) of
  491. {ok, #hostent{h_addrtype = inet6}} ->
  492. true;
  493. _ ->
  494. false
  495. end
  496. end.
  497. %% We don't want the caller to specify certain options
  498. filter_sock_options(Opts) ->
  499. lists:filter(fun({active, _}) ->
  500. false;
  501. ({packet, _}) ->
  502. false;
  503. (list) ->
  504. false;
  505. (_) ->
  506. true
  507. end, Opts).
  508. do_send(Req, #state{socket = Sock,
  509. is_ssl = true,
  510. use_proxy = true,
  511. proxy_tunnel_setup = Pts}) when Pts /= done -> gen_tcp:send(Sock, Req);
  512. do_send(Req, #state{socket = Sock, is_ssl = true}) -> ssl:send(Sock, Req);
  513. do_send(Req, #state{socket = Sock, is_ssl = false}) -> gen_tcp:send(Sock, Req).
  514. %% @spec do_send_body(Sock::socket_descriptor(), Source::source_descriptor(), IsSSL::boolean()) -> ok | error()
  515. %% source_descriptor() = fun_arity_0 |
  516. %% {fun_arity_0} |
  517. %% {fun_arity_1, term()}
  518. %% error() = term()
  519. do_send_body(Source, State, TE) when is_function(Source) ->
  520. do_send_body({Source}, State, TE);
  521. do_send_body({Source}, State, TE) when is_function(Source) ->
  522. do_send_body1(Source, Source(), State, TE);
  523. do_send_body({Source, Source_state}, State, TE) when is_function(Source) ->
  524. do_send_body1(Source, Source(Source_state), State, TE);
  525. do_send_body(Body, State, _TE) ->
  526. do_send(Body, State).
  527. do_send_body1(Source, Resp, State, TE) ->
  528. case Resp of
  529. {ok, Data} when Data == []; Data == <<>> ->
  530. do_send_body({Source}, State, TE);
  531. {ok, Data} ->
  532. do_send(maybe_chunked_encode(Data, TE), State),
  533. do_send_body({Source}, State, TE);
  534. {ok, Data, New_source_state} when Data == []; Data == <<>> ->
  535. do_send_body({Source, New_source_state}, State, TE);
  536. {ok, Data, New_source_state} ->
  537. do_send(maybe_chunked_encode(Data, TE), State),
  538. do_send_body({Source, New_source_state}, State, TE);
  539. eof when TE == true ->
  540. do_send(<<"0\r\n\r\n">>, State),
  541. ok;
  542. eof ->
  543. ok;
  544. Err ->
  545. Err
  546. end.
  547. maybe_chunked_encode(Data, false) ->
  548. Data;
  549. maybe_chunked_encode(Data, true) ->
  550. [?dec2hex(iolist_size(Data)), "\r\n", Data, "\r\n"].
  551. do_close(#state{socket = undefined}) -> ok;
  552. do_close(#state{socket = Sock,
  553. is_ssl = true,
  554. use_proxy = true,
  555. proxy_tunnel_setup = Pts
  556. }) when Pts /= done -> catch gen_tcp:close(Sock);
  557. do_close(#state{socket = Sock, is_ssl = true}) -> catch ssl:close(Sock);
  558. do_close(#state{socket = Sock, is_ssl = false}) -> catch gen_tcp:close(Sock).
  559. active_once(#state{cur_req = #request{caller_controls_socket = true}}) ->
  560. ok;
  561. active_once(#state{socket = Socket} = State) ->
  562. do_setopts(Socket, [{active, once}], State).
  563. do_setopts(_Sock, [], _) -> ok;
  564. do_setopts(Sock, Opts, #state{is_ssl = true,
  565. use_proxy = true,
  566. proxy_tunnel_setup = Pts}
  567. ) when Pts /= done -> inet:setopts(Sock, Opts);
  568. do_setopts(Sock, Opts, #state{is_ssl = true}) -> ssl:setopts(Sock, Opts);
  569. do_setopts(Sock, Opts, _) -> inet:setopts(Sock, Opts).
  570. check_ssl_options(Options, State) ->
  571. case get_value(is_ssl, Options, false) of
  572. false ->
  573. State;
  574. true ->
  575. State#state{is_ssl=true, ssl_options=get_value(ssl_options, Options)}
  576. end.
  577. send_req_1(From,
  578. #url{host = Host,
  579. port = Port} = Url,
  580. Headers, Method, Body, Options, Timeout,
  581. #state{socket = undefined} = State) ->
  582. {Host_1, Port_1, State_1} =
  583. case get_value(proxy_host, Options, false) of
  584. false ->
  585. {Host, Port, State};
  586. PHost ->
  587. ProxyUser = get_value(proxy_user, Options, []),
  588. ProxyPassword = get_value(proxy_password, Options, []),
  589. Digest = http_auth_digest(ProxyUser, ProxyPassword),
  590. {PHost, get_value(proxy_port, Options, 80),
  591. State#state{use_proxy = true,
  592. proxy_auth_digest = Digest}}
  593. end,
  594. State_2 = check_ssl_options(Options, State_1),
  595. do_trace("Connecting...~n", []),
  596. Conn_timeout = get_value(connect_timeout, Options, Timeout),
  597. case do_connect(Host_1, Port_1, Options, State_2, Conn_timeout) of
  598. {ok, Sock} ->
  599. do_trace("Connected! Socket: ~1000.p~n", [Sock]),
  600. State_3 = State_2#state{socket = Sock,
  601. connect_timeout = Conn_timeout},
  602. send_req_1(From, Url, Headers, Method, Body, Options, Timeout, State_3);
  603. Err ->
  604. shutting_down(State_2),
  605. do_trace("Error connecting. Reason: ~1000.p~n", [Err]),
  606. gen_server:reply(From, {error, {conn_failed, Err}}),
  607. {stop, normal, State_2}
  608. end;
  609. %% Send a CONNECT request.
  610. %% Wait for 200 OK
  611. %% Upgrade to SSL connection
  612. %% Then send request
  613. send_req_1(From,
  614. #url{
  615. host = Server_host,
  616. port = Server_port
  617. } = Url,
  618. Headers, Method, Body, Options, Timeout,
  619. #state{
  620. proxy_tunnel_setup = false,
  621. use_proxy = true,
  622. is_ssl = true} = State) ->
  623. Ref = case Timeout of
  624. infinity ->
  625. undefined;
  626. _ ->
  627. erlang:send_after(Timeout, self(), {req_timedout, From})
  628. end,
  629. NewReq = #request{
  630. method = connect,
  631. preserve_chunked_encoding = get_value(preserve_chunked_encoding, Options, false),
  632. options = Options,
  633. timer_ref = Ref
  634. },
  635. State_1 = State#state{reqs=queue:in(NewReq, State#state.reqs)},
  636. Pxy_auth_headers = maybe_modify_headers(Url, Method, Options, [], State_1),
  637. Path = [Server_host, $:, integer_to_list(Server_port)],
  638. {Req, Body_1} = make_request(connect, Pxy_auth_headers,
  639. Path, Path,
  640. [], Options, State_1, undefined),
  641. TE = is_chunked_encoding_specified(Options),
  642. trace_request(Req),
  643. case do_send(Req, State) of
  644. ok ->
  645. case do_send_body(Body_1, State_1, TE) of
  646. ok ->
  647. trace_request_body(Body_1),
  648. active_once(State_1),
  649. State_1_1 = inc_pipeline_counter(State_1),
  650. State_2 = State_1_1#state{status = get_header,
  651. cur_req = NewReq,
  652. proxy_tunnel_setup = in_progress,
  653. tunnel_setup_queue = [{From, Url, Headers, Method, Body, Options, Timeout}]},
  654. State_3 = set_inac_timer(State_2),
  655. {noreply, State_3};
  656. Err ->
  657. shutting_down(State_1),
  658. do_trace("Send failed... Reason: ~p~n", [Err]),
  659. gen_server:reply(From, {error, {send_failed, Err}}),
  660. {stop, normal, State_1}
  661. end;
  662. Err ->
  663. shutting_down(State_1),
  664. do_trace("Send failed... Reason: ~p~n", [Err]),
  665. gen_server:reply(From, {error, {send_failed, Err}}),
  666. {stop, normal, State_1}
  667. end;
  668. send_req_1(From, Url, Headers, Method, Body, Options, Timeout,
  669. #state{proxy_tunnel_setup = in_progress,
  670. tunnel_setup_queue = Q} = State) ->
  671. do_trace("Queued SSL request awaiting tunnel setup: ~n"
  672. "URL : ~s~n"
  673. "Method : ~p~n"
  674. "Headers : ~p~n", [Url, Method, Headers]),
  675. {noreply, State#state{tunnel_setup_queue = [{From, Url, Headers, Method, Body, Options, Timeout} | Q]}};
  676. send_req_1(From,
  677. #url{abspath = AbsPath,
  678. path = RelPath} = Url,
  679. Headers, Method, Body, Options, Timeout,
  680. #state{status = Status,
  681. socket = Socket} = State) ->
  682. cancel_timer(State#state.inactivity_timer_ref, {eat_message, timeout}),
  683. ReqId = make_req_id(),
  684. Resp_format = get_value(response_format, Options, list),
  685. Caller_socket_options = get_value(socket_options, Options, []),
  686. {StreamTo, Caller_controls_socket} =
  687. case get_value(stream_to, Options, undefined) of
  688. {Caller, once} when is_pid(Caller) or
  689. is_atom(Caller) ->
  690. Async_pid_rec = {{req_id_pid, ReqId}, self()},
  691. true = ets:insert(ibrowse_stream, Async_pid_rec),
  692. {Caller, true};
  693. undefined ->
  694. {undefined, false};
  695. Caller when is_pid(Caller) or
  696. is_atom(Caller) ->
  697. {Caller, false};
  698. Stream_to_inv ->
  699. exit({invalid_option, {stream_to, Stream_to_inv}})
  700. end,
  701. SaveResponseToFile = get_value(save_response_to_file, Options, false),
  702. Ref = case Timeout of
  703. infinity ->
  704. undefined;
  705. _ ->
  706. erlang:send_after(Timeout, self(), {req_timedout, From})
  707. end,
  708. NewReq = #request{url = Url,
  709. method = Method,
  710. stream_to = StreamTo,
  711. caller_controls_socket = Caller_controls_socket,
  712. caller_socket_options = Caller_socket_options,
  713. options = Options,
  714. req_id = ReqId,
  715. save_response_to_file = SaveResponseToFile,
  716. stream_chunk_size = get_stream_chunk_size(Options),
  717. response_format = Resp_format,
  718. from = From,
  719. preserve_chunked_encoding = get_value(preserve_chunked_encoding, Options, false),
  720. timer_ref = Ref
  721. },
  722. State_1 = State#state{reqs=queue:in(NewReq, State#state.reqs)},
  723. Headers_1 = maybe_modify_headers(Url, Method, Options, Headers, State_1),
  724. {Req, Body_1} = make_request(Method,
  725. Headers_1,
  726. AbsPath, RelPath, Body, Options, State_1,
  727. ReqId),
  728. trace_request(Req),
  729. do_setopts(Socket, Caller_socket_options, State_1),
  730. TE = is_chunked_encoding_specified(Options),
  731. case do_send(Req, State_1) of
  732. ok ->
  733. case do_send_body(Body_1, State_1, TE) of
  734. ok ->
  735. trace_request_body(Body_1),
  736. State_2 = inc_pipeline_counter(State_1),
  737. active_once(State_2),
  738. State_3 = case Status of
  739. idle ->
  740. State_2#state{status = get_header,
  741. cur_req = NewReq};
  742. _ ->
  743. State_2
  744. end,
  745. case StreamTo of
  746. undefined ->
  747. ok;
  748. _ ->
  749. gen_server:reply(From, {ibrowse_req_id, ReqId})
  750. end,
  751. State_4 = set_inac_timer(State_3),
  752. {noreply, State_4};
  753. Err ->
  754. shutting_down(State_1),
  755. do_trace("Send failed... Reason: ~p~n", [Err]),
  756. gen_server:reply(From, {error, {send_failed, Err}}),
  757. {stop, normal, State_1}
  758. end;
  759. Err ->
  760. shutting_down(State_1),
  761. do_trace("Send failed... Reason: ~p~n", [Err]),
  762. gen_server:reply(From, {error, {send_failed, Err}}),
  763. {stop, normal, State_1}
  764. end.
  765. maybe_modify_headers(#url{}, connect, _, Headers, State) ->
  766. add_proxy_auth_headers(State, Headers);
  767. maybe_modify_headers(#url{host = Host, port = Port} = Url,
  768. _Method,
  769. Options, Headers, State) ->
  770. case get_value(headers_as_is, Options, false) of
  771. false ->
  772. Headers_1 = add_auth_headers(Url, Options, Headers, State),
  773. HostHeaderValue = case lists:keysearch(host_header, 1, Options) of
  774. false ->
  775. case Port of
  776. 80 -> Host;
  777. 443 -> Host;
  778. _ -> [Host, ":", integer_to_list(Port)]
  779. end;
  780. {value, {_, Host_h_val}} ->
  781. Host_h_val
  782. end,
  783. [{"Host", HostHeaderValue} | Headers_1];
  784. true ->
  785. Headers
  786. end.
  787. add_auth_headers(#url{username = User,
  788. password = UPw},
  789. Options,
  790. Headers,
  791. State) ->
  792. Headers_1 = case User of
  793. undefined ->
  794. case get_value(basic_auth, Options, undefined) of
  795. undefined ->
  796. Headers;
  797. {U,P} ->
  798. [{"Authorization", ["Basic ", http_auth_digest(U, P)]} | Headers]
  799. end;
  800. _ ->
  801. [{"Authorization", ["Basic ", http_auth_digest(User, UPw)]} | Headers]
  802. end,
  803. add_proxy_auth_headers(State, Headers_1).
  804. add_proxy_auth_headers(#state{use_proxy = false}, Headers) ->
  805. Headers;
  806. add_proxy_auth_headers(#state{proxy_auth_digest = []}, Headers) ->
  807. Headers;
  808. add_proxy_auth_headers(#state{proxy_auth_digest = Auth_digest}, Headers) ->
  809. [{"Proxy-Authorization", ["Basic ", Auth_digest]} | Headers].
  810. http_auth_digest([], []) ->
  811. [];
  812. http_auth_digest(Username, Password) ->
  813. ibrowse_lib:encode_base64(Username ++ [$: | Password]).
  814. make_request(Method, Headers, AbsPath, RelPath, Body, Options,
  815. #state{use_proxy = UseProxy, is_ssl = Is_ssl}, ReqId) ->
  816. HttpVsn = http_vsn_string(get_value(http_vsn, Options, {1,1})),
  817. Fun1 = fun({X, Y}) when is_atom(X) ->
  818. {to_lower(atom_to_list(X)), X, Y};
  819. ({X, Y}) when is_list(X) ->
  820. {to_lower(X), X, Y}
  821. end,
  822. Headers_0 = [Fun1(X) || X <- Headers],
  823. Headers_1 =
  824. case lists:keysearch("content-length", 1, Headers_0) of
  825. false when (Body =:= [] orelse Body =:= <<>>) andalso
  826. (Method =:= post orelse Method =:= put) ->
  827. [{"content-length", "Content-Length", "0"} | Headers_0];
  828. false when is_binary(Body) orelse is_list(Body) ->
  829. [{"content-length", "Content-Length", integer_to_list(iolist_size(Body))} | Headers_0];
  830. _ ->
  831. %% Content-Length is already specified or Body is a
  832. %% function or function/state pair
  833. Headers_0
  834. end,
  835. {Headers_2, Body_1} =
  836. case is_chunked_encoding_specified(Options) of
  837. false ->
  838. {[{Y, Z} || {_, Y, Z} <- Headers_1], Body};
  839. true ->
  840. Chunk_size_1 = case get_value(transfer_encoding, Options) of
  841. chunked ->
  842. 5120;
  843. {chunked, Chunk_size} ->
  844. Chunk_size
  845. end,
  846. {[{Y, Z} || {X, Y, Z} <- Headers_1,
  847. X /= "content-length"] ++
  848. [{"Transfer-Encoding", "chunked"}],
  849. chunk_request_body(Body, Chunk_size_1)}
  850. end,
  851. Headers_3 = case lists:member({include_ibrowse_req_id, true}, Options) of
  852. true ->
  853. [{"x-ibrowse-request-id", io_lib:format("~1000.p",[ReqId])} | Headers_2];
  854. false ->
  855. Headers_2
  856. end,
  857. Headers_4 = cons_headers(Headers_3),
  858. Uri = case get_value(use_absolute_uri, Options, false) or UseProxy of
  859. true ->
  860. case Is_ssl of
  861. true ->
  862. RelPath;
  863. false ->
  864. AbsPath
  865. end;
  866. false ->
  867. RelPath
  868. end,
  869. {[method(Method), " ", Uri, " ", HttpVsn, crnl(), Headers_4, crnl()], Body_1}.
  870. is_chunked_encoding_specified(Options) ->
  871. case get_value(transfer_encoding, Options, false) of
  872. false ->
  873. false;
  874. {chunked, _} ->
  875. true;
  876. chunked ->
  877. true
  878. end.
  879. http_vsn_string({0,9}) -> "HTTP/0.9";
  880. http_vsn_string({1,0}) -> "HTTP/1.0";
  881. http_vsn_string({1,1}) -> "HTTP/1.1".
  882. cons_headers(Headers) ->
  883. cons_headers(Headers, []).
  884. cons_headers([], Acc) ->
  885. encode_headers(Acc);
  886. cons_headers([{basic_auth, {U,P}} | T], Acc) ->
  887. cons_headers(T, [{"Authorization",
  888. ["Basic ", ibrowse_lib:encode_base64(U++":"++P)]} | Acc]);
  889. cons_headers([{cookie, Cookie} | T], Acc) ->
  890. cons_headers(T, [{"Cookie", Cookie} | Acc]);
  891. cons_headers([{content_length, L} | T], Acc) ->
  892. cons_headers(T, [{"Content-Length", L} | Acc]);
  893. cons_headers([{content_type, L} | T], Acc) ->
  894. cons_headers(T, [{"Content-Type", L} | Acc]);
  895. cons_headers([H | T], Acc) ->
  896. cons_headers(T, [H | Acc]).
  897. encode_headers(L) ->
  898. encode_headers(L, []).
  899. encode_headers([{http_vsn, _Val} | T], Acc) ->
  900. encode_headers(T, Acc);
  901. encode_headers([{Name,Val} | T], Acc) when is_list(Name) ->
  902. encode_headers(T, [[Name, ": ", fmt_val(Val), crnl()] | Acc]);
  903. encode_headers([{Name,Val} | T], Acc) when is_atom(Name) ->
  904. encode_headers(T, [[atom_to_list(Name), ": ", fmt_val(Val), crnl()] | Acc]);
  905. encode_headers([], Acc) ->
  906. lists:reverse(Acc).
  907. chunk_request_body(Body, _ChunkSize) when is_tuple(Body) orelse
  908. is_function(Body) ->
  909. Body;
  910. chunk_request_body(Body, ChunkSize) ->
  911. chunk_request_body(Body, ChunkSize, []).
  912. chunk_request_body(Body, _ChunkSize, Acc) when Body == <<>>; Body == [] ->
  913. LastChunk = "0\r\n",
  914. lists:reverse(["\r\n", LastChunk | Acc]);
  915. chunk_request_body(Body, ChunkSize, Acc) when is_binary(Body),
  916. size(Body) >= ChunkSize ->
  917. <<ChunkBody:ChunkSize/binary, Rest/binary>> = Body,
  918. Chunk = [?dec2hex(ChunkSize),"\r\n",
  919. ChunkBody, "\r\n"],
  920. chunk_request_body(Rest, ChunkSize, [Chunk | Acc]);
  921. chunk_request_body(Body, _ChunkSize, Acc) when is_binary(Body) ->
  922. BodySize = size(Body),
  923. Chunk = [?dec2hex(BodySize),"\r\n",
  924. Body, "\r\n"],
  925. LastChunk = "0\r\n",
  926. lists:reverse(["\r\n", LastChunk, Chunk | Acc]);
  927. chunk_request_body(Body, ChunkSize, Acc) when length(Body) >= ChunkSize ->
  928. {ChunkBody, Rest} = split_list_at(Body, ChunkSize),
  929. Chunk = [?dec2hex(ChunkSize),"\r\n",
  930. ChunkBody, "\r\n"],
  931. chunk_request_body(Rest, ChunkSize, [Chunk | Acc]);
  932. chunk_request_body(Body, _ChunkSize, Acc) when is_list(Body) ->
  933. BodySize = length(Body),
  934. Chunk = [?dec2hex(BodySize),"\r\n",
  935. Body, "\r\n"],
  936. LastChunk = "0\r\n",
  937. lists:reverse(["\r\n", LastChunk, Chunk | Acc]).
  938. parse_response(<<>>, #state{cur_req = undefined}=State) ->
  939. State#state{status = idle};
  940. parse_response(Data, #state{cur_req = undefined}) ->
  941. do_trace("Data left to process when no pending request. ~1000.p~n", [Data]),
  942. {error, data_in_status_idle};
  943. parse_response(Data, #state{reply_buffer = Acc, reqs = Reqs,
  944. cur_req = CurReq} = State) ->
  945. #request{from=From, stream_to=StreamTo, req_id=ReqId,
  946. method=Method, response_format = Resp_format,
  947. options = Options, timer_ref = T_ref
  948. } = CurReq,
  949. MaxHeaderSize = ibrowse:get_config_value(max_headers_size, infinity),
  950. case scan_header(Acc, Data) of
  951. {yes, Headers, Data_1} ->
  952. do_trace("Recvd Header Data -> ~s~n----~n", [Headers]),
  953. do_trace("Recvd headers~n--- Headers Begin ---~n~s~n--- Headers End ---~n~n", [Headers]),
  954. {HttpVsn, StatCode, Headers_1, Status_line, Raw_headers} = parse_headers(Headers),
  955. do_trace("HttpVsn: ~p StatusCode: ~p Headers_1 -> ~1000.p~n", [HttpVsn, StatCode, Headers_1]),
  956. LCHeaders = [{to_lower(X), Y} || {X,Y} <- Headers_1],
  957. ConnClose = to_lower(get_value("connection", LCHeaders, "false")),
  958. IsClosing = is_connection_closing(HttpVsn, ConnClose),
  959. State_0 = case IsClosing of
  960. true ->
  961. shutting_down(State),
  962. State#state{is_closing = IsClosing};
  963. false ->
  964. State
  965. end,
  966. Give_raw_headers = get_value(give_raw_headers, Options, false),
  967. State_1 = case Give_raw_headers of
  968. true ->
  969. State_0#state{recvd_headers=Headers_1, status=get_body,
  970. reply_buffer = <<>>,
  971. status_line = Status_line,
  972. raw_headers = Raw_headers,
  973. http_status_code=StatCode};
  974. false ->
  975. State_0#state{recvd_headers=Headers_1, status=get_body,
  976. reply_buffer = <<>>,
  977. http_status_code=StatCode}
  978. end,
  979. put(conn_close, ConnClose),
  980. TransferEncodings = to_lower(get_value("transfer-encoding", LCHeaders, "false")),
  981. IsChunked = lists:any(fun(Enc) -> string:strip(Enc) =:= "chunked" end,
  982. string:tokens(TransferEncodings, ",")),
  983. Head_response_with_body = lists:member({workaround, head_response_with_body}, Options),
  984. case get_value("content-length", LCHeaders, undefined) of
  985. _ when Method == connect,
  986. hd(StatCode) == $2 ->
  987. {_, Reqs_1} = queue:out(Reqs),
  988. cancel_timer(T_ref),
  989. upgrade_to_ssl(set_cur_request(State_0#state{reqs = Reqs_1,
  990. recvd_headers = [],
  991. status = idle
  992. }));
  993. _ when Method == connect ->
  994. {_, Reqs_1} = queue:out(Reqs),
  995. do_error_reply(State#state{reqs = Reqs_1},
  996. {error, proxy_tunnel_failed}),
  997. {error, proxy_tunnel_failed};
  998. _ when Method =:= head,
  999. Head_response_with_body =:= false ->
  1000. %% This (HEAD response with body) is not supposed
  1001. %% to happen, but it does. An Apache server was
  1002. %% observed to send an "empty" body, but in a
  1003. %% Chunked-Transfer-Encoding way, which meant
  1004. %% there was still a body. Issue #67 on Github
  1005. {_, Reqs_1} = queue:out(Reqs),
  1006. send_async_headers(ReqId, StreamTo, Give_raw_headers, State_1),
  1007. State_1_1 = do_reply(State_1, From, StreamTo, ReqId, Resp_format,
  1008. {ok, StatCode, Headers_1, []}),
  1009. cancel_timer(T_ref, {eat_message, {req_timedout, From}}),
  1010. State_2 = reset_state(State_1_1),
  1011. State_3 = set_cur_request(State_2#state{reqs = Reqs_1}),
  1012. parse_response(Data_1, State_3);
  1013. _ when hd(StatCode) =:= $1 ->
  1014. %% No message body is expected. Server may send
  1015. %% one or more 1XX responses before a proper
  1016. %% response.
  1017. send_async_headers(ReqId, StreamTo, Give_raw_headers, State_1),
  1018. do_trace("Recvd a status code of ~p. Ignoring and waiting for a proper response~n", [StatCode]),
  1019. parse_response(Data_1, State_1#state{recvd_headers = [],
  1020. status = get_header});
  1021. _ when StatCode =:= "204";
  1022. StatCode =:= "304" ->
  1023. %% No message body is expected for these Status Codes.
  1024. %% RFC2616 - Sec 4.4
  1025. {_, Reqs_1} = queue:out(Reqs),
  1026. send_async_headers(ReqId, StreamTo, Give_raw_headers, State_1),
  1027. State_1_1 = do_reply(State_1, From, StreamTo, ReqId, Resp_format,
  1028. {ok, StatCode, Headers_1, []}),
  1029. cancel_timer(T_ref, {eat_message, {req_timedout, From}}),
  1030. State_2 = reset_state(State_1_1),
  1031. State_3 = set_cur_request(State_2#state{reqs = Reqs_1}),
  1032. parse_response(Data_1, State_3);
  1033. _ when IsChunked ->
  1034. do_trace("Chunked encoding detected...~n",[]),
  1035. send_async_headers(ReqId, StreamTo, Give_raw_headers, State_1),
  1036. case parse_11_response(Data_1, State_1#state{transfer_encoding=chunked,
  1037. chunk_size=chunk_start,
  1038. reply_buffer = <<>>}) of
  1039. {error, Reason} ->
  1040. fail_pipelined_requests(State_1,
  1041. {error, {Reason,
  1042. {stat_code, StatCode}, Headers_1}}),
  1043. {error, Reason};
  1044. State_2 ->
  1045. State_2
  1046. end;
  1047. undefined when HttpVsn =:= "HTTP/1.0";
  1048. ConnClose =:= "close" ->
  1049. send_async_headers(ReqId, StreamTo, Give_raw_headers, State_1),
  1050. State_1#state{reply_buffer = Data_1};
  1051. undefined ->
  1052. fail_pipelined_requests(State_1,
  1053. {error, {content_length_undefined,
  1054. {stat_code, StatCode}, Headers}}),
  1055. {error, content_length_undefined};
  1056. V ->
  1057. case catch list_to_integer(V) of
  1058. V_1 when is_integer(V_1), V_1 >= 0 ->
  1059. send_async_headers(ReqId, StreamTo, Give_raw_headers, State_1),
  1060. do_trace("Recvd Content-Length of ~p~n", [V_1]),
  1061. State_2 = State_1#state{rep_buf_size=0,
  1062. reply_buffer = <<>>,
  1063. content_length=V_1},
  1064. case parse_11_response(Data_1, State_2) of
  1065. {error, Reason} ->
  1066. fail_pipelined_requests(State_1,
  1067. {error, {Reason,
  1068. {stat_code, StatCode}, Headers_1}}),
  1069. {error, Reason};
  1070. State_3 ->
  1071. State_3
  1072. end;
  1073. _ ->
  1074. fail_pipelined_requests(State_1,
  1075. {error, {content_length_undefined,
  1076. {stat_code, StatCode}, Headers}}),
  1077. {error, content_length_undefined}
  1078. end
  1079. end;
  1080. {no, Acc_1} when MaxHeaderSize == infinity ->
  1081. State#state{reply_buffer = Acc_1};
  1082. {no, Acc_1} when size(Acc_1) < MaxHeaderSize ->
  1083. State#state{reply_buffer = Acc_1};
  1084. {no, _Acc_1} ->
  1085. fail_pipelined_requests(State, {error, max_headers_size_exceeded}),
  1086. {error, max_headers_size_exceeded}
  1087. end.
  1088. upgrade_to_ssl(#state{socket = Socket,
  1089. connect_timeout = Conn_timeout,
  1090. ssl_options = Ssl_options,
  1091. tunnel_setup_queue = Q} = State) ->
  1092. case ssl:connect(Socket, Ssl_options, Conn_timeout) of
  1093. {ok, Ssl_socket} ->
  1094. do_trace("Upgraded to SSL socket!!~n", []),
  1095. State_1 = State#state{socket = Ssl_socket,
  1096. proxy_tunnel_setup = done},
  1097. send_queued_requests(lists:reverse(Q), State_1);
  1098. Err ->
  1099. do_trace("Upgrade to SSL socket failed. Reson: ~p~n", [Err]),
  1100. do_error_reply(State, {error, {send_failed, Err}}),
  1101. {error, send_failed}
  1102. end.
  1103. send_queued_requests([], State) ->
  1104. do_trace("Sent all queued requests via SSL connection~n", []),
  1105. State#state{tunnel_setup_queue = []};
  1106. send_queued_requests([{From, Url, Headers, Method, Body, Options, Timeout} | Q],
  1107. State) ->
  1108. case send_req_1(From, Url, Headers, Method, Body, Options, Timeout, State) of
  1109. {noreply, State_1} ->
  1110. send_queued_requests(Q, State_1);
  1111. Err ->
  1112. do_trace("Error sending queued SSL request: ~n"
  1113. "URL : ~s~n"
  1114. "Method : ~p~n"
  1115. "Headers : ~p~n", [Url, Method, Headers]),
  1116. do_error_reply(State, {error, {send_failed, Err}}),
  1117. {error, send_failed}
  1118. end.
  1119. is_connection_closing("HTTP/0.9", _) -> true;
  1120. is_connection_closing(_, "close") -> true;
  1121. is_connection_closing("HTTP/1.0", "false") -> true;
  1122. is_connection_closing(_, _) -> false.
  1123. %% This clause determines the chunk size when given data from the beginning of the chunk
  1124. parse_11_response(DataRecvd,
  1125. #state{transfer_encoding = chunked,
  1126. chunk_size = chunk_start,
  1127. chunk_size_buffer = Chunk_sz_buf
  1128. } = State) ->
  1129. case scan_crlf(Chunk_sz_buf, DataRecvd) of
  1130. {yes, ChunkHeader, Data_1} ->
  1131. State_1 = maybe_accumulate_ce_data(State, <<ChunkHeader/binary, $\r, $\n>>),
  1132. ChunkSize = parse_chunk_header(ChunkHeader),
  1133. %%
  1134. %% Do we have to preserve the chunk encoding when
  1135. %% streaming? NO. This should be transparent to the client
  1136. %% process. Chunked encoding was only introduced to make
  1137. %% it efficient for the server.
  1138. %%
  1139. RemLen = size(Data_1),
  1140. do_trace("Determined chunk size: ~p. Already recvd: ~p~n",
  1141. [ChunkSize, RemLen]),
  1142. parse_11_response(Data_1, State_1#state{chunk_size_buffer = <<>>,
  1143. deleted_crlf = true,
  1144. recvd_chunk_size = 0,
  1145. chunk_size = ChunkSize});
  1146. {no, Data_1} ->
  1147. State#state{chunk_size_buffer = Data_1}
  1148. end;
  1149. %% This clause is to remove the CRLF between two chunks
  1150. %%
  1151. parse_11_response(DataRecvd,
  1152. #state{transfer_encoding = chunked,
  1153. chunk_size = tbd,
  1154. chunk_size_buffer = Buf
  1155. } = State) ->
  1156. case scan_crlf(Buf, DataRecvd) of
  1157. {yes, _, NextChunk} ->
  1158. State_1 = maybe_accumulate_ce_data(State, <<$\r, $\n>>),
  1159. State_2 = State_1#state{chunk_size = chunk_start,
  1160. chunk_size_buffer = <<>>,
  1161. deleted_crlf = true},
  1162. parse_11_response(NextChunk, State_2);
  1163. {no, Data_1} ->
  1164. State#state{chunk_size_buffer = Data_1}
  1165. end;
  1166. %% This clause deals with the end of a chunked transfer. ibrowse does
  1167. %% not support Trailers in the Chunked Transfer encoding. Any trailer
  1168. %% received is silently discarded.
  1169. parse_11_response(DataRecvd,
  1170. #state{transfer_encoding = chunked, chunk_size = 0,
  1171. cur_req = CurReq,
  1172. deleted_crlf = DelCrlf,
  1173. chunk_size_buffer = Trailer,
  1174. reqs = Reqs} = State) ->
  1175. do_trace("Detected end of chunked transfer...~n", []),
  1176. DataRecvd_1 = case DelCrlf of
  1177. false ->
  1178. DataRecvd;
  1179. true ->
  1180. <<$\r, $\n, DataRecvd/binary>>
  1181. end,
  1182. case scan_header(Trailer, DataRecvd_1) of
  1183. {yes, TEHeaders, Rem} ->
  1184. {_, Reqs_1} = queue:out(Reqs),
  1185. State_1 = maybe_accumulate_ce_data(State, <<TEHeaders/binary, $\r, $\n>>),
  1186. State_2 = handle_response(CurReq,
  1187. State_1#state{reqs = Reqs_1}),
  1188. parse_response(Rem, reset_state(State_2));
  1189. {no, Rem} ->
  1190. accumulate_response(<<>>, State#state{chunk_size_buffer = Rem, deleted_crlf = false})
  1191. end;
  1192. %% This clause extracts a chunk, given the size.
  1193. parse_11_response(DataRecvd,
  1194. #state{transfer_encoding = chunked,
  1195. chunk_size = CSz,
  1196. recvd_chunk_size = Recvd_csz,
  1197. rep_buf_size = RepBufSz} = State) ->
  1198. NeedBytes = CSz - Recvd_csz,
  1199. DataLen = size(DataRecvd),
  1200. do_trace("Recvd more data: size: ~p. NeedBytes: ~p~n", [DataLen, NeedBytes]),
  1201. case DataLen >= NeedBytes of
  1202. true ->
  1203. {RemChunk, RemData} = split_binary(DataRecvd, NeedBytes),
  1204. do_trace("Recvd another chunk...~p~n", [RemChunk]),
  1205. do_trace("RemData -> ~p~n", [RemData]),
  1206. case accumulate_response(RemChunk, State) of
  1207. {error, Reason} ->
  1208. do_trace("Error accumulating response --> ~p~n", [Reason]),
  1209. {error, Reason};
  1210. #state{} = State_1 ->
  1211. State_2 = State_1#state{chunk_size=tbd},
  1212. parse_11_response(RemData, State_2)
  1213. end;
  1214. false ->
  1215. accumulate_response(DataRecvd,
  1216. State#state{rep_buf_size = RepBufSz + DataLen,
  1217. recvd_chunk_size = Recvd_csz + DataLen})
  1218. end;
  1219. %% This clause to extract the body when Content-Length is specified
  1220. parse_11_response(DataRecvd,
  1221. #state{content_length=CL, rep_buf_size=RepBufSz,
  1222. reqs=Reqs}=State) ->
  1223. NeedBytes = CL - RepBufSz,
  1224. DataLen = size(DataRecvd),
  1225. case DataLen >= NeedBytes of
  1226. true ->
  1227. {RemBody, Rem} = split_binary(DataRecvd, NeedBytes),
  1228. {_, Reqs_1} = queue:out(Reqs),
  1229. State_1 = accumulate_response(RemBody, State),
  1230. State_2 = handle_response(State_1#state.cur_req, State_1#state{reqs=Reqs_1}),
  1231. State_3 = reset_state(State_2),
  1232. parse_response(Rem, State_3);
  1233. false ->
  1234. accumulate_response(DataRecvd, State#state{rep_buf_size = (RepBufSz+DataLen)})
  1235. end.
  1236. maybe_accumulate_ce_data(#state{cur_req = #request{preserve_chunked_encoding = false}} = State, _) ->
  1237. State;
  1238. maybe_accumulate_ce_data(State, Data) ->
  1239. accumulate_response(Data, State).
  1240. handle_response(#request{from=From, stream_to=StreamTo, req_id=ReqId,
  1241. response_format = Resp_format,
  1242. save_response_to_file = SaveResponseToFile,
  1243. tmp_file_name = TmpFilename,
  1244. tmp_file_fd = Fd,
  1245. options = Options,
  1246. timer_ref = ReqTimer
  1247. },
  1248. #state{http_status_code = SCode,
  1249. status_line = Status_line,
  1250. raw_headers = Raw_headers,
  1251. reply_buffer = RepBuf,
  1252. recvd_headers = RespHeaders}=State) when SaveResponseToFile /= false ->
  1253. Body = RepBuf,
  1254. case Fd of
  1255. undefined ->
  1256. ok;
  1257. _ ->
  1258. ok = file:close(Fd)
  1259. end,
  1260. ResponseBody = case TmpFilename of
  1261. undefined ->
  1262. Body;
  1263. _ ->
  1264. {file, TmpFilename}
  1265. end,
  1266. {Resp_headers_1, Raw_headers_1} = maybe_add_custom_headers(RespHeaders, Raw_headers, Options),
  1267. Reply = case get_value(give_raw_headers, Options, false) of
  1268. true ->
  1269. {ok, Status_line, Raw_headers_1, ResponseBody};
  1270. false ->
  1271. {ok, SCode, Resp_headers_1, ResponseBody}
  1272. end,
  1273. State_1 = do_reply(State, From, StreamTo, ReqId, Resp_format, Reply),
  1274. cancel_timer(ReqTimer, {eat_message, {req_timedout, From}}),
  1275. set_cur_request(State_1);
  1276. handle_response(#request{from=From, stream_to=StreamTo, req_id=ReqId,
  1277. response_format = Resp_format,
  1278. options = Options, timer_ref = ReqTimer},
  1279. #state{http_status_code = SCode,
  1280. status_line = Status_line,
  1281. raw_headers = Raw_headers,
  1282. recvd_headers = Resp_headers,
  1283. reply_buffer = RepBuf
  1284. } = State) ->
  1285. Body = RepBuf,
  1286. {Resp_headers_1, Raw_headers_1} = maybe_add_custom_headers(Resp_headers, Raw_headers, Options),
  1287. Reply = case get_value(give_raw_headers, Options, false) of
  1288. true ->
  1289. {ok, Status_line, Raw_headers_1, Body};
  1290. false ->
  1291. {ok, SCode, Resp_headers_1, Body}
  1292. end,
  1293. State_1 = do_reply(State, From, StreamTo, ReqId, Resp_format, Reply),
  1294. cancel_timer(ReqTimer, {eat_message, {req_timedout, From}}),
  1295. set_cur_request(State_1).
  1296. reset_state(State) ->
  1297. State#state{status = get_header,
  1298. rep_buf_size = 0,
  1299. streamed_size = 0,
  1300. content_length = undefined,
  1301. reply_buffer = <<>>,
  1302. chunk_size_buffer = <<>>,
  1303. recvd_headers = [],
  1304. status_line = undefined,
  1305. raw_headers = undefined,
  1306. deleted_crlf = false,
  1307. http_status_code = undefined,
  1308. chunk_size = undefined,
  1309. transfer_encoding = undefined
  1310. }.
  1311. set_cur_request(#state{reqs = Reqs, socket = Socket} = State) ->
  1312. case queue:peek(Reqs) of
  1313. empty ->
  1314. State#state{cur_req = undefined};
  1315. {value, #request{caller_controls_socket = Ccs} = NextReq} ->
  1316. case Ccs of
  1317. true ->
  1318. do_setopts(Socket, [{active, once}], State);
  1319. _ ->
  1320. ok
  1321. end,
  1322. State#state{cur_req = NextReq}
  1323. end.
  1324. parse_headers(Headers) ->
  1325. case scan_crlf(Headers) of
  1326. {yes, StatusLine, T} ->
  1327. parse_headers(StatusLine, T);
  1328. {no, StatusLine} ->
  1329. parse_headers(StatusLine, <<>>)
  1330. end.
  1331. parse_headers(StatusLine, Headers) ->
  1332. Headers_1 = parse_headers_1(Headers),
  1333. case parse_status_line(StatusLine) of
  1334. {ok, HttpVsn, StatCode, _Msg} ->
  1335. put(http_prot_vsn, HttpVsn),
  1336. {HttpVsn, StatCode, Headers_1, StatusLine, Headers};
  1337. _ -> %% A HTTP 0.9 response?
  1338. put(http_prot_vsn, "HTTP/0.9"),
  1339. {"HTTP/0.9", undefined, Headers, StatusLine, Headers}
  1340. end.
  1341. % From RFC 2616
  1342. %
  1343. % HTTP/1.1 header field values can be folded onto multiple lines if
  1344. % the continuation line begins with a space or horizontal tab. All
  1345. % linear white space, including folding, has the same semantics as
  1346. % SP. A recipient MAY replace any linear white space with a single
  1347. % SP before interpreting the field value or forwarding the message
  1348. % downstream.
  1349. parse_headers_1(B) when is_binary(B) ->
  1350. parse_headers_1(binary_to_list(B));
  1351. parse_headers_1(String) ->
  1352. parse_headers_1(String, [], []).
  1353. parse_headers_1([$\n, H |T], [$\r | L], Acc) when H =:= 32;
  1354. H =:= $\t ->
  1355. parse_headers_1(lists:dropwhile(fun(X) ->
  1356. is_whitespace(X)
  1357. end, T), [32 | L], Acc);
  1358. parse_headers_1([$\n, H |T], L, Acc) when H =:= 32;
  1359. H =:= $\t ->
  1360. parse_headers_1(lists:dropwhile(fun(X) ->
  1361. is_whitespace(X)
  1362. end, T), [32 | L], Acc);
  1363. parse_headers_1([$\n|T], [$\r | L], Acc) ->
  1364. case parse_header(lists:reverse(L)) of
  1365. invalid ->
  1366. parse_headers_1(T, [], Acc);
  1367. NewHeader ->
  1368. parse_headers_1(T, [], [NewHeader | Acc])
  1369. end;
  1370. parse_headers_1([$\n|T], L, Acc) ->
  1371. case parse_header(lists:reverse(L)) of
  1372. invalid ->
  1373. parse_headers_1(T, [], Acc);
  1374. NewHeader ->
  1375. parse_headers_1(T, [], [NewHeader | Acc])
  1376. end;
  1377. parse_headers_1([H|T], L, Acc) ->
  1378. parse_headers_1(T, [H|L], Acc);
  1379. parse_headers_1([], [], Acc) ->
  1380. lists:reverse(Acc);
  1381. parse_headers_1([], L, Acc) ->
  1382. Acc_1 = case parse_header(lists:reverse(L)) of
  1383. invalid ->
  1384. Acc;
  1385. NewHeader ->
  1386. [NewHeader | Acc]
  1387. end,
  1388. lists:reverse(Acc_1).
  1389. parse_status_line(Line) when is_binary(Line) ->
  1390. parse_status_line(binary_to_list(Line));
  1391. parse_status_line(Line) ->
  1392. parse_status_line(Line, get_prot_vsn, [], []).
  1393. parse_status_line([32 | T], get_prot_vsn, ProtVsn, StatCode) ->
  1394. parse_status_line(T, get_status_code, ProtVsn, StatCode);
  1395. parse_status_line([32 | T], get_status_code, ProtVsn, StatCode) ->
  1396. {ok, lists:reverse(ProtVsn), lists:reverse(StatCode), T};
  1397. parse_status_line([], get_status_code, ProtVsn, StatCode) ->
  1398. {ok, lists:reverse(ProtVsn), lists:reverse(StatCode), []};
  1399. parse_status_line([H | T], get_prot_vsn, ProtVsn, StatCode) ->
  1400. parse_status_line(T, get_prot_vsn, [H|ProtVsn], StatCode);
  1401. parse_status_line([H | T], get_status_code, ProtVsn, StatCode) ->
  1402. parse_status_line(T, get_status_code, ProtVsn, [H | StatCode]);
  1403. parse_status_line([], _, _, _) ->
  1404. http_09.
  1405. parse_header(L) ->
  1406. parse_header(L, []).
  1407. parse_header([$: | V], Acc) ->
  1408. {lists:reverse(Acc), string:strip(V)};
  1409. parse_header([H | T], Acc) ->
  1410. parse_header(T, [H | Acc]);
  1411. parse_header([], _) ->
  1412. invalid.
  1413. scan_header(Bin) ->
  1414. case get_crlf_crlf_pos(Bin, 0) of
  1415. {yes, Pos} ->
  1416. {Headers, <<_:4/binary, Body/binary>>} = split_binary(Bin, Pos),
  1417. {yes, Headers, Body};
  1418. {yes_dodgy, Pos} ->
  1419. {Headers, <<_:2/binary, Body/binary>>} = split_binary(Bin, Pos),
  1420. {yes, Headers, Body};
  1421. no ->
  1422. {no, Bin}
  1423. end.
  1424. scan_header(Bin1, Bin2) when size(Bin1) < 4 ->
  1425. scan_header(<<Bin1/binary, Bin2/binary>>);
  1426. scan_header(Bin1, <<>>) ->
  1427. scan_header(Bin1);
  1428. scan_header(Bin1, Bin2) ->
  1429. Bin1_already_scanned_size = size(Bin1) - 4,
  1430. <<Headers_prefix:Bin1_already_scanned_size/binary, Rest/binary>> = Bin1,
  1431. Bin_to_scan = <<Rest/binary, Bin2/binary>>,
  1432. case get_crlf_crlf_pos(Bin_to_scan, 0) of
  1433. {yes, Pos} ->
  1434. {Headers_suffix, <<_:4/binary, Body/binary>>} = split_binary(Bin_to_scan, Pos),
  1435. {yes, <<Headers_prefix/binary, Headers_suffix/binary>>, Body};
  1436. {yes_dodgy, Pos} ->
  1437. {Headers_suffix, <<_:2/binary, Body/binary>>} = split_binary(Bin_to_scan, Pos),
  1438. {yes, <<Headers_prefix/binary, Headers_suffix/binary>>, Body};
  1439. no ->
  1440. {no, <<Bin1/binary, Bin2/binary>>}
  1441. end.
  1442. get_crlf_crlf_pos(<<$\r, $\n, $\r, $\n, _/binary>>, Pos) -> {yes, Pos};
  1443. get_crlf_crlf_pos(<<$\n, $\n, _/binary>>, Pos) -> {yes_dodgy, Pos};
  1444. get_crlf_crlf_pos(<<_, Rest/binary>>, Pos) -> get_crlf_crlf_pos(Rest, Pos + 1);
  1445. get_crlf_crlf_pos(<<>>, _) -> no.
  1446. scan_crlf(Bin) ->
  1447. case get_crlf_pos(Bin) of
  1448. {yes, Offset, Pos} ->
  1449. {Prefix, <<_:Offset/binary, Suffix/binary>>} = split_binary(Bin, Pos),
  1450. {yes, Prefix, Suffix};
  1451. no ->
  1452. {no, Bin}
  1453. end.
  1454. scan_crlf(<<>>, Bin2) ->
  1455. scan_crlf(Bin2);
  1456. scan_crlf(Bin1, Bin2) when size(Bin1) < 2 ->
  1457. scan_crlf(<<Bin1/binary, Bin2/binary>>);
  1458. scan_crlf(Bin1, Bin2) ->
  1459. scan_crlf_1(size(Bin1) - 2, Bin1, Bin2).
  1460. scan_crlf_1(Bin1_head_size, Bin1, Bin2) ->
  1461. <<Bin1_head:Bin1_head_size/binary, Bin1_tail/binary>> = Bin1,
  1462. Bin3 = <<Bin1_tail/binary, Bin2/binary>>,
  1463. case get_crlf_pos(Bin3) of
  1464. {yes, Offset, Pos} ->
  1465. {Prefix, <<_:Offset/binary, Suffix/binary>>} = split_binary(Bin3, Pos),
  1466. {yes, list_to_binary([Bin1_head, Prefix]), Suffix};
  1467. no ->
  1468. {no, list_to_binary([Bin1, Bin2])}
  1469. end.
  1470. get_crlf_pos(Bin) ->
  1471. get_crlf_pos(Bin, 0).
  1472. get_crlf_pos(<<$\r, $\n, _/binary>>, Pos) -> {yes, 2, Pos};
  1473. get_crlf_pos(<<$\n, _/binary>>, Pos) -> {yes, 1, Pos};
  1474. get_crlf_pos(<<_, Rest/binary>>, Pos) -> get_crlf_pos(Rest, Pos + 1);
  1475. get_crlf_pos(<<>>, _) -> no.
  1476. fmt_val(L) when is_list(L) -> L;
  1477. fmt_val(I) when is_integer(I) -> integer_to_list(I);
  1478. fmt_val(A) when is_atom(A) -> atom_to_list(A);
  1479. fmt_val(B) when is_binary(B) -> B;
  1480. fmt_val(Term) -> io_lib:format("~p", [Term]).
  1481. crnl() -> "\r\n".
  1482. method(connect) -> "CONNECT";
  1483. method(delete) -> "DELETE";
  1484. method(get) -> "GET";
  1485. method(head) -> "HEAD";
  1486. method(options) -> "OPTIONS";
  1487. method(post) -> "POST";
  1488. method(put) -> "PUT";
  1489. method(trace) -> "TRACE";
  1490. %% webdav
  1491. method(copy) -> "COPY";
  1492. method(lock) -> "LOCK";
  1493. method(mkcol) -> "MKCOL";
  1494. method(move) -> "MOVE";
  1495. method(propfind) -> "PROPFIND";
  1496. method(proppatch) -> "PROPPATCH";
  1497. method(search) -> "SEARCH";
  1498. method(unlock) -> "UNLOCK";
  1499. %% subversion %%
  1500. method(report) -> "REPORT";
  1501. method(mkactivity) -> "MKACTIVITY";
  1502. method(checkout) -> "CHECKOUT";
  1503. method(merge) -> "MERGE";
  1504. %% upnp
  1505. method(msearch) -> "MSEARCH";
  1506. method(notify) -> "NOTIFY";
  1507. method(subscribe) -> "SUBSCRIBE";
  1508. method(unsubscribe) -> "UNSUBSCRIBE";
  1509. %% rfc-5789
  1510. method(patch) -> "PATCH";
  1511. method(purge) -> "PURGE".
  1512. %% From RFC 2616
  1513. %%
  1514. % The chunked encoding modifies the body of a message in order to
  1515. % transfer it as a series of chunks, each with its own size indicator,
  1516. % followed by an OPTIONAL trailer containing entity-header
  1517. % fields. This allows dynamically produced content to be transferred
  1518. % along with the information necessary for the recipient to verify
  1519. % that it has received the full message.
  1520. % Chunked-Body = *chunk
  1521. % last-chunk
  1522. % trailer
  1523. % CRLF
  1524. % chunk = chunk-size [ chunk-extension ] CRLF
  1525. % chunk-data CRLF
  1526. % chunk-size = 1*HEX
  1527. % last-chunk = 1*("0") [ chunk-extension ] CRLF
  1528. % chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
  1529. % chunk-ext-name = token
  1530. % chunk-ext-val = token | quoted-string
  1531. % chunk-data = chunk-size(OCTET)
  1532. % trailer = *(entity-header CRLF)
  1533. % The chunk-size field is a string of hex digits indicating the size
  1534. % of the chunk. The chunked encoding is ended by any chunk whose size
  1535. % is zero, followed by the trailer, which is terminated by an empty
  1536. % line.
  1537. %%
  1538. %% The parsing implemented here discards all chunk extensions. It also
  1539. %% strips trailing spaces from the chunk size fields as Apache 1.3.27 was
  1540. %% sending them.
  1541. parse_chunk_header(ChunkHeader) ->
  1542. parse_chunk_header(ChunkHeader, []).
  1543. parse_chunk_header(<<$;, _/binary>>, Acc) ->
  1544. hexlist_to_integer(lists:reverse(Acc));
  1545. parse_chunk_header(<<H, T/binary>>, Acc) ->
  1546. case is_whitespace(H) of
  1547. true ->
  1548. parse_chunk_header(T, Acc);
  1549. false ->
  1550. parse_chunk_header(T, [H | Acc])
  1551. end;
  1552. parse_chunk_header(<<>>, Acc) ->
  1553. hexlist_to_integer(lists:reverse(Acc)).
  1554. is_whitespace($\s) -> true;
  1555. is_whitespace($\r) -> true;
  1556. is_whitespace($\n) -> true;
  1557. is_whitespace($\t) -> true;
  1558. is_whitespace(_) -> false.
  1559. send_async_headers(_ReqId, undefined, _, _State) ->
  1560. ok;
  1561. send_async_headers(ReqId, StreamTo, Give_raw_headers,
  1562. #state{status_line = Status_line, raw_headers = Raw_headers,
  1563. recvd_headers = Headers, http_status_code = StatCode,
  1564. cur_req = #request{options = Opts}
  1565. }) ->
  1566. {Headers_1, Raw_headers_1} = maybe_add_custom_headers(Headers, Raw_headers, Opts),
  1567. case Give_raw_headers of
  1568. false ->
  1569. catch StreamTo ! {ibrowse_async_headers, ReqId, StatCode, Headers_1};
  1570. true ->
  1571. catch StreamTo ! {ibrowse_async_headers, ReqId, Status_line, Raw_headers_1}
  1572. end.
  1573. maybe_add_custom_headers(Headers, Raw_headers, Opts) ->
  1574. Custom_headers = get_value(add_custom_headers, Opts, []),
  1575. Headers_1 = Headers ++ Custom_headers,
  1576. Raw_headers_1 = case Custom_headers of
  1577. [_ | _] when is_binary(Raw_headers) ->
  1578. Custom_headers_bin = list_to_binary(string:join([[X, $:, Y] || {X, Y} <- Custom_headers], "\r\n")),
  1579. <<Raw_headers/binary, "\r\n", Custom_headers_bin/binary>>;
  1580. _ ->
  1581. Raw_headers
  1582. end,
  1583. {Headers_1, Raw_headers_1}.
  1584. format_response_data(Resp_format, Body) ->
  1585. case Resp_format of
  1586. list when is_list(Body) ->
  1587. flatten(Body);
  1588. list when is_binary(Body) ->
  1589. binary_to_list(Body);
  1590. binary when is_list(Body) ->
  1591. list_to_binary(Body);
  1592. _ ->
  1593. %% This is to cater for sending messages such as
  1594. %% {chunk_start, _}, chunk_end etc
  1595. Body
  1596. end.
  1597. do_reply(State, From, undefined, _, Resp_format, {ok, St_code, Headers, Body}) ->
  1598. Msg_1 = {ok, St_code, Headers, format_response_data(Resp_format, Body)},
  1599. gen_server:reply(From, Msg_1),
  1600. dec_pipeline_counter(State);
  1601. do_reply(State, From, undefined, _, _, Msg) ->
  1602. gen_server:reply(From, Msg),
  1603. dec_pipeline_counter(State);
  1604. do_reply(#state{prev_req_id = Prev_req_id} = State,
  1605. _From, StreamTo, ReqId, Resp_format, {ok, _, _, Body}) ->
  1606. State_1 = dec_pipeline_counter(State),
  1607. case Body of
  1608. [] ->
  1609. ok;
  1610. _ ->
  1611. Body_1 = format_response_data(Resp_format, Body),
  1612. catch StreamTo ! {ibrowse_async_response, ReqId, Body_1}
  1613. end,
  1614. catch StreamTo ! {ibrowse_async_response_end, ReqId},
  1615. %% We don't want to delete the Req-id to Pid mapping straightaway
  1616. %% as the client may send a stream_next message just while we are
  1617. %% sending back this ibrowse_async_response_end message. If we
  1618. %% deleted this mapping straightaway, the caller will see a
  1619. %% {error, unknown_req_id} when it calls ibrowse:stream_next/1. To
  1620. %% get around this, we store the req id, and clear it after the
  1621. %% next request. If there are wierd combinations of stream,
  1622. %% stream_once and sync requests on the same connection, it will
  1623. %% take a while for the req_id-pid mapping to get cleared, but it
  1624. %% should do no harm.
  1625. ets:delete(ibrowse_stream, {req_id_pid, Prev_req_id}),
  1626. State_1#state{prev_req_id = ReqId};
  1627. do_reply(State, _From, StreamTo, ReqId, Resp_format, Msg) ->
  1628. State_1 = dec_pipeline_counter(State),
  1629. Msg_1 = format_response_data(Resp_format, Msg),
  1630. catch StreamTo ! {ibrowse_async_response, ReqId, Msg_1},
  1631. State_1.
  1632. do_interim_reply(undefined, _, _ReqId, _Msg) ->
  1633. ok;
  1634. do_interim_reply(StreamTo, Response_format, ReqId, Msg) ->
  1635. Msg_1 = format_response_data(Response_format, Msg),
  1636. catch StreamTo ! {ibrowse_async_response, ReqId, Msg_1}.
  1637. do_error_reply(#state{reqs = Reqs, tunnel_setup_queue = Tun_q} = State, Err) ->
  1638. ReqList = queue:to_list(Reqs),
  1639. lists:foreach(fun(#request{from=From, stream_to=StreamTo, req_id=ReqId,
  1640. response_format = Resp_format}) ->
  1641. ets:delete(ibrowse_stream, {req_id_pid, ReqId}),
  1642. do_reply(State, From, StreamTo, ReqId, Resp_format, {error, Err})
  1643. end, ReqList),
  1644. lists:foreach(
  1645. fun({From, _Url, _Headers, _Method, _Body, _Options, _Timeout}) ->
  1646. do_reply(State, From, undefined, undefined, undefined, Err)
  1647. end, Tun_q).
  1648. fail_pipelined_requests(#state{reqs = Reqs, cur_req = CurReq} = State, Reply) ->
  1649. {_, Reqs_1} = queue:out(Reqs),
  1650. #request{from=From, stream_to=StreamTo, req_id=ReqId,
  1651. response_format = Resp_format} = CurReq,
  1652. State_1 = do_reply(State, From, StreamTo, ReqId, Resp_format, Reply),
  1653. do_error_reply(State_1#state{reqs = Reqs_1}, previous_request_failed).
  1654. split_list_at(List, N) ->
  1655. split_list_at(List, N, []).
  1656. split_list_at([], _, Acc) ->
  1657. {lists:reverse(Acc), []};
  1658. split_list_at(List2, 0, List1) ->
  1659. {lists:reverse(List1), List2};
  1660. split_list_at([H | List2], N, List1) ->
  1661. split_list_at(List2, N-1, [H | List1]).
  1662. hexlist_to_integer(List) ->
  1663. hexlist_to_integer(lists:reverse(List), 1, 0).
  1664. hexlist_to_integer([H | T], Multiplier, Acc) ->
  1665. hexlist_to_integer(T, Multiplier*16, Multiplier*to_ascii(H) + Acc);
  1666. hexlist_to_integer([], _, Acc) ->
  1667. Acc.
  1668. to_ascii($A) -> 10;
  1669. to_ascii($a) -> 10;
  1670. to_ascii($B) -> 11;
  1671. to_ascii($b) -> 11;
  1672. to_ascii($C) -> 12;
  1673. to_ascii($c) -> 12;
  1674. to_ascii($D) -> 13;
  1675. to_ascii($d) -> 13;
  1676. to_ascii($E) -> 14;
  1677. to_ascii($e) -> 14;
  1678. to_ascii($F) -> 15;
  1679. to_ascii($f) -> 15;
  1680. to_ascii($1) -> 1;
  1681. to_ascii($2) -> 2;
  1682. to_ascii($3) -> 3;
  1683. to_ascii($4) -> 4;
  1684. to_ascii($5) -> 5;
  1685. to_ascii($6) -> 6;
  1686. to_ascii($7) -> 7;
  1687. to_ascii($8) -> 8;
  1688. to_ascii($9) -> 9;
  1689. to_ascii($0) -> 0.
  1690. cancel_timer(undefined) -> ok;
  1691. cancel_timer(Ref) -> _ = erlang:cancel_timer(Ref),
  1692. ok.
  1693. cancel_timer(Ref, {eat_message, Msg}) ->
  1694. cancel_timer(Ref),
  1695. receive
  1696. Msg ->
  1697. ok
  1698. after 0 ->
  1699. ok
  1700. end.
  1701. make_req_id() ->
  1702. now().
  1703. to_lower(Str) ->
  1704. to_lower(Str, []).
  1705. to_lower([H|T], Acc) when H >= $A, H =< $Z ->
  1706. to_lower(T, [H+32|Acc]);
  1707. to_lower([H|T], Acc) ->
  1708. to_lower(T, [H|Acc]);
  1709. to_lower([], Acc) ->
  1710. lists:reverse(Acc).
  1711. shutting_down(#state{lb_ets_tid = undefined}) ->
  1712. ok;
  1713. shutting_down(#state{lb_ets_tid = Tid,
  1714. cur_pipeline_size = _Sz}) ->
  1715. catch ets:delete(Tid, self()).
  1716. inc_pipeline_counter(#state{is_closing = true} = State) ->
  1717. State;
  1718. inc_pipeline_counter(#state{lb_ets_tid = undefined} = State) ->
  1719. State;
  1720. inc_pipeline_counter(#state{cur_pipeline_size = Pipe_sz,
  1721. lb_ets_tid = Tid} = State) ->
  1722. update_counter(Tid, self(), {2,1,99999,9999}),
  1723. State#state{cur_pipeline_size = Pipe_sz + 1}.
  1724. update_counter(Tid, Key, Args) ->
  1725. ets:update_counter(Tid, Key, Args).
  1726. dec_pipeline_counter(#state{is_closing = true} = State) ->
  1727. State;
  1728. dec_pipeline_counter(#state{lb_ets_tid = undefined} = State) ->
  1729. State;
  1730. dec_pipeline_counter(#state{cur_pipeline_size = Pipe_sz,
  1731. lb_ets_tid = Tid} = State) ->
  1732. try
  1733. update_counter(Tid, self(), {2,-1,0,0}),
  1734. update_counter(Tid, self(), {3,-1,0,0})
  1735. catch
  1736. _:_ ->
  1737. ok
  1738. end,
  1739. State#state{cur_pipeline_size = Pipe_sz - 1}.
  1740. flatten([H | _] = L) when is_integer(H) ->
  1741. L;
  1742. flatten([H | _] = L) when is_list(H) ->
  1743. lists:flatten(L);
  1744. flatten([]) ->
  1745. [].
  1746. get_stream_chunk_size(Options) ->
  1747. case lists:keysearch(stream_chunk_size, 1, Options) of
  1748. {value, {_, V}} when V > 0 ->
  1749. V;
  1750. _ ->
  1751. ?DEFAULT_STREAM_CHUNK_SIZE
  1752. end.
  1753. set_inac_timer(State) ->
  1754. cancel_timer(State#state.inactivity_timer_ref),
  1755. set_inac_timer(State#state{inactivity_timer_ref = undefined},
  1756. get_inac_timeout(State)).
  1757. set_inac_timer(State, Timeout) when is_integer(Timeout) ->
  1758. Ref = erlang:send_after(Timeout, self(), timeout),
  1759. State#state{inactivity_timer_ref = Ref};
  1760. set_inac_timer(State, _) ->
  1761. State.
  1762. get_inac_timeout(#state{cur_req = #request{options = Opts}}) ->
  1763. get_value(inactivity_timeout, Opts, infinity);
  1764. get_inac_timeout(#state{cur_req = undefined}) ->
  1765. case ibrowse:get_config_value(inactivity_timeout, undefined) of
  1766. Val when is_integer(Val) ->
  1767. Val;
  1768. _ ->
  1769. case application:get_env(ibrowse, inactivity_timeout) of
  1770. {ok, Val} when is_integer(Val), Val > 0 ->
  1771. Val;
  1772. _ ->
  1773. 10000
  1774. end
  1775. end.
  1776. trace_request(Req) ->
  1777. case get(my_trace_flag) of
  1778. true ->
  1779. %%Avoid the binary operations if trace is not on...
  1780. NReq = to_binary(Req),
  1781. do_trace("Sending request: ~n"
  1782. "--- Request Begin ---~n~s~n"
  1783. "--- Request End ---~n", [NReq]);
  1784. _ -> ok
  1785. end.
  1786. trace_request_body(Body) ->
  1787. case get(my_trace_flag) of
  1788. true ->
  1789. %%Avoid the binary operations if trace is not on...
  1790. NBody = to_binary(Body),
  1791. case size(NBody) > 1024 of
  1792. true ->
  1793. ok;
  1794. false ->
  1795. do_trace("Sending request body: ~n"
  1796. "--- Request Body Begin ---~n~s~n"
  1797. "--- Request Body End ---~n", [NBody])
  1798. end;
  1799. false ->
  1800. ok
  1801. end.
  1802. to_binary(X) when is_list(X) -> list_to_binary(X);
  1803. to_binary(X) when is_binary(X) -> X.