1%% ``Licensed under the Apache License, Version 2.0 (the "License");
2%% you may not use this file except in compliance with the License.
3%% You may obtain a copy of the License at
4%%
5%%     http://www.apache.org/licenses/LICENSE-2.0
6%%
7%% Unless required by applicable law or agreed to in writing, software
8%% distributed under the License is distributed on an "AS IS" BASIS,
9%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10%% See the License for the specific language governing permissions and
11%% limitations under the License.
12%%
13%% The Initial Developer of the Original Code is Mobile Arts AB
14%% Portions created by Mobile Arts are Copyright 2002, Mobile Arts AB
15%% All Rights Reserved.''
16%%
17%%% File    : http_lib.erl
18%%% Author  : Johan Blom <johan.blom@mobilearts.se>
19%%% Description : Generic, HTTP specific helper functions
20%%% Created :  4 Mar 2002 by Johan Blom
21
22%%% TODO
23%%% - Check if I need to anything special when parsing
24%%%   "Content-Type:multipart/form-data"
25
26-module(http_lib).
27-author("johan.blom@mobilearts.se").
28
29-include("http.hrl").
30-include("jnets_httpd.hrl").
31
32-export([connection_close/1,
33	 accept/3,deliver/3,recv/4,recv0/3,
34	 connect/1,send/3,close/2,controlling_process/3,setopts/3,
35	 getParameterValue/2,
36%	 get_var/2,
37	 create_request_line/3]).
38
39-export([read_client_headers/2,read_server_headers/2,
40	 get_auth_data/1,create_header_list/1,
41	 read_client_body/2,read_client_multipartrange_body/3,
42	 read_server_body/2]).
43
44
45%%% Server response:
46%%%    Check "Connection" header if server requests session to be closed.
47%%%    No 'close' means returns false
48%%% Client Request:
49%%%    Check if 'close' in request headers
50%%% Only care about HTTP 1.1 clients!
51connection_close(Headers) when record(Headers,req_headers) ->
52    case Headers#req_headers.connection of
53	"close" ->
54	    true;
55	"keep-alive" ->
56	    false;
57	Value when list(Value) ->
58	    true;
59	_ ->
60	    false
61    end;
62connection_close(Headers) when record(Headers,res_headers) ->
63    case Headers#res_headers.connection of
64	"close" ->
65	    true;
66	"keep-alive" ->
67	    false;
68	Value when list(Value) ->
69	    true;
70	_ ->
71	    false
72    end.
73
74
75%% =============================================================================
76%%% Debugging:
77
78% format_time(TS) ->
79%     {_,_,MicroSecs}=TS,
80%     {{Y,Mon,D},{H,M,S}}=calendar:now_to_universal_time(TS),
81%     lists:flatten(io_lib:format("~4.4.0w-~2.2.0w-~2.2.0w,~2.2.0w:~2.2.0w:~6.3.0f",
82% 				[Y,Mon,D,H,M,S+(MicroSecs/1000000)])).
83
84%% Time in milli seconds
85% t() ->
86%     {A,B,C} = erlang:now(),
87%     A*1000000000+B*1000+(C div 1000).
88
89% sz(L) when list(L) ->
90%     length(L);
91% sz(B) when binary(B) ->
92%     size(B);
93% sz(O) ->
94%     {unknown_size,O}.
95
96
97%% =============================================================================
98
99getHeaderValue(_Attr,[]) ->
100    [];
101getHeaderValue(Attr,[{Attr,Value}|_Rest]) ->
102    Value;
103getHeaderValue(Attr,[_|Rest]) ->
104    getHeaderValue(Attr,Rest).
105
106getParameterValue(_Attr,undefined) ->
107    undefined;
108getParameterValue(Attr,List) ->
109    case lists:keysearch(Attr,1,List) of
110	{value,{Attr,Val}} ->
111	    Val;
112	_ ->
113	    undefined
114    end.
115
116create_request_line(Method,Path,{Major,Minor}) ->
117    [atom_to_list(Method)," ",Path,
118     " HTTP/",integer_to_list(Major),".",integer_to_list(Minor)];
119create_request_line(Method,Path,Minor) ->
120    [atom_to_list(Method)," ",Path," HTTP/1.",integer_to_list(Minor)].
121
122
123%%% ============================================================================
124read_client_headers(Info,Timeout) ->
125    Headers=read_response_h(Info#response.scheme,Info#response.socket,Timeout,
126			    Info#response.headers),
127    Info#response{headers=Headers}.
128
129read_server_headers(Info,Timeout) ->
130    Headers=read_request_h(Info#mod.socket_type,Info#mod.socket,Timeout,
131			   Info#mod.headers),
132    Info#mod{headers=Headers}.
133
134
135%% Parses the header of a HTTP request and returns a key,value tuple
136%% list containing Name and Value of each header directive as of:
137%%
138%% Content-Type: multipart/mixed -> {"Content-Type", "multipart/mixed"}
139%%
140%% But in http/1.1 the field-names are case insencitive so now it must be
141%% Content-Type: multipart/mixed -> {"content-type", "multipart/mixed"}
142%% The standard furthermore says that leading and traling white space
143%% is not a part of the fieldvalue and shall therefore be removed.
144read_request_h(SType,S,Timeout,H) ->
145    case recv0(SType,S,Timeout) of
146	{ok,{http_header,_,'Connection',_,Value}} ->
147	    read_request_h(SType,S,Timeout,H#req_headers{connection=Value});
148	{ok,{http_header,_,'Content-Type',_,Val}} ->
149	    read_request_h(SType,S,Timeout,H#req_headers{content_type=Val});
150	{ok,{http_header,_,'Host',_,Value}} ->
151	    read_request_h(SType,S,Timeout,H#req_headers{host=Value});
152	{ok,{http_header,_,'Content-Length',_,Value}} ->
153	    read_request_h(SType,S,Timeout,H#req_headers{content_length=Value});
154%	{ok,{http_header,_,'Expect',_,Value}} -> % FIXME! Update inet_drv.c!!
155%	    read_request_h(SType,S,Timeout,H#req_headers{expect=Value});
156	{ok,{http_header,_,'Transfer-Encoding',_,V}} ->
157	    read_request_h(SType,S,Timeout,H#req_headers{transfer_encoding=V});
158	{ok,{http_header,_,'Authorization',_,Value}} ->
159	    read_request_h(SType,S,Timeout,H#req_headers{authorization=Value});
160	{ok,{http_header,_,'User-Agent',_,Value}} ->
161	    read_request_h(SType,S,Timeout,H#req_headers{user_agent=Value});
162	{ok,{http_header,_,'Range',_,Value}} ->
163	    read_request_h(SType,S,Timeout,H#req_headers{range=Value});
164	{ok,{http_header,_,'If-Range',_,Value}} ->
165	    read_request_h(SType,S,Timeout,H#req_headers{if_range=Value});
166	{ok,{http_header,_,'If-Match',_,Value}} ->
167	    read_request_h(SType,S,Timeout,H#req_headers{if_match=Value});
168	{ok,{http_header,_,'If-None-Match',_,Value}} ->
169	    read_request_h(SType,S,Timeout,H#req_headers{if_none_match=Value});
170	{ok,{http_header,_,'If-Modified-Since',_,V}} ->
171	    read_request_h(SType,S,Timeout,H#req_headers{if_modified_since=V});
172	{ok,{http_header,_,'If-Unmodified-Since',_,V}} ->
173	    read_request_h(SType,S,Timeout,H#req_headers{if_unmodified_since=V});
174	{ok,{http_header,_,K,_,V}} ->
175	    read_request_h(SType,S,Timeout,
176			   H#req_headers{other=H#req_headers.other++[{K,V}]});
177	{ok,http_eoh} ->
178	    H;
179	{error, timeout} when SType==http ->
180	    throw({error, session_local_timeout});
181	{error, etimedout} when SType==https ->
182	    throw({error, session_local_timeout});
183	{error, Reason} when Reason==closed;Reason==enotconn ->
184	    throw({error, session_remotely_closed});
185	{error, Reason} ->
186	    throw({error,Reason})
187    end.
188
189
190read_response_h(SType,S,Timeout,H) ->
191    case recv0(SType,S,Timeout) of
192	{ok,{http_header,_,'Connection',_,Val}} ->
193	    read_response_h(SType,S,Timeout,H#res_headers{connection=Val});
194	{ok,{http_header,_,'Content-Length',_,Val}} ->
195	    read_response_h(SType,S,Timeout,H#res_headers{content_length=Val});
196	{ok,{http_header,_,'Content-Type',_,Val}} ->
197	    read_response_h(SType,S,Timeout,H#res_headers{content_type=Val});
198	{ok,{http_header,_,'Transfer-Encoding',_,V}} ->
199	    read_response_h(SType,S,Timeout,H#res_headers{transfer_encoding=V});
200	{ok,{http_header,_,'Location',_,V}} ->
201	    read_response_h(SType,S,Timeout,H#res_headers{location=V});
202	{ok,{http_header,_,'Retry-After',_,V}} ->
203	    read_response_h(SType,S,Timeout,H#res_headers{retry_after=V});
204	{ok,{http_header,_,K,_,V}} ->
205	    read_response_h(SType,S,Timeout,
206			    H#res_headers{other=H#res_headers.other++[{K,V}]});
207	{ok,http_eoh} ->
208	    H;
209	{error, timeout} when SType==http ->
210	    throw({error, session_local_timeout});
211	{error, etimedout} when SType==https ->
212	    throw({error, session_local_timeout});
213	{error, Reason} when Reason==closed;Reason==enotconn ->
214	    throw({error, session_remotely_closed});
215	{error, Reason} ->
216	    throw({error,Reason})
217    end.
218
219
220%%% Got the headers, and maybe a part of the body, now read in the rest
221%%% Note:
222%%% - No need to check for Expect header if client
223%%% - Currently no support for setting MaxHeaderSize in client, set to
224%%%   unlimited.
225%%% - Move to raw packet mode as we are finished with HTTP parsing
226read_client_body(Info,Timeout) ->
227    Headers=Info#response.headers,
228    case Headers#res_headers.transfer_encoding of
229	"chunked" ->
230	    ?DEBUG("read_entity_body2()->"
231		"Transfer-encoding:Chunked Data:",[]),
232	    read_client_chunked_body(Info,Timeout,?MAXBODYSIZE);
233	Encoding when list(Encoding) ->
234	    ?DEBUG("read_entity_body2()->"
235		"Transfer-encoding:Unknown",[]),
236	    throw({error,unknown_coding});
237	_ ->
238	    ContLen=list_to_integer(Headers#res_headers.content_length),
239	    if
240		ContLen>?MAXBODYSIZE ->
241		    throw({error,body_too_big});
242		true ->
243		    ?DEBUG("read_entity_body2()->"
244			"Transfer-encoding:none ",[]),
245		    Info#response{body=read_plain_body(Info#response.scheme,
246						       Info#response.socket,
247						       ContLen,
248						       Info#response.body,
249						       Timeout)}
250	    end
251    end.
252
253
254%%% ----------------------------------------------------------------------
255read_server_body(Info,Timeout) ->
256    MaxBodySz=httpd_util:lookup(Info#mod.config_db,max_body_size,?MAXBODYSIZE),
257    ContLen=list_to_integer((Info#mod.headers)#req_headers.content_length),
258    %% ?vtrace("ContentLength: ~p", [ContLen]),
259    if
260	integer(ContLen),integer(MaxBodySz),ContLen>MaxBodySz ->
261	    throw({error,body_too_big});
262	true ->
263	    read_server_body2(Info,Timeout,ContLen,MaxBodySz)
264    end.
265
266
267%%----------------------------------------------------------------------
268%% Control if the body is transfer encoded, if so decode it.
269%% Note:
270%% - MaxBodySz has an integer value or 'nolimit'
271%% - ContLen has an integer value or 'undefined'
272%% All applications MUST be able to receive and decode the "chunked"
273%% transfer-coding, see RFC 2616 Section 3.6.1
274read_server_body2(Info,Timeout,ContLen,MaxBodySz) ->
275    ?DEBUG("read_entity_body2()->Max: ~p ~nLength:~p ~nSocket: ~p ~n",
276	[MaxBodySz,ContLen,Info#mod.socket]),
277    case (Info#mod.headers)#req_headers.transfer_encoding of
278	"chunked" ->
279	    ?DEBUG("read_entity_body2()->"
280		"Transfer-encoding:Chunked Data:",[]),
281	    read_server_chunked_body(Info,Timeout,MaxBodySz);
282	Encoding when list(Encoding) ->
283	    ?DEBUG("read_entity_body2()->"
284		"Transfer-encoding:Unknown",[]),
285	    httpd_response:send_status(Info,501,"Unknown Transfer-Encoding"),
286	    http_lib:close(Info#mod.socket_type,Info#mod.socket),
287	    throw({error,{status_sent,"Unknown Transfer-Encoding "++Encoding}});
288	_ when integer(ContLen),integer(MaxBodySz),ContLen>MaxBodySz ->
289	    throw({error,body_too_big});
290	_ when integer(ContLen) ->
291	    ?DEBUG("read_entity_body2()->"
292		"Transfer-encoding:none ",[]),
293	    Info#mod{entity_body=read_plain_body(Info#mod.socket_type,
294						 Info#mod.socket,
295						 ContLen,Info#mod.entity_body,
296						 Timeout)}
297    end.
298
299
300%%% ----------------------------------------------------------------------------
301%%% The body was plain, just read it from the socket.
302read_plain_body(_SocketType,Socket,0,Cont,_Timeout) ->
303    Cont;
304read_plain_body(SocketType,Socket,ContLen,Cont,Timeout) ->
305    Body=read_more_data(SocketType,Socket,ContLen,Timeout),
306    <<Cont/binary,Body/binary>>.
307
308%%% ----------------------------------------------------------------------------
309%%% The body was chunked, decode it.
310%%% From RFC2616, Section 3.6.1
311%%        Chunked-Body   = *chunk
312%%                         last-chunk
313%%                         trailer
314%%                         CRLF
315%%
316%%        chunk          = chunk-size [ chunk-extension ] CRLF
317%%                         chunk-data CRLF
318%%        chunk-size     = 1*HEX
319%%        last-chunk     = 1*("0") [ chunk-extension ] CRLF
320%%
321%%        chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
322%%        chunk-ext-name = token
323%%        chunk-ext-val  = token | quoted-string
324%%        chunk-data     = chunk-size(OCTET)
325%%        trailer        = *(entity-header CRLF)
326%%
327%%% "All applications MUST ignore chunk-extension extensions they do not
328%%% understand.", see RFC 2616 Section 3.6.1
329%%% We don't understand any extension...
330read_client_chunked_body(Info,Timeout,MaxChunkSz) ->
331    case read_chunk(Info#response.scheme,Info#response.socket,
332		    Timeout,0,MaxChunkSz) of
333	{last_chunk,_ExtensionList} -> % Ignore extension
334	    TrailH=read_headers_old(Info#response.scheme,Info#response.socket,
335				    Timeout),
336	    H=Info#response.headers,
337	    OtherHeaders=H#res_headers.other++TrailH,
338	    Info#response{headers=H#res_headers{other=OtherHeaders}};
339	{Chunk,ChunkSize,_ExtensionList} -> % Ignore extension
340	    Info1=Info#response{body= <<(Info#response.body)/binary,
341				        Chunk/binary>>},
342	    read_client_chunked_body(Info1,Timeout,MaxChunkSz-ChunkSize);
343	{error,Reason} ->
344	    throw({error,Reason})
345    end.
346
347
348read_server_chunked_body(Info,Timeout,MaxChunkSz) ->
349    case read_chunk(Info#mod.socket_type,Info#mod.socket,
350		    Timeout,0,MaxChunkSz) of
351	{last_chunk,_ExtensionList} -> % Ignore extension
352	    TrailH=read_headers_old(Info#mod.socket_type,Info#mod.socket,
353				    Timeout),
354	    H=Info#mod.headers,
355	    OtherHeaders=H#req_headers.other++TrailH,
356	    Info#mod{headers=H#req_headers{other=OtherHeaders}};
357	{Chunk,ChunkSize,_ExtensionList} -> % Ignore extension
358	    Info1=Info#mod{entity_body= <<(Info#mod.entity_body)/binary,
359					   Chunk/binary>>},
360	    read_server_chunked_body(Info1,Timeout,MaxChunkSz-ChunkSize);
361	{error,Reason} ->
362	    throw({error,Reason})
363    end.
364
365
366read_chunk(Scheme,Socket,Timeout,Int,MaxChunkSz) when MaxChunkSz>Int ->
367    case read_more_data(Scheme,Socket,1,Timeout) of
368	<<C>> when $0=<C,C=<$9 ->
369	    read_chunk(Scheme,Socket,Timeout,16*Int+(C-$0),MaxChunkSz);
370	<<C>> when $a=<C,C=<$f ->
371	    read_chunk(Scheme,Socket,Timeout,16*Int+10+(C-$a),MaxChunkSz);
372	<<C>> when $A=<C,C=<$F ->
373	    read_chunk(Scheme,Socket,Timeout,16*Int+10+(C-$A),MaxChunkSz);
374	<<$;>> when Int>0 ->
375	    ExtensionList=read_chunk_ext_name(Scheme,Socket,Timeout,[],[]),
376	    read_chunk_data(Scheme,Socket,Int+1,ExtensionList,Timeout);
377	<<$;>> when Int==0 ->
378	    ExtensionList=read_chunk_ext_name(Scheme,Socket,Timeout,[],[]),
379	    read_data_lf(Scheme,Socket,Timeout),
380	    {last_chunk,ExtensionList};
381	<<?CR>> when Int>0 ->
382	    read_chunk_data(Scheme,Socket,Int+1,[],Timeout);
383	<<?CR>> when Int==0 ->
384	    read_data_lf(Scheme,Socket,Timeout),
385	    {last_chunk,[]};
386	<<C>> when C==$ -> % Some servers (e.g., Apache 1.3.6) throw in
387			   % additional whitespace...
388	    read_chunk(Scheme,Socket,Timeout,Int,MaxChunkSz);
389	_Other ->
390	    {error,unexpected_chunkdata}
391    end;
392read_chunk(_Scheme,_Socket,_Timeout,_Int,_MaxChunkSz) ->
393    {error,body_too_big}.
394
395
396%%% Note:
397%%% - Got the initial ?CR already!
398%%% - Bitsyntax does not allow matching of ?CR,?LF in the end of the first read
399read_chunk_data(Scheme,Socket,Int,ExtensionList,Timeout) ->
400    case read_more_data(Scheme,Socket,Int,Timeout) of
401	<<?LF,Chunk/binary>> ->
402	    case read_more_data(Scheme,Socket,2,Timeout) of
403		<<?CR,?LF>> ->
404		    {Chunk,size(Chunk),ExtensionList};
405		_ ->
406		    {error,bad_chunkdata}
407	    end;
408	_ ->
409	    {error,bad_chunkdata}
410    end.
411
412read_chunk_ext_name(Scheme,Socket,Timeout,Name,Acc) ->
413    Len=length(Name),
414    case read_more_data(Scheme,Socket,1,Timeout) of
415	$= when Len>0 ->
416	    read_chunk_ext_val(Scheme,Socket,Timeout,Name,[],Acc);
417	$; when Len>0 ->
418	    read_chunk_ext_name(Scheme,Socket,Timeout,[],
419				[{lists:reverse(Name),""}|Acc]);
420	?CR when Len>0 ->
421	    lists:reverse([{lists:reverse(Name,"")}|Acc]);
422	Token -> % FIXME Check that it is "token"
423	    read_chunk_ext_name(Scheme,Socket,Timeout,[Token|Name],Acc);
424	_ ->
425	    {error,bad_chunk_extension_name}
426    end.
427
428read_chunk_ext_val(Scheme,Socket,Timeout,Name,Val,Acc) ->
429    Len=length(Val),
430    case read_more_data(Scheme,Socket,1,Timeout) of
431	$; when Len>0 ->
432	    read_chunk_ext_name(Scheme,Socket,Timeout,[],
433				[{Name,lists:reverse(Val)}|Acc]);
434	?CR when Len>0 ->
435	    lists:reverse([{Name,lists:reverse(Val)}|Acc]);
436	Token -> % FIXME Check that it is "token" or "quoted-string"
437	    read_chunk_ext_val(Scheme,Socket,Timeout,Name,[Token|Val],Acc);
438	_ ->
439	    {error,bad_chunk_extension_value}
440    end.
441
442read_data_lf(Scheme,Socket,Timeout) ->
443    case read_more_data(Scheme,Socket,1,Timeout) of
444	?LF ->
445	    ok;
446	_ ->
447	    {error,bad_chunkdata}
448    end.
449
450%%% ----------------------------------------------------------------------------
451%%% The body was "multipart/byteranges", decode it.
452%%% Example from RFC 2616, Appendix 19.2
453%%%    HTTP/1.1 206 Partial Content
454%%%    Date: Wed, 15 Nov 1995 06:25:24 GMT
455%%%    Last-Modified: Wed, 15 Nov 1995 04:58:08 GMT
456%%%    Content-type: multipart/byteranges; boundary=THIS_STRING_SEPARATES
457%%%
458%%%    --THIS_STRING_SEPARATES
459%%%    Content-type: application/pdf
460%%%    Content-range: bytes 500-999/8000
461%%%
462%%%    ...the first range...
463%%%    --THIS_STRING_SEPARATES
464%%%    Content-type: application/pdf
465%%%    Content-range: bytes 7000-7999/8000
466%%%
467%%%    ...the second range
468%%%    --THIS_STRING_SEPARATES--
469%%%
470%%%       Notes:
471%%%
472%%%       1) Additional CRLFs may precede the first boundary string in the
473%%%          entity.
474%%% FIXME!!
475read_client_multipartrange_body(Info,Parstr,Timeout) ->
476    Boundary=get_boundary(Parstr),
477    scan_boundary(Info,Boundary),
478    Info#response{body=read_multipart_body(Info,Boundary,Timeout)}.
479
480read_multipart_body(Info,Boundary,Timeout) ->
481    Info.
482
483%     Headers=read_headers_old(Info#response.scheme,Info#response.socket,Timeout),
484%     H=Info#response.headers,
485%     OtherHeaders=H#res_headers.other++TrailH,
486%     Info#response{headers=H#res_headers{other=OtherHeaders}}.
487
488
489scan_boundary(Info,Boundary) ->
490    Info.
491
492
493get_boundary(Parstr) ->
494    case skip_lwsp(Parstr) of
495	[] ->
496	    throw({error,missing_range_boundary_parameter});
497	Val ->
498	    get_boundary2(string:tokens(Val, ";"))
499    end.
500
501get_boundary2([]) ->
502    undefined;
503get_boundary2([Param|Rest]) ->
504    case string:tokens(skip_lwsp(Param), "=") of
505	["boundary"++Attribute,Value] ->
506	    Value;
507	_ ->
508	    get_boundary2(Rest)
509    end.
510
511
512%% skip space & tab
513skip_lwsp([$ | Cs]) -> skip_lwsp(Cs);
514skip_lwsp([$\t | Cs]) -> skip_lwsp(Cs);
515skip_lwsp(Cs) -> Cs.
516
517%%% ----------------------------------------------------------------------------
518
519%%% Read the incoming data from the open socket.
520read_more_data(http,Socket,Len,Timeout) ->
521    case gen_tcp:recv(Socket,Len,Timeout) of
522	{ok,Val} ->
523	    Val;
524	{error, timeout} ->
525	    throw({error, session_local_timeout});
526	{error, Reason} when Reason==closed;Reason==enotconn ->
527	    throw({error, session_remotely_closed});
528	{error, Reason} ->
529%	    httpd_response:send_status(Info,400,none),
530	    throw({error, Reason})
531    end;
532read_more_data(https,Socket,Len,Timeout) ->
533    case ssl:recv(Socket,Len,Timeout) of
534	{ok,Val} ->
535	    Val;
536	{error, etimedout} ->
537	    throw({error, session_local_timeout});
538	{error, Reason} when Reason==closed;Reason==enotconn ->
539	    throw({error, session_remotely_closed});
540	{error, Reason} ->
541%	    httpd_response:send_status(Info,400,none),
542	    throw({error, Reason})
543    end.
544
545
546%% =============================================================================
547%%% Socket handling
548
549accept(http,ListenSocket, Timeout) ->
550    gen_tcp:accept(ListenSocket, Timeout);
551accept(https,ListenSocket, Timeout) ->
552    ssl:accept(ListenSocket, Timeout).
553
554
555close(http,Socket) ->
556    gen_tcp:close(Socket);
557close(https,Socket) ->
558    ssl:close(Socket).
559
560
561connect(#request{scheme=http,settings=Settings,address=Addr}) ->
562    case proxyusage(Addr,Settings) of
563	{error,Reason} ->
564	    {error,Reason};
565	{Host,Port} ->
566	    Opts=[binary,{active,false},{reuseaddr,true}],
567	    gen_tcp:connect(Host,Port,Opts)
568    end;
569connect(#request{scheme=https,settings=Settings,address=Addr}) ->
570    case proxyusage(Addr,Settings) of
571	{error,Reason} ->
572	    {error,Reason};
573	{Host,Port} ->
574	    Opts=case Settings#client_settings.ssl of
575		     false ->
576			 [binary,{active,false}];
577		     SSLSettings ->
578			 [binary,{active,false}]++SSLSettings
579		 end,
580	    ssl:connect(Host,Port,Opts)
581    end.
582
583
584%%% Check to see if the given {Host,Port} tuple is in the NoProxyList
585%%% Returns an eventually updated {Host,Port} tuple, with the proxy address
586proxyusage(HostPort,Settings) ->
587    case Settings#client_settings.useproxy of
588	true ->
589	    case noProxy(HostPort,Settings#client_settings.noproxylist) of
590		true ->
591		    HostPort;
592		_ ->
593		    case Settings#client_settings.proxy of
594			undefined ->
595			    {error,no_proxy_defined};
596			ProxyHostPort ->
597			    ProxyHostPort
598		    end
599	    end;
600	_ ->
601	    HostPort
602    end.
603
604noProxy(_HostPort,[]) ->
605    false;
606noProxy({Host,Port},[{Host,Port}|Rest]) ->
607    true;
608noProxy(HostPort,[_|Rest]) ->
609    noProxy(HostPort,Rest).
610
611
612controlling_process(http,Socket,Pid) ->
613    gen_tcp:controlling_process(Socket,Pid);
614controlling_process(https,Socket,Pid) ->
615    ssl:controlling_process(Socket,Pid).
616
617
618deliver(SocketType, Socket, Message)  ->
619    case send(SocketType, Socket, Message) of
620	{error, einval} ->
621	    close(SocketType, Socket),
622	    socket_closed;
623	{error, _Reason} ->
624%	    ?vlog("deliver(~p) failed for reason:"
625%		  "~n   Reason: ~p",[SocketType,_Reason]),
626	    close(SocketType, Socket),
627	    socket_closed;
628	_Other ->
629	    ok
630    end.
631
632
633recv0(http,Socket,Timeout) ->
634    gen_tcp:recv(Socket,0,Timeout);
635recv0(https,Socket,Timeout) ->
636    ssl:recv(Socket,0,Timeout).
637
638recv(http,Socket,Len,Timeout) ->
639    gen_tcp:recv(Socket,Len,Timeout);
640recv(https,Socket,Len,Timeout) ->
641    ssl:recv(Socket,Len,Timeout).
642
643
644setopts(http,Socket,Options) ->
645    inet:setopts(Socket,Options);
646setopts(https,Socket,Options) ->
647    ssl:setopts(Socket,Options).
648
649
650send(http,Socket,Message) ->
651    gen_tcp:send(Socket,Message);
652send(https,Socket,Message) ->
653    ssl:send(Socket,Message).
654
655
656%%% ============================================================================
657%%% HTTP Server only
658
659%%% Returns the Authenticating data in the HTTP request
660get_auth_data("Basic "++EncodedString) ->
661    UnCodedString=httpd_util:decode_base64(EncodedString),
662    case catch string:tokens(UnCodedString,":") of
663	[User,PassWord] ->
664	    {User,PassWord};
665	{error,Error}->
666	    {error,Error}
667    end;
668get_auth_data(BadCredentials) when list(BadCredentials) ->
669    {error,BadCredentials};
670get_auth_data(_) ->
671    {error,nouser}.
672
673
674create_header_list(H) ->
675    lookup(connection,H#req_headers.connection)++
676	lookup(host,H#req_headers.host)++
677	lookup(content_length,H#req_headers.content_length)++
678	lookup(transfer_encoding,H#req_headers.transfer_encoding)++
679	lookup(authorization,H#req_headers.authorization)++
680	lookup(user_agent,H#req_headers.user_agent)++
681	lookup(user_agent,H#req_headers.range)++
682	lookup(user_agent,H#req_headers.if_range)++
683	lookup(user_agent,H#req_headers.if_match)++
684	lookup(user_agent,H#req_headers.if_none_match)++
685	lookup(user_agent,H#req_headers.if_modified_since)++
686	lookup(user_agent,H#req_headers.if_unmodified_since)++
687	H#req_headers.other.
688
689lookup(_Key,undefined) ->
690    [];
691lookup(Key,Val) ->
692    [{Key,Val}].
693
694
695
696%%% ============================================================================
697%%% This code is for parsing trailer headers in chunked messages.
698%%% Will be deprecated whenever I have found an alternative working solution!
699%%% Note:
700%%% - The header names are returned slightly different from what the what
701%%%   inet_drv returns
702read_headers_old(Scheme,Socket,Timeout) ->
703    read_headers_old(<<>>,Scheme,Socket,Timeout,[],[]).
704
705read_headers_old(<<>>,Scheme,Socket,Timeout,Acc,AccHdrs) ->
706    read_headers_old(read_more_data(Scheme,Socket,1,Timeout),
707		     Scheme,Socket,Timeout,Acc,AccHdrs);
708read_headers_old(<<$\r>>,Scheme,Socket,Timeout,Acc,AccHdrs) ->
709    read_headers_old(<<$\r,(read_more_data(Scheme,Socket,1,Timeout))/binary>>,
710		     Scheme,Socket,Timeout,Acc,AccHdrs);
711read_headers_old(<<$\r,$\n>>,Scheme,Socket,Timeout,Acc,AccHdrs) ->
712    if
713	Acc==[] -> % Done!
714            tagup_header(lists:reverse(AccHdrs));
715        true ->
716            read_headers_old(read_more_data(Scheme,Socket,1,Timeout),
717			     Scheme,Socket,
718			     Timeout,[],[lists:reverse(Acc)|AccHdrs])
719    end;
720read_headers_old(<<C>>,Scheme,Socket,Timeout,Acc,AccHdrs) ->
721    read_headers_old(read_more_data(Scheme,Socket,1,Timeout),
722		     Scheme,Socket,Timeout,[C|Acc],AccHdrs);
723read_headers_old(Bin,_Scheme,_Socket,_Timeout,_Acc,_AccHdrs) ->
724    io:format("ERROR: Unexpected data from inet driver: ~p",[Bin]),
725    throw({error,this_is_a_bug}).
726
727
728%% Parses the header of a HTTP request and returns a key,value tuple
729%% list containing Name and Value of each header directive as of:
730%%
731%% Content-Type: multipart/mixed -> {"Content-Type", "multipart/mixed"}
732%%
733%% But in http/1.1 the field-names are case insencitive so now it must be
734%% Content-Type: multipart/mixed -> {"content-type", "multipart/mixed"}
735%% The standard furthermore says that leading and traling white space
736%% is not a part of the fieldvalue and shall therefore be removed.
737tagup_header([]) ->          [];
738tagup_header([Line|Rest]) -> [tag(Line, [])|tagup_header(Rest)].
739
740tag([], Tag) ->
741    {httpd_util:to_lower(lists:reverse(Tag)), ""};
742tag([$:|Rest], Tag) ->
743    {httpd_util:to_lower(lists:reverse(Tag)), httpd_util:strip(Rest)};
744tag([Chr|Rest], Tag) ->
745    tag(Rest, [Chr|Tag]).
746