Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

1249 строки
42 KiB

20 лет назад
  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. -vsn('$Id: ibrowse_http_client.erl,v 1.1 2005/05/05 22:28:28 chandrusf Exp $ ').
  10. -behaviour(gen_server).
  11. %%--------------------------------------------------------------------
  12. %% Include files
  13. %%--------------------------------------------------------------------
  14. %%--------------------------------------------------------------------
  15. %% External exports
  16. -export([start_link/1]).
  17. -ifdef(debug).
  18. -compile(export_all).
  19. -endif.
  20. %% gen_server callbacks
  21. -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
  22. -export([parse_url/1,
  23. printable_date/0]).
  24. -include("ibrowse.hrl").
  25. -record(state, {host, port, use_proxy = false, proxy_auth_digest,
  26. ssl_options=[], is_ssl, socket,
  27. reqs=queue:new(), cur_req, status=idle, http_status_code,
  28. reply_buffer=[], rep_buf_size=0, recvd_headers=[],
  29. is_closing, send_timer, content_length,
  30. deleted_crlf = false, transfer_encoding, chunk_size,
  31. chunks=[], save_response_to_file = false,
  32. tmp_file_name, tmp_file_fd}).
  33. -record(request, {url, method, options, from,
  34. stream_to, req_id}).
  35. %%====================================================================
  36. %% External functions
  37. %%====================================================================
  38. %%--------------------------------------------------------------------
  39. %% Function: start_link/0
  40. %% Description: Starts the server
  41. %%--------------------------------------------------------------------
  42. start_link(Args) ->
  43. gen_server:start_link(?MODULE, Args, []).
  44. %%====================================================================
  45. %% Server functions
  46. %%====================================================================
  47. %%--------------------------------------------------------------------
  48. %% Function: init/1
  49. %% Description: Initiates the server
  50. %% Returns: {ok, State} |
  51. %% {ok, State, Timeout} |
  52. %% ignore |
  53. %% {stop, Reason}
  54. %%--------------------------------------------------------------------
  55. init([Host, Port, Trace, Options]) ->
  56. {SSLOptions, IsSSL} = case get_value(is_ssl, Options, false) of
  57. false -> {[], false};
  58. true -> {get_value(ssl_options, Options), true}
  59. end,
  60. State = #state{host=Host, port=Port, is_ssl=IsSSL, ssl_options=SSLOptions},
  61. put(ibrowse_http_client_host, Host),
  62. put(ibrowse_http_client_port, Port),
  63. put(my_trace_flag, Trace),
  64. {ok, State}.
  65. %%--------------------------------------------------------------------
  66. %% Function: handle_call/3
  67. %% Description: Handling call messages
  68. %% Returns: {reply, Reply, State} |
  69. %% {reply, Reply, State, Timeout} |
  70. %% {noreply, State} |
  71. %% {noreply, State, Timeout} |
  72. %% {stop, Reason, Reply, State} | (terminate/2 is called)
  73. %% {stop, Reason, State} (terminate/2 is called)
  74. %%--------------------------------------------------------------------
  75. handle_call(_Request, _From, State) ->
  76. Reply = ok,
  77. {reply, Reply, State}.
  78. %%--------------------------------------------------------------------
  79. %% Function: handle_cast/2
  80. %% Description: Handling cast messages
  81. %% Returns: {noreply, State} |
  82. %% {noreply, State, Timeout} |
  83. %% {stop, Reason, State} (terminate/2 is called)
  84. %%--------------------------------------------------------------------
  85. handle_cast(_Msg, State) ->
  86. {noreply, State}.
  87. %%--------------------------------------------------------------------
  88. %% Function: handle_info/2
  89. %% Description: Handling all non call/cast messages
  90. %% Returns: {noreply, State} |
  91. %% {noreply, State, Timeout} |
  92. %% {stop, Reason, State} (terminate/2 is called)
  93. %%--------------------------------------------------------------------
  94. %% Received a request when the remote server has already sent us a
  95. %% Connection: Close header
  96. handle_info({{send_req, Req}, From}, #state{is_closing=true}=State) ->
  97. ibrowse ! {conn_closing, self(), {send_req, Req}, From},
  98. {noreply, State};
  99. %% First request when no connection exists.
  100. handle_info({{send_req, [Url, Headers, Method,
  101. Body, Options, Timeout]}, From},
  102. #state{socket=undefined,
  103. host=Host, port=Port}=State) ->
  104. State_1 = case get_value(proxy_host, Options, false) of
  105. false ->
  106. State;
  107. _PHost ->
  108. ProxyUser = get_value(proxy_user, Options),
  109. ProxyPassword = get_value(proxy_password, Options),
  110. SaveResponseToFile = get_value(save_response_to_file, Options, false),
  111. Digest = http_auth_digest(ProxyUser, ProxyPassword),
  112. State#state{use_proxy = true,
  113. save_response_to_file = SaveResponseToFile,
  114. proxy_auth_digest = Digest}
  115. end,
  116. StreamTo = get_value(stream_to, Options, undefined),
  117. ReqId = make_req_id(),
  118. NewReq = #request{url=Url,
  119. method=Method,
  120. stream_to=StreamTo,
  121. options=Options,
  122. req_id=ReqId,
  123. from=From},
  124. Reqs = queue:in(NewReq, State#state.reqs),
  125. State_2 = check_ssl_options(Options, State_1#state{reqs = Reqs}),
  126. do_trace("Connecting...~n", []),
  127. Timeout_1 = case Timeout of
  128. infinity ->
  129. infinity;
  130. _ ->
  131. round(Timeout*0.9)
  132. end,
  133. case do_connect(Host, Port, Options, State_2, Timeout_1) of
  134. {ok, Sock} ->
  135. Ref = case Timeout of
  136. infinity ->
  137. undefined;
  138. _ ->
  139. erlang:send_after(Timeout, self(), {req_timedout, From})
  140. end,
  141. do_trace("Connected!~n", []),
  142. case send_req_1(Url, Headers, Method, Body, Options, Sock, State_2) of
  143. ok ->
  144. case StreamTo of
  145. undefined ->
  146. ok;
  147. _ ->
  148. gen_server:reply(From, {ibrowse_req_id, ReqId})
  149. end,
  150. {noreply, State_2#state{socket=Sock,
  151. send_timer = Ref,
  152. cur_req = NewReq,
  153. status=get_header}};
  154. Err ->
  155. do_trace("Send failed... Reason: ~p~n", [Err]),
  156. ibrowse:shutting_down(),
  157. ibrowse:reply(From, {error, send_failed}),
  158. {stop, normal, State_2}
  159. end;
  160. Err ->
  161. do_trace("Error connecting. Reason: ~1000.p~n", [Err]),
  162. ibrowse:shutting_down(),
  163. ibrowse:reply(From, {error, conn_failed}),
  164. {stop, normal, State_2}
  165. end;
  166. %% Request which is to be pipelined
  167. handle_info({{send_req, [Url, Headers, Method,
  168. Body, Options, Timeout]}, From},
  169. #state{socket=Sock, status=Status, reqs=Reqs}=State) ->
  170. do_trace("Recvd request in connected state. Status -> ~p NumPending: ~p~n", [Status, length(queue:to_list(Reqs))]),
  171. StreamTo = get_value(stream_to, Options, undefined),
  172. ReqId = make_req_id(),
  173. NewReq = #request{url=Url,
  174. stream_to=StreamTo,
  175. method=Method,
  176. options=Options,
  177. req_id=ReqId,
  178. from=From},
  179. State_1 = State#state{reqs=queue:in(NewReq, State#state.reqs)},
  180. case send_req_1(Url, Headers, Method, Body, Options, Sock, State_1) of
  181. ok ->
  182. do_setopts(Sock, [{active, true}], State#state.is_ssl),
  183. case Timeout of
  184. infinity ->
  185. ok;
  186. _ ->
  187. erlang:send_after(Timeout, self(), {req_timedout, From})
  188. end,
  189. State_2 = case Status of
  190. idle ->
  191. State_1#state{status=get_header,
  192. cur_req=NewReq};
  193. _ ->
  194. State_1
  195. end,
  196. case StreamTo of
  197. undefined ->
  198. ok;
  199. _ ->
  200. %% We don't use ibrowse:reply here because we are
  201. %% just sending back the request ID. Not the
  202. %% response
  203. gen_server:reply(From, {ibrowse_req_id, ReqId})
  204. end,
  205. {noreply, State_2};
  206. Err ->
  207. do_trace("Send request failed: Reason: ~p~n", [Err]),
  208. ibrowse:reply(From, {error, send_failed}),
  209. do_error_reply(State, send_failed),
  210. ibrowse:shutting_down(),
  211. {stop, normal, State_1}
  212. end;
  213. handle_info({tcp, _Sock, Data}, State) ->
  214. handle_sock_data(Data, State);
  215. handle_info({ssl, _Sock, Data}, State) ->
  216. handle_sock_data(Data, State);
  217. handle_info({tcp_closed, _Sock}, State) ->
  218. do_trace("TCP connection closed by peer!~n", []),
  219. handle_sock_closed(State),
  220. {stop, normal, State};
  221. handle_info({ssl_closed, _Sock}, State) ->
  222. do_trace("SSL connection closed by peer!~n", []),
  223. handle_sock_closed(State),
  224. {stop, normal, State};
  225. handle_info({req_timedout, From}, State) ->
  226. case lists:keysearch(From, #request.from, queue:to_list(State#state.reqs)) of
  227. false ->
  228. {noreply, State};
  229. {value, _} ->
  230. ibrowse:shutting_down(),
  231. do_error_reply(State, req_timedout),
  232. {stop, normal, State}
  233. end;
  234. handle_info({trace, Bool}, State) ->
  235. do_trace("Turning trace on: Host: ~p Port: ~p~n", [State#state.host, State#state.port]),
  236. put(my_trace_flag, Bool),
  237. {noreply, State};
  238. handle_info(Info, State) ->
  239. io:format("Recvd unknown message ~p when in state: ~p~n", [Info, State]),
  240. {noreply, State}.
  241. %%--------------------------------------------------------------------
  242. %% Function: terminate/2
  243. %% Description: Shutdown the server
  244. %% Returns: any (ignored by gen_server)
  245. %%--------------------------------------------------------------------
  246. terminate(_Reason, State) ->
  247. case State#state.socket of
  248. undefined ->
  249. ok;
  250. Sock ->
  251. do_close(Sock, State#state.is_ssl)
  252. end.
  253. %%--------------------------------------------------------------------
  254. %% Func: code_change/3
  255. %% Purpose: Convert process state when code is changed
  256. %% Returns: {ok, NewState}
  257. %%--------------------------------------------------------------------
  258. code_change(_OldVsn, State, _Extra) ->
  259. {ok, State}.
  260. %%--------------------------------------------------------------------
  261. %%% Internal functions
  262. %%--------------------------------------------------------------------
  263. %%--------------------------------------------------------------------
  264. %% Handles data recvd on the socket
  265. %%--------------------------------------------------------------------
  266. handle_sock_data(Data, #state{status=idle}=State) ->
  267. do_trace("Data recvd on socket in state idle!. ~1000.p~n", [Data]),
  268. ibrowse:shutting_down(),
  269. do_error_reply(State, data_in_status_idle),
  270. do_close(State#state.socket, State#state.is_ssl),
  271. {stop, normal, State};
  272. handle_sock_data(Data, #state{status=get_header, socket=Sock}=State) ->
  273. case parse_response(Data, State) of
  274. {error, _Reason} ->
  275. ibrowse:shutting_down(),
  276. {stop, normal, State};
  277. stop ->
  278. ibrowse:shutting_down(),
  279. {stop, normal, State};
  280. State_1 ->
  281. do_setopts(Sock, [{active, true}], State#state.is_ssl),
  282. {noreply, State_1}
  283. end;
  284. handle_sock_data(Data, #state{status=get_body, content_length=CL,
  285. recvd_headers=Headers, cur_req=CurReq,
  286. chunk_size=CSz, reqs=Reqs, socket=Sock}=State) ->
  287. case (CL == undefined) and (CSz == undefined) of
  288. true ->
  289. case accumulate_response(Data, State) of
  290. {error, Reason} ->
  291. ibrowse:shutting_down(),
  292. {_, Reqs_1} = queue:out(Reqs),
  293. % {{value, Req}, Reqs_1} = queue:out(Reqs),
  294. #request{from=From, stream_to=StreamTo, req_id=ReqId} = CurReq,
  295. do_reply(From, StreamTo, ReqId,
  296. {error, {file_open_error, Reason, Headers}}),
  297. do_error_reply(State#state{reqs=Reqs_1}, previous_request_failed),
  298. {stop, normal, State};
  299. State_1 ->
  300. do_setopts(Sock, [{active, true}], State#state.is_ssl),
  301. {noreply, State_1}
  302. end;
  303. _ ->
  304. case parse_11_response(Data, State) of
  305. {error, _Reason} ->
  306. ibrowse:shutting_down(),
  307. {stop, normal, State};
  308. stop ->
  309. ibrowse:shutting_down(),
  310. {stop, normal, State};
  311. State_1 ->
  312. do_setopts(Sock, [{active, true}], State#state.is_ssl),
  313. {noreply, State_1}
  314. end
  315. end.
  316. accumulate_response(Data, #state{save_response_to_file = true,
  317. tmp_file_fd = undefined,
  318. http_status_code=[$2 | _]}=State) ->
  319. TmpFilename = make_tmp_filename(),
  320. case file:open(TmpFilename, [write, delayed_write, raw]) of
  321. {ok, Fd} ->
  322. accumulate_response(Data, State#state{tmp_file_fd=Fd,
  323. tmp_file_name=TmpFilename});
  324. {error, Reason} ->
  325. {error, {file_open_error, Reason}}
  326. end;
  327. accumulate_response(Data, #state{save_response_to_file=true,
  328. transfer_encoding=chunked,
  329. chunks = Chunks,
  330. http_status_code=[$2 | _],
  331. tmp_file_fd=Fd}=State) ->
  332. case file:write(Fd, [Chunks | Data]) of
  333. ok ->
  334. State#state{chunks = []};
  335. {error, Reason} ->
  336. {error, {file_write_error, Reason}}
  337. end;
  338. accumulate_response(Data, #state{save_response_to_file=true,
  339. reply_buffer = RepBuf,
  340. http_status_code=[$2 | _],
  341. tmp_file_fd=Fd}=State) ->
  342. case file:write(Fd, [RepBuf | Data]) of
  343. ok ->
  344. State#state{reply_buffer = []};
  345. {error, Reason} ->
  346. {error, {file_write_error, Reason}}
  347. end;
  348. accumulate_response([], State) ->
  349. State;
  350. accumulate_response(Data, #state{reply_buffer=RepBuf,
  351. cur_req=CurReq}=State) ->
  352. #request{stream_to=StreamTo, req_id=ReqId} = CurReq,
  353. case StreamTo of
  354. undefined ->
  355. State#state{reply_buffer = [Data | RepBuf]};
  356. _ ->
  357. do_interim_reply(StreamTo, ReqId, Data),
  358. State
  359. end.
  360. make_tmp_filename() ->
  361. DownloadDir = safe_get_env(ibrowse, download_dir, filename:absname("./")),
  362. {A,B,C} = now(),
  363. filename:join([DownloadDir,
  364. "ibrowse_tmp_file_"++
  365. integer_to_list(A) ++
  366. integer_to_list(B) ++
  367. integer_to_list(C)]).
  368. %%--------------------------------------------------------------------
  369. %% Handles the case when the server closes the socket
  370. %%--------------------------------------------------------------------
  371. handle_sock_closed(#state{status=get_header}=State) ->
  372. ibrowse:shutting_down(),
  373. do_error_reply(State, connection_closed);
  374. handle_sock_closed(#state{cur_req=undefined}) ->
  375. ibrowse:shutting_down();
  376. %% We check for IsClosing because this the server could have sent a
  377. %% Connection-Close header and has closed the socket to indicate end
  378. %% of response. There maybe requests pipelined which need a response.
  379. handle_sock_closed(#state{reply_buffer=Buf, reqs=Reqs, http_status_code=SC,
  380. is_closing=IsClosing, cur_req=CurReq,
  381. status=get_body, recvd_headers=Headers}=State) ->
  382. #request{from=From, stream_to=StreamTo, req_id=ReqId} = CurReq,
  383. case IsClosing of
  384. true ->
  385. {_, Reqs_1} = queue:out(Reqs),
  386. % {{value, Req}, Reqs_1} = queue:out(Reqs),
  387. do_reply(From, StreamTo, ReqId, {ok, SC, Headers, lists:flatten(lists:reverse(Buf))}),
  388. do_error_reply(State#state{reqs = Reqs_1}, connection_closed);
  389. _ ->
  390. do_error_reply(State, connection_closed)
  391. end.
  392. do_connect(Host, Port, _Options, #state{is_ssl=true, ssl_options=SSLOptions}, Timeout) ->
  393. ssl:connect(Host, Port, [{active, false} | SSLOptions], Timeout);
  394. do_connect(Host, Port, _Options, _State, Timeout) ->
  395. gen_tcp:connect(Host, Port, [{active, false}], Timeout).
  396. do_send(Sock, Req, true) -> ssl:send(Sock, Req);
  397. do_send(Sock, Req, false) -> gen_tcp:send(Sock, Req).
  398. do_close(Sock, true) -> ssl:close(Sock);
  399. do_close(Sock, false) -> gen_tcp:close(Sock).
  400. do_setopts(Sock, Opts, true) -> ssl:setopts(Sock, Opts);
  401. do_setopts(Sock, Opts, false) -> inet:setopts(Sock, Opts).
  402. check_ssl_options(Options, State) ->
  403. case get_value(is_ssl, Options, false) of
  404. false ->
  405. State;
  406. true ->
  407. State#state{is_ssl=true, ssl_options=get_value(ssl_options, Options)}
  408. end.
  409. send_req_1(Url, Headers, Method, Body, Options, Sock, State) ->
  410. #url{abspath = AbsPath,
  411. host = Host,
  412. port = Port,
  413. path = RelPath} = Url_1 = parse_url(Url),
  414. Headers_1 = add_auth_headers(Url_1, Options, Headers, State),
  415. Req = make_request(Method,
  416. [{"Host", [Host, ":", integer_to_list(Port)]} | Headers_1],
  417. AbsPath, RelPath, Body, Options, State#state.use_proxy),
  418. case get(my_trace_flag) of %%Avoid the binary operations if trace is not on...
  419. true ->
  420. NReq = binary_to_list(list_to_binary(Req)),
  421. do_trace("Sending request: ~n"
  422. "--- Request Begin ---~n~s~n"
  423. "--- Request End ---~n", [NReq]);
  424. _ -> ok
  425. end,
  426. SndRes = do_send(Sock, Req, State#state.is_ssl),
  427. do_setopts(Sock, [{active, true}], State#state.is_ssl),
  428. SndRes.
  429. add_auth_headers(#url{username = User,
  430. password = UPw},
  431. Options,
  432. Headers,
  433. #state{use_proxy = UseProxy,
  434. proxy_auth_digest = ProxyAuthDigest}) ->
  435. Headers_1 = case User of
  436. undefined ->
  437. case get_value(basic_auth, Options, undefined) of
  438. undefined ->
  439. Headers;
  440. {U,P} ->
  441. [{"Authorization", ["Basic ", http_auth_digest(U, P)]} | Headers]
  442. end;
  443. _ ->
  444. [{"Authorization", ["Basic ", http_auth_digest(User, UPw)]} | Headers]
  445. end,
  446. case UseProxy of
  447. false ->
  448. Headers_1;
  449. true ->
  450. [{"Proxy-Authorization", ["Basic ", ProxyAuthDigest]} | Headers_1]
  451. end.
  452. http_auth_digest(Username, Password) ->
  453. encode_base64(Username ++ [$: | Password]).
  454. encode_base64([]) ->
  455. [];
  456. encode_base64([A]) ->
  457. [e(A bsr 2), e((A band 3) bsl 4), $=, $=];
  458. encode_base64([A,B]) ->
  459. [e(A bsr 2), e(((A band 3) bsl 4) bor (B bsr 4)), e((B band 15) bsl 2), $=];
  460. encode_base64([A,B,C|Ls]) ->
  461. encode_base64_do(A,B,C, Ls).
  462. encode_base64_do(A,B,C, Rest) ->
  463. BB = (A bsl 16) bor (B bsl 8) bor C,
  464. [e(BB bsr 18), e((BB bsr 12) band 63),
  465. e((BB bsr 6) band 63), e(BB band 63)|encode_base64(Rest)].
  466. e(X) when X >= 0, X < 26 -> X+65;
  467. e(X) when X>25, X<52 -> X+71;
  468. e(X) when X>51, X<62 -> X-4;
  469. e(62) -> $+;
  470. e(63) -> $/;
  471. e(X) -> exit({bad_encode_base64_token, X}).
  472. make_request(Method, Headers, AbsPath, RelPath, Body, Options, UseProxy) ->
  473. HttpVsn = http_vsn_string(get_value(http_vsn, Options, {1,1})),
  474. Headers_1 = case get_value(content_length, Headers, false) of
  475. false when (Body == []) or (Body == <<>>) ->
  476. Headers;
  477. false when is_binary(Body) ->
  478. [{"content-length", integer_to_list(size(Body))} | Headers];
  479. false ->
  480. [{"content-length", integer_to_list(length(Body))} | Headers];
  481. true ->
  482. Headers
  483. end,
  484. Headers_2 = cons_headers(Headers_1),
  485. Uri = case get_value(use_absolute_uri, Options, false) or UseProxy of
  486. true ->
  487. AbsPath;
  488. false ->
  489. RelPath
  490. end,
  491. [method(Method), " ", Uri, " ", HttpVsn, crnl(), Headers_2, crnl(), Body].
  492. http_vsn_string({0,9}) -> "HTTP/0.9";
  493. http_vsn_string({1,0}) -> "HTTP/1.0";
  494. http_vsn_string({1,1}) -> "HTTP/1.1".
  495. cons_headers(Headers) ->
  496. cons_headers(Headers, []).
  497. cons_headers([], Acc) ->
  498. encode_headers(Acc);
  499. cons_headers([{basic_auth, {U,P}} | T], Acc) ->
  500. cons_headers(T, [{"Authorization", ["Basic ", httpd_util:encode_base64(U++":"++P)]} | Acc]);
  501. cons_headers([{cookie, Cookie} | T], Acc) ->
  502. cons_headers(T, [{"Cookie", Cookie} | Acc]);
  503. cons_headers([{content_length, L} | T], Acc) ->
  504. cons_headers(T, [{"Content-Length", L} | Acc]);
  505. cons_headers([{content_type, L} | T], Acc) ->
  506. cons_headers(T, [{"Content-Type", L} | Acc]);
  507. cons_headers([H | T], Acc) ->
  508. cons_headers(T, [H | Acc]).
  509. encode_headers(L) ->
  510. encode_headers(L, []).
  511. encode_headers([{http_vsn, _Val} | T], Acc) ->
  512. encode_headers(T, Acc);
  513. encode_headers([{Name,Val} | T], Acc) when list(Name) ->
  514. encode_headers(T, [[Name, ": ", fmt_val(Val), crnl()] | Acc]);
  515. encode_headers([{Name,Val} | T], Acc) when atom(Name) ->
  516. encode_headers(T, [[atom_to_list(Name), ": ", fmt_val(Val), crnl()] | Acc]);
  517. encode_headers([], Acc) ->
  518. lists:reverse(Acc).
  519. parse_response(_Data, #state{cur_req = undefined}=State) ->
  520. State#state{status = idle};
  521. parse_response(Data, #state{reply_buffer=Acc, reqs=Reqs,
  522. cur_req=CurReq}=State) ->
  523. #request{from=From, stream_to=StreamTo, req_id=ReqId} = CurReq,
  524. MaxHeaderSize = safe_get_env(ibrowse, max_headers_size, infinity),
  525. case scan_header(Data, Acc) of
  526. {yes, Headers, Data_1} ->
  527. do_trace("Recvd Header Data -> ~s~n----~n", [Headers]),
  528. do_trace("Recvd headers~n--- Headers Begin ---~n~s~n--- Headers End ---~n~n", [Headers]),
  529. {HttpVsn, StatCode, Headers_1} = parse_headers(Headers),
  530. do_trace("HttpVsn: ~p StatusCode: ~p Headers_1 -> ~1000.p~n", [HttpVsn, StatCode, Headers_1]),
  531. LCHeaders = [{to_lower(X), Y} || {X,Y} <- Headers_1],
  532. ConnClose = to_lower(get_value("connection", LCHeaders, "false")),
  533. IsClosing = is_connection_closing(HttpVsn, ConnClose),
  534. case IsClosing of
  535. true ->
  536. ibrowse:shutting_down();
  537. false ->
  538. ok
  539. end,
  540. [#request{options = CurReqOptions,
  541. method=Method} | _] = queue:to_list(Reqs),
  542. SaveResponseToFile = get_value(save_response_to_file, CurReqOptions, false),
  543. State_1 = State#state{recvd_headers=Headers_1, status=get_body,
  544. save_response_to_file = SaveResponseToFile,
  545. tmp_file_fd = undefined, tmp_file_name = undefined,
  546. http_status_code=StatCode, is_closing=IsClosing},
  547. put(conn_close, ConnClose),
  548. TransferEncoding = to_lower(get_value("transfer-encoding", LCHeaders, "false")),
  549. case get_value("content-length", LCHeaders, undefined) of
  550. _ when Method == head ->
  551. {_, Reqs_1} = queue:out(Reqs),
  552. % {{value, Req}, Reqs_1} = queue:out(Reqs),
  553. send_async_headers(ReqId, StreamTo, StatCode, Headers_1),
  554. do_reply(From, StreamTo, ReqId, {ok, StatCode, Headers_1, []}),
  555. % ibrowse:reply(From, {ok, StatCode, Headers_1, []}),
  556. cancel_timer(State#state.send_timer, {eat_message, {req_timedout, From}}),
  557. State_2 = reset_state(State_1),
  558. parse_response(Data_1, State_2#state{reqs=Reqs_1});
  559. _ when hd(StatCode) == $1 ->
  560. %% No message body is expected. Server may send
  561. %% one or more 1XX responses before a proper
  562. %% response.
  563. send_async_headers(ReqId, StreamTo, StatCode, Headers_1),
  564. do_trace("Recvd a status code of ~p. Ignoring and waiting for a proper response~n", [StatCode]),
  565. parse_response(Data_1, State_1);
  566. _ when StatCode == "204";
  567. StatCode == "304" ->
  568. %% No message body is expected for these Status Codes.
  569. %% RFC2616 - Sec 4.4
  570. {_, Reqs_1} = queue:out(Reqs),
  571. % {{value, Req}, Reqs_1} = queue:out(Reqs),
  572. send_async_headers(ReqId, StreamTo, StatCode, Headers_1),
  573. do_reply(From, StreamTo, ReqId, {ok, StatCode, Headers_1, []}),
  574. % ibrowse:reply(From, {ok, StatCode, Headers_1, []}),
  575. cancel_timer(State#state.send_timer, {eat_message, {req_timedout, From}}),
  576. State_2 = reset_state(State_1),
  577. parse_response(Data_1, State_2#state{reqs=Reqs_1});
  578. _ when TransferEncoding == "chunked" ->
  579. do_trace("Chunked encoding detected...~n",[]),
  580. send_async_headers(ReqId, StreamTo, StatCode, Headers_1),
  581. parse_11_response(Data_1, State_1#state{transfer_encoding=chunked,
  582. chunk_size=chunk_start,
  583. reply_buffer=[], chunks=[]});
  584. undefined when HttpVsn == "HTTP/1.0";
  585. ConnClose == "close" ->
  586. send_async_headers(ReqId, StreamTo, StatCode, Headers_1),
  587. State_1#state{reply_buffer=[Data_1]};
  588. undefined ->
  589. {_, Reqs_1} = queue:out(Reqs),
  590. % {{value, Req}, Reqs_1} = queue:out(Reqs),
  591. do_reply(From, StreamTo, ReqId,
  592. {error, {content_length_undefined, Headers}}),
  593. do_error_reply(State_1#state{reqs=Reqs_1}, previous_request_failed),
  594. {error, content_length_undefined};
  595. V ->
  596. case catch list_to_integer(V) of
  597. V_1 when integer(V_1), V_1 >= 0 ->
  598. send_async_headers(ReqId, StreamTo, StatCode, Headers_1),
  599. do_trace("Recvd Content-Length of ~p~n", [V_1]),
  600. parse_11_response(Data_1,
  601. State_1#state{rep_buf_size=0,
  602. reply_buffer=[],
  603. content_length=V_1});
  604. _ ->
  605. {_, Reqs_1} = queue:out(Reqs),
  606. % {{value, Req}, Reqs_1} = queue:out(Reqs),
  607. do_reply(From, StreamTo, ReqId,
  608. {error, {content_length_undefined, Headers}}),
  609. do_error_reply(State_1#state{reqs=Reqs_1}, previous_request_failed),
  610. {error, content_length_undefined}
  611. end
  612. end;
  613. {no, Acc_1} when MaxHeaderSize == infinity ->
  614. State#state{reply_buffer=Acc_1};
  615. {no, Acc_1} when length(Acc_1) < MaxHeaderSize ->
  616. State#state{reply_buffer=Acc_1};
  617. {no, _Acc_1} ->
  618. do_reply(From, StreamTo, ReqId, {error, max_headers_size_exceeded}),
  619. {_, Reqs_1} = queue:out(Reqs),
  620. do_error_reply(State#state{reqs=Reqs_1}, previous_request_failed),
  621. {error, max_headers_size_exceeded}
  622. end.
  623. is_connection_closing("HTTP/0.9", _) -> true;
  624. is_connection_closing(_, "close") -> true;
  625. is_connection_closing("HTTP/1.0", "false") -> true;
  626. is_connection_closing(_, _) -> false.
  627. %% This clause determines the chunk size when given data from the beginning of the chunk
  628. parse_11_response(DataRecvd,
  629. #state{transfer_encoding=chunked,
  630. chunk_size=chunk_start,
  631. cur_req=CurReq,
  632. reply_buffer=Buf}=State) ->
  633. case scan_crlf(DataRecvd, Buf) of
  634. {yes, ChunkHeader, Data_1} ->
  635. case parse_chunk_header(ChunkHeader) of
  636. {error, Reason} ->
  637. {error, Reason};
  638. ChunkSize ->
  639. #request{stream_to=StreamTo, req_id=ReqId} = CurReq,
  640. %%
  641. %% Do we have to preserve the chunk encoding when streaming?
  642. %%
  643. % do_interim_reply(From, StreamTo, ReqId, ChunkHeader++[$\r, $\n]),
  644. do_interim_reply(StreamTo, ReqId, {chunk_start, ChunkSize}),
  645. RemLen = length(Data_1),
  646. do_trace("Determined chunk size: ~p. Already recvd: ~p~n", [ChunkSize, RemLen]),
  647. parse_11_response(Data_1, State#state{rep_buf_size=0,
  648. reply_buffer=[],
  649. deleted_crlf=true,
  650. chunk_size=ChunkSize})
  651. end;
  652. {no, Data_1} ->
  653. State#state{reply_buffer=Data_1}
  654. end;
  655. %% This clause is there to remove the CRLF between two chunks
  656. %%
  657. parse_11_response(DataRecvd,
  658. #state{transfer_encoding=chunked,
  659. chunk_size=tbd,
  660. chunks = Chunks,
  661. cur_req=CurReq,
  662. reply_buffer=Buf}=State) ->
  663. case scan_crlf(DataRecvd, Buf) of
  664. {yes, _, NextChunk} ->
  665. #request{stream_to=StreamTo, req_id=ReqId} = CurReq,
  666. %%
  667. %% Do we have to preserve the chunk encoding when streaming?
  668. %%
  669. % do_interim_reply(From, StreamTo, ReqId, [$\r, $\n]),
  670. State_1 = State#state{chunk_size=chunk_start, deleted_crlf=true},
  671. State_2 = case StreamTo of
  672. undefined ->
  673. State_1#state{chunks = [Buf | Chunks]};
  674. _ ->
  675. do_interim_reply(StreamTo, ReqId, chunk_end),
  676. State_1
  677. end,
  678. parse_11_response(NextChunk, State_2);
  679. {no, Data_1} ->
  680. State#state{reply_buffer=Data_1}
  681. end;
  682. %% This clause deals with the end of a chunked transfer
  683. parse_11_response(DataRecvd,
  684. #state{transfer_encoding=chunked, chunk_size=0,
  685. cur_req=CurReq,
  686. deleted_crlf = DelCrlf,
  687. reply_buffer=Trailer, reqs=Reqs}=State) ->
  688. do_trace("Detected end of chunked transfer...~n", []),
  689. DataRecvd_1 = case DelCrlf of
  690. false ->
  691. DataRecvd;
  692. true ->
  693. [$\r, $\n | DataRecvd]
  694. end,
  695. #request{stream_to=StreamTo, req_id=ReqId} = CurReq,
  696. case scan_header(DataRecvd_1, Trailer) of
  697. {yes, _TEHeaders, Rem} ->
  698. {_, Reqs_1} = queue:out(Reqs),
  699. % {{value, Req}, Reqs_1} = queue:out(Reqs),
  700. %%
  701. %% Do we have to preserve the chunk encoding when streaming?
  702. %%
  703. % do_interim_reply(StreamTo, ReqId, [$\r, $\n]++TEHeaders++[$\r, $\n]),
  704. do_interim_reply(StreamTo, ReqId, chunk_end),
  705. State_1 = handle_response(CurReq, State#state{reqs=Reqs_1}),
  706. parse_response(Rem, reset_state(State_1));
  707. {no, Rem} ->
  708. State#state{reply_buffer=Rem, rep_buf_size=length(Rem), chunk_size=tbd}
  709. end;
  710. %% This clause extracts a chunk, given the size.
  711. parse_11_response(DataRecvd,
  712. #state{transfer_encoding=chunked, chunk_size=CSz,
  713. rep_buf_size=RepBufSz}=State) ->
  714. NeedBytes = CSz - RepBufSz,
  715. DataLen = length(DataRecvd),
  716. do_trace("Recvd more data: size: ~p. NeedBytes: ~p~n", [DataLen, NeedBytes]),
  717. case DataLen >= NeedBytes of
  718. true ->
  719. {RemChunk, RemData} = split_list_at(DataRecvd, NeedBytes),
  720. do_trace("Recvd another chunk...~n", []),
  721. do_trace("RemData -> ~p~n", [RemData]),
  722. case accumulate_response(RemChunk, State) of
  723. {error, Reason} ->
  724. {error, Reason};
  725. #state{reply_buffer = NewRepBuf,
  726. chunks = NewChunks} = State_1 ->
  727. State_2 = State_1#state{reply_buffer=[],
  728. chunks = [lists:reverse(NewRepBuf) | NewChunks],
  729. rep_buf_size=0,
  730. chunk_size=tbd},
  731. parse_11_response(RemData, State_2)
  732. end;
  733. false ->
  734. accumulate_response(DataRecvd, State#state{rep_buf_size=RepBufSz + DataLen})
  735. end;
  736. %% This clause to extract the body when Content-Length is specified
  737. parse_11_response(DataRecvd,
  738. #state{content_length=CL, rep_buf_size=RepBufSz,
  739. cur_req = CurReq,
  740. reqs=Reqs}=State) ->
  741. NeedBytes = CL - RepBufSz,
  742. DataLen = length(DataRecvd),
  743. case DataLen >= NeedBytes of
  744. true ->
  745. {RemBody, Rem} = split_list_at(DataRecvd, NeedBytes),
  746. {_, Reqs_1} = queue:out(Reqs),
  747. State_1 = accumulate_response(RemBody, State),
  748. State_2 = handle_response(CurReq, State_1#state{reqs=Reqs_1}),
  749. State_3 = reset_state(State_2),
  750. parse_response(Rem, State_3);
  751. false ->
  752. accumulate_response(DataRecvd, State#state{rep_buf_size=RepBufSz+DataLen})
  753. end.
  754. handle_response(#request{from=From, stream_to=StreamTo, req_id=ReqId},
  755. #state{save_response_to_file = true,
  756. reqs = Reqs,
  757. http_status_code=SCode,
  758. tmp_file_name=TmpFilename,
  759. tmp_file_fd=Fd,
  760. send_timer=ReqTimer,
  761. recvd_headers = RespHeaders}=State) ->
  762. State_1 = case queue:to_list(Reqs) of
  763. [] ->
  764. State#state{cur_req = undefined};
  765. [NextReq | _] ->
  766. State#state{cur_req = NextReq}
  767. end,
  768. do_reply(From, StreamTo, ReqId, {ok, SCode, RespHeaders, {file, TmpFilename}}),
  769. cancel_timer(ReqTimer, {eat_message, {req_timedout, From}}),
  770. file:close(Fd),
  771. State_1#state{tmp_file_name=undefined, tmp_file_fd=undefined};
  772. handle_response(#request{from=From, stream_to=StreamTo, req_id=ReqId},
  773. #state{http_status_code=SCode, recvd_headers=RespHeaders,
  774. reply_buffer=RepBuf, transfer_encoding=TEnc,
  775. reqs = Reqs,
  776. chunks=Chunks, send_timer=ReqTimer}=State) ->
  777. Body = case TEnc of
  778. chunked ->
  779. lists:flatten(lists:reverse(Chunks));
  780. _ ->
  781. lists:flatten(lists:reverse(RepBuf))
  782. end,
  783. State_1 = case queue:to_list(Reqs) of
  784. [] ->
  785. State#state{cur_req = undefined};
  786. [NextReq | _] ->
  787. State#state{cur_req = NextReq}
  788. end,
  789. case get(conn_close) of
  790. "close" ->
  791. do_reply(From, StreamTo, ReqId, {ok, SCode, RespHeaders, Body}),
  792. exit(normal);
  793. _ ->
  794. do_reply(From, StreamTo, ReqId, {ok, SCode, RespHeaders, Body}),
  795. cancel_timer(ReqTimer, {eat_message, {req_timedout, From}}),
  796. State_1
  797. end.
  798. reset_state(State) ->
  799. State#state{status=get_header, rep_buf_size=0,content_length=undefined,
  800. reply_buffer=[], chunks=[], recvd_headers=[], deleted_crlf=false,
  801. http_status_code=undefined, chunk_size=0, transfer_encoding=undefined}.
  802. parse_headers(Headers) ->
  803. case scan_crlf(Headers, []) of
  804. {yes, StatusLine, T} ->
  805. Headers_1 = parse_headers_1(T),
  806. case parse_status_line(StatusLine) of
  807. {ok, HttpVsn, StatCode, _Msg} ->
  808. put(http_prot_vsn, HttpVsn),
  809. {HttpVsn, StatCode, Headers_1};
  810. _ -> %% A HTTP 0.9 response?
  811. put(http_prot_vsn, "HTTP/0.9"),
  812. {"HTTP/0.9", undefined, Headers}
  813. end;
  814. _ ->
  815. {error, no_status_line}
  816. end.
  817. % From RFC 2616
  818. %
  819. % HTTP/1.1 header field values can be folded onto multiple lines if
  820. % the continuation line begins with a space or horizontal tab. All
  821. % linear white space, including folding, has the same semantics as
  822. % SP. A recipient MAY replace any linear white space with a single
  823. % SP before interpreting the field value or forwarding the message
  824. % downstream.
  825. parse_headers_1(String) ->
  826. parse_headers_1(String, [], []).
  827. parse_headers_1([$\n, H |T], [$\r | L], Acc) when H == 32;
  828. H == $\t ->
  829. parse_headers_1(lists:dropwhile(fun(X) ->
  830. is_whitespace(X)
  831. end, T), [32 | L], Acc);
  832. parse_headers_1([$\n|T], [$\r | L], Acc) ->
  833. case parse_header(lists:reverse(L)) of
  834. invalid ->
  835. parse_headers_1(T, [], Acc);
  836. NewHeader ->
  837. parse_headers_1(T, [], [NewHeader | Acc])
  838. end;
  839. parse_headers_1([H|T], L, Acc) ->
  840. parse_headers_1(T, [H|L], Acc);
  841. parse_headers_1([], [], Acc) ->
  842. lists:reverse(Acc);
  843. parse_headers_1([], L, Acc) ->
  844. Acc_1 = case parse_header(lists:reverse(L)) of
  845. invalid ->
  846. Acc;
  847. NewHeader ->
  848. [NewHeader | Acc]
  849. end,
  850. lists:reverse(Acc_1).
  851. parse_status_line(Line) ->
  852. parse_status_line(Line, get_prot_vsn, [], []).
  853. parse_status_line([32 | T], get_prot_vsn, ProtVsn, StatCode) ->
  854. parse_status_line(T, get_status_code, ProtVsn, StatCode);
  855. parse_status_line([32 | T], get_status_code, ProtVsn, StatCode) ->
  856. {ok, lists:reverse(ProtVsn), lists:reverse(StatCode), T};
  857. parse_status_line([H | T], get_prot_vsn, ProtVsn, StatCode) ->
  858. parse_status_line(T, get_prot_vsn, [H|ProtVsn], StatCode);
  859. parse_status_line([H | T], get_status_code, ProtVsn, StatCode) ->
  860. parse_status_line(T, get_status_code, ProtVsn, [H | StatCode]);
  861. parse_status_line([], _, _, _) ->
  862. http_09.
  863. parse_header(L) ->
  864. parse_header(L, []).
  865. parse_header([$: | V], Acc) ->
  866. {lists:reverse(Acc), string:strip(V)};
  867. parse_header([H | T], Acc) ->
  868. parse_header(T, [H | Acc]);
  869. parse_header([], _) ->
  870. invalid.
  871. scan_header([$\n|T], [$\r,$\n,$\r|L]) -> {yes, lists:reverse(L), T};
  872. scan_header([H|T], L) -> scan_header(T, [H|L]);
  873. scan_header([], L) -> {no, L}.
  874. scan_crlf([$\n|T], [$\r | L]) -> {yes, lists:reverse(L), T};
  875. scan_crlf([H|T], L) -> scan_crlf(T, [H|L]);
  876. scan_crlf([], L) -> {no, L}.
  877. fmt_val(L) when list(L) -> L;
  878. fmt_val(I) when integer(I) -> integer_to_list(I);
  879. fmt_val(A) when atom(A) -> atom_to_list(A);
  880. fmt_val(Term) -> io_lib:format("~p", [Term]).
  881. crnl() -> "\r\n".
  882. method(get) -> "GET";
  883. method(post) -> "POST";
  884. method(head) -> "HEAD";
  885. method(options) -> "OPTIONS";
  886. method(put) -> "PUT";
  887. method(delete) -> "DELETE";
  888. method(trace) -> "TRACE".
  889. %% From RFC 2616
  890. %%
  891. % The chunked encoding modifies the body of a message in order to
  892. % transfer it as a series of chunks, each with its own size indicator,
  893. % followed by an OPTIONAL trailer containing entity-header
  894. % fields. This allows dynamically produced content to be transferred
  895. % along with the information necessary for the recipient to verify
  896. % that it has received the full message.
  897. % Chunked-Body = *chunk
  898. % last-chunk
  899. % trailer
  900. % CRLF
  901. % chunk = chunk-size [ chunk-extension ] CRLF
  902. % chunk-data CRLF
  903. % chunk-size = 1*HEX
  904. % last-chunk = 1*("0") [ chunk-extension ] CRLF
  905. % chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
  906. % chunk-ext-name = token
  907. % chunk-ext-val = token | quoted-string
  908. % chunk-data = chunk-size(OCTET)
  909. % trailer = *(entity-header CRLF)
  910. % The chunk-size field is a string of hex digits indicating the size
  911. % of the chunk. The chunked encoding is ended by any chunk whose size
  912. % is zero, followed by the trailer, which is terminated by an empty
  913. % line.
  914. %%
  915. %% The parsing implemented here discards all chunk extensions. It also
  916. %% strips trailing spaces from the chunk size fields as Apache 1.3.27 was
  917. %% sending them.
  918. parse_chunk_header([]) ->
  919. throw({error, invalid_chunk_size});
  920. parse_chunk_header(ChunkHeader) ->
  921. parse_chunk_header(ChunkHeader, []).
  922. parse_chunk_header([$; | _], Acc) ->
  923. hexlist_to_integer(lists:reverse(Acc));
  924. parse_chunk_header([H | T], Acc) ->
  925. case is_whitespace(H) of
  926. true ->
  927. parse_chunk_header(T, Acc);
  928. false ->
  929. parse_chunk_header(T, [H | Acc])
  930. end;
  931. parse_chunk_header([], Acc) ->
  932. hexlist_to_integer(lists:reverse(Acc)).
  933. is_whitespace(32) -> true;
  934. is_whitespace($\r) -> true;
  935. is_whitespace($\n) -> true;
  936. is_whitespace($\t) -> true;
  937. is_whitespace(_) -> false.
  938. parse_url(Url) ->
  939. parse_url(Url, get_protocol, #url{abspath=Url}, []).
  940. parse_url([$:, $/, $/ | _], get_protocol, Url, []) ->
  941. {invalid_uri_1, Url};
  942. parse_url([$:, $/, $/ | T], get_protocol, Url, TmpAcc) ->
  943. Prot = list_to_atom(lists:reverse(TmpAcc)),
  944. parse_url(T, get_username,
  945. Url#url{protocol = Prot},
  946. []);
  947. parse_url([$/ | T], get_username, Url, TmpAcc) ->
  948. %% No username/password. No port number
  949. Url#url{host = lists:reverse(TmpAcc),
  950. port = default_port(Url#url.protocol),
  951. path = [$/ | T]};
  952. parse_url([$: | T], get_username, Url, TmpAcc) ->
  953. %% It is possible that no username/password has been
  954. %% specified. But we'll continue with the assumption that there is
  955. %% a username/password. If we encounter a '@' later on, there is a
  956. %% username/password indeed. If we encounter a '/', it was
  957. %% actually the hostname
  958. parse_url(T, get_password,
  959. Url#url{username = lists:reverse(TmpAcc)},
  960. []);
  961. parse_url([$@ | T], get_username, Url, TmpAcc) ->
  962. parse_url(T, get_host,
  963. Url#url{username = lists:reverse(TmpAcc),
  964. password = ""},
  965. []);
  966. parse_url([$@ | T], get_password, Url, TmpAcc) ->
  967. parse_url(T, get_host,
  968. Url#url{password = lists:reverse(TmpAcc)},
  969. []);
  970. parse_url([$/ | T], get_password, Url, TmpAcc) ->
  971. %% Ok, what we thought was the username/password was the hostname
  972. %% and portnumber
  973. #url{username=User} = Url,
  974. Port = list_to_integer(lists:reverse(TmpAcc)),
  975. Url#url{host = User,
  976. port = Port,
  977. username = undefined,
  978. password = undefined,
  979. path = [$/ | T]};
  980. parse_url([$: | T], get_host, #url{} = Url, TmpAcc) ->
  981. parse_url(T, get_port,
  982. Url#url{host = lists:reverse(TmpAcc)},
  983. []);
  984. parse_url([$/ | T], get_host, #url{protocol=Prot} = Url, TmpAcc) ->
  985. Url#url{host = lists:reverse(TmpAcc),
  986. port = default_port(Prot),
  987. path = [$/ | T]};
  988. parse_url([$/ | T], get_port, #url{protocol=Prot} = Url, TmpAcc) ->
  989. Port = case TmpAcc of
  990. [] ->
  991. default_port(Prot);
  992. _ ->
  993. list_to_integer(lists:reverse(TmpAcc))
  994. end,
  995. Url#url{port = Port, path = [$/ | T]};
  996. parse_url([H | T], State, Url, TmpAcc) ->
  997. parse_url(T, State, Url, [H | TmpAcc]);
  998. parse_url([], get_host, Url, TmpAcc) when TmpAcc /= [] ->
  999. Url#url{host = lists:reverse(TmpAcc),
  1000. port = default_port(Url#url.protocol),
  1001. path = "/"};
  1002. parse_url([], get_username, Url, TmpAcc) when TmpAcc /= [] ->
  1003. Url#url{host = lists:reverse(TmpAcc),
  1004. port = default_port(Url#url.protocol),
  1005. path = "/"};
  1006. parse_url([], get_port, #url{protocol=Prot} = Url, TmpAcc) ->
  1007. Port = case TmpAcc of
  1008. [] ->
  1009. default_port(Prot);
  1010. _ ->
  1011. list_to_integer(lists:reverse(TmpAcc))
  1012. end,
  1013. Url#url{port = Port,
  1014. path = "/"};
  1015. parse_url([], get_password, Url, TmpAcc) ->
  1016. %% Ok, what we thought was the username/password was the hostname
  1017. %% and portnumber
  1018. #url{username=User} = Url,
  1019. Port = case TmpAcc of
  1020. [] ->
  1021. default_port(Url#url.protocol);
  1022. _ ->
  1023. list_to_integer(lists:reverse(TmpAcc))
  1024. end,
  1025. Url#url{host = User,
  1026. port = Port,
  1027. username = undefined,
  1028. password = undefined,
  1029. path = "/"};
  1030. parse_url([], State, Url, TmpAcc) ->
  1031. {invalid_uri_2, State, Url, TmpAcc}.
  1032. default_port(http) -> 80;
  1033. default_port(https) -> 443;
  1034. default_port(ftp) -> 21.
  1035. send_async_headers(_ReqId, undefined, _StatCode, _Headers) ->
  1036. ok;
  1037. send_async_headers(ReqId, StreamTo, StatCode, Headers) ->
  1038. catch StreamTo ! {ibrowse_async_headers, ReqId, StatCode, Headers}.
  1039. do_reply(From, undefined, _, Msg) ->
  1040. ibrowse:reply(From, Msg);
  1041. do_reply(_From, StreamTo, ReqId, {ok, _, _, _}) ->
  1042. ibrowse:finished_async_request(),
  1043. catch StreamTo ! {ibrowse_async_response_end, ReqId};
  1044. % do_reply(_From, StreamTo, ReqId, {ok, _, _, Data}) ->
  1045. % ibrowse:finished_async_request(),
  1046. % catch StreamTo ! {ibrowse_async_response_end, ReqId, Data};
  1047. do_reply(_From, StreamTo, ReqId, Msg) ->
  1048. catch StreamTo ! {ibrowse_async_response, ReqId, Msg}.
  1049. do_interim_reply(undefined, _ReqId, _Msg) ->
  1050. ok;
  1051. do_interim_reply(StreamTo, ReqId, Msg) ->
  1052. catch StreamTo ! {ibrowse_async_response, ReqId, Msg}.
  1053. do_error_reply(#state{reqs = Reqs}, Err) ->
  1054. ReqList = queue:to_list(Reqs),
  1055. lists:foreach(fun(#request{from=From, stream_to=StreamTo, req_id=ReqId}) ->
  1056. do_reply(From, StreamTo, ReqId, {error, Err})
  1057. end, ReqList).
  1058. split_list_at(List, N) ->
  1059. split_list_at(List, N, []).
  1060. split_list_at([], _, Acc) ->
  1061. {lists:reverse(Acc), []};
  1062. split_list_at(List2, 0, List1) ->
  1063. {lists:reverse(List1), List2};
  1064. split_list_at([H | List2], N, List1) ->
  1065. split_list_at(List2, N-1, [H | List1]).
  1066. get_value(Tag, TVL) ->
  1067. {value, {_, V}} = lists:keysearch(Tag,1,TVL),
  1068. V.
  1069. get_value(Tag, TVL, DefVal) ->
  1070. case lists:keysearch(Tag, 1, TVL) of
  1071. {value, {_, V}} ->
  1072. V;
  1073. false ->
  1074. DefVal
  1075. end.
  1076. hexlist_to_integer(List) ->
  1077. hexlist_to_integer(lists:reverse(List), 1, 0).
  1078. hexlist_to_integer([H | T], Multiplier, Acc) ->
  1079. hexlist_to_integer(T, Multiplier*16, Multiplier*to_ascii(H) + Acc);
  1080. hexlist_to_integer([], _, Acc) ->
  1081. Acc.
  1082. to_ascii($A) -> 10;
  1083. to_ascii($a) -> 10;
  1084. to_ascii($B) -> 11;
  1085. to_ascii($b) -> 11;
  1086. to_ascii($C) -> 12;
  1087. to_ascii($c) -> 12;
  1088. to_ascii($D) -> 13;
  1089. to_ascii($d) -> 13;
  1090. to_ascii($E) -> 14;
  1091. to_ascii($e) -> 14;
  1092. to_ascii($F) -> 15;
  1093. to_ascii($f) -> 15;
  1094. to_ascii($1) -> 1;
  1095. to_ascii($2) -> 2;
  1096. to_ascii($3) -> 3;
  1097. to_ascii($4) -> 4;
  1098. to_ascii($5) -> 5;
  1099. to_ascii($6) -> 6;
  1100. to_ascii($7) -> 7;
  1101. to_ascii($8) -> 8;
  1102. to_ascii($9) -> 9;
  1103. to_ascii($0) -> 0.
  1104. safe_get_env(App, EnvVar, DefaultValue) ->
  1105. case application:get_env(App,EnvVar) of
  1106. undefined ->
  1107. DefaultValue;
  1108. {ok, V} ->
  1109. V
  1110. end.
  1111. cancel_timer(undefined) -> ok;
  1112. cancel_timer(Ref) -> erlang:cancel_timer(Ref).
  1113. cancel_timer(Ref, {eat_message, Msg}) ->
  1114. cancel_timer(Ref),
  1115. receive
  1116. Msg ->
  1117. ok
  1118. after 0 ->
  1119. ok
  1120. end.
  1121. make_req_id() ->
  1122. now().
  1123. do_trace(Fmt, Args) ->
  1124. do_trace(get(my_trace_flag), Fmt, Args).
  1125. % Useful for debugging
  1126. % do_trace(_, Fmt, Args) ->
  1127. % io:format("~s -- CLI(~p,~p) - "++Fmt, [printable_date(),
  1128. % get(ibrowse_http_client_host),
  1129. % get(ibrowse_http_client_port) | Args]);
  1130. do_trace(true, Fmt, Args) ->
  1131. io:format("~s -- CLI(~p,~p) - "++Fmt,
  1132. [printable_date(),
  1133. get(ibrowse_http_client_host),
  1134. get(ibrowse_http_client_port) | Args]);
  1135. do_trace(_, _, _) -> ok.
  1136. printable_date() ->
  1137. {{Y,Mo,D},{H, M, S}} = calendar:local_time(),
  1138. {_,_,MicroSecs} = now(),
  1139. [integer_to_list(Y),
  1140. $-,
  1141. integer_to_list(Mo),
  1142. $-,
  1143. integer_to_list(D),
  1144. $_,
  1145. integer_to_list(H),
  1146. $:,
  1147. integer_to_list(M),
  1148. $:,
  1149. integer_to_list(S),
  1150. $:,
  1151. integer_to_list(MicroSecs div 1000)].
  1152. to_lower(Str) ->
  1153. to_lower(Str, []).
  1154. to_lower([H|T], Acc) when H >= $A, H =< $Z ->
  1155. to_lower(T, [H+32|Acc]);
  1156. to_lower([H|T], Acc) ->
  1157. to_lower(T, [H|Acc]);
  1158. to_lower([], Acc) ->
  1159. lists:reverse(Acc).