1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2004-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21-module(httpc_request).
22
23-include_lib("inets/src/http_lib/http_internal.hrl").
24-include("httpc_internal.hrl").
25
26%%% Internal API
27-export([send/3, is_idempotent/1, is_client_closing/1]).
28
29
30%%%=========================================================================
31%%%  Internal application API
32%%%=========================================================================
33%%-------------------------------------------------------------------------
34%% send(MaybeProxy, Request) ->
35%%      MaybeProxy - {Host, Port}
36%%      Host = string()
37%%      Port = integer()
38%%	Request - #request{}
39%%	Socket - socket()
40%%      CookieSupport - enabled | disabled | verify
41%%
42%% Description: Composes and sends a HTTP-request.
43%%-------------------------------------------------------------------------
44send(SendAddr, #session{socket = Socket, socket_type = SocketType},
45     #request{socket_opts = SocketOpts} = Request)
46  when is_list(SocketOpts) ->
47    case http_transport:setopts(SocketType, Socket, SocketOpts) of
48	ok ->
49	    send(SendAddr, Socket, SocketType,
50		 Request#request{socket_opts = undefined});
51	{error, Reason} ->
52	    {error, {setopts_failed, Reason}}
53    end;
54send(SendAddr, #session{socket = Socket, socket_type = SocketType}, Request) ->
55    send(SendAddr, Socket, SocketType, Request).
56
57send(SendAddr, Socket, SocketType,
58     #request{method        = Method,
59	      path          = Path,
60	      pquery        = Query,
61	      headers       = Headers,
62	      content       = Content,
63	      address       = Address,
64	      abs_uri       = AbsUri,
65	      headers_as_is = HeadersAsIs,
66	      settings      = HttpOptions,
67	      userinfo      = UserInfo}) ->
68
69    ?hcrt("send",
70	  [{send_addr,     SendAddr},
71	   {socket,        Socket},
72	   {method,        Method},
73	   {path,          Path},
74	   {pquery,        Query},
75	   {headers,       Headers},
76	   {content,       Content},
77	   {address,       Address},
78	   {abs_uri,       AbsUri},
79	   {headers_as_is, HeadersAsIs},
80	   {settings,      HttpOptions},
81	   {userinfo,      UserInfo}]),
82
83    TmpHdrs = handle_user_info(UserInfo, Headers),
84
85    {TmpHdrs2, Body} = post_data(Method, TmpHdrs, Content, HeadersAsIs),
86
87    {NewHeaders, Uri} =
88	case Address of
89	    SendAddr ->
90		{TmpHdrs2, Path ++ Query};
91	    _Proxy when SocketType == ip_comm ->
92		TmpHdrs3 = handle_proxy(HttpOptions, TmpHdrs2),
93		{TmpHdrs3, AbsUri};
94	    _  ->
95		{TmpHdrs2, Path ++ Query}
96	end,
97
98    FinalHeaders =
99	case NewHeaders of
100	    HeaderList when is_list(HeaderList) ->
101		http_headers(HeaderList, []);
102	    _  ->
103		http_request:http_headers(NewHeaders)
104	end,
105    Version = HttpOptions#http_options.version,
106
107    do_send_body(SocketType, Socket, Method, Uri, Version, FinalHeaders, Body).
108
109
110do_send_body(SocketType, Socket, Method, Uri, Version, Headers,
111	     {ProcessBody, Acc}) when is_function(ProcessBody, 1) ->
112    ?hcrt("send", [{acc, Acc}]),
113    case do_send_body(SocketType, Socket, Method, Uri, Version, Headers, []) of
114        ok ->
115            do_send_body(SocketType, Socket, ProcessBody, Acc);
116        Error ->
117            Error
118    end;
119
120do_send_body(SocketType, Socket, Method, Uri, Version, Headers, Body) ->
121    ?hcrt("create message", [{body, Body}]),
122    Message = [method(Method), " ", Uri, " ",
123	       version(Version), ?CRLF,
124	       headers(Headers, Version), ?CRLF, Body],
125    ?hcrd("send", [{message, Message}]),
126    http_transport:send(SocketType, Socket, Message).
127
128
129do_send_body(SocketType, Socket, ProcessBody, Acc) ->
130    case ProcessBody(Acc) of
131        eof ->
132            ok;
133        {ok, Data, NewAcc} ->
134            case http_transport:send(SocketType, Socket, Data) of
135                ok ->
136                    do_send_body(SocketType, Socket, ProcessBody, NewAcc);
137                Error ->
138                    Error
139            end
140    end.
141
142
143%%-------------------------------------------------------------------------
144%% is_idempotent(Method) ->
145%% Method = atom()
146%%
147%% Description: Checks if Method is considered idempotent.
148%%-------------------------------------------------------------------------
149
150%% In particular, the convention has been established that the GET and
151%% HEAD methods SHOULD NOT have the significance of taking an action
152%% other than retrieval. These methods ought to be considered "safe".
153is_idempotent(head) ->
154    true;
155is_idempotent(get) ->
156    true;
157%% Methods can also have the property of "idempotence" in that (aside
158%% from error or expiration issues) the side-effects of N > 0
159%% identical requests is the same as for a single request.
160is_idempotent(put) ->
161    true;
162is_idempotent(delete) ->
163    true;
164%% Also, the methods OPTIONS and TRACE SHOULD NOT have side effects,
165%% and so are inherently idempotent.
166is_idempotent(trace) ->
167    true;
168is_idempotent(options) ->
169    true;
170is_idempotent(_) ->
171    false.
172
173%%-------------------------------------------------------------------------
174%% is_client_closing(Headers) ->
175%% Headers = #http_request_h{}
176%%
177%% Description: Checks if the client has supplied a "Connection:
178%% close" header.
179%%-------------------------------------------------------------------------
180is_client_closing(Headers) ->
181    case Headers#http_request_h.connection of
182	"close" ->
183	    true;
184	 _ ->
185	    false
186    end.
187
188%%%========================================================================
189%%% Internal functions
190%%%========================================================================
191post_data(Method, Headers, {ContentType, Body}, HeadersAsIs)
192    when (Method =:= post)
193         orelse (Method =:= put)
194         orelse (Method =:= patch)
195         orelse (Method =:= delete) ->
196    NewBody = update_body(Headers, Body),
197    NewHeaders = update_headers(Headers, ContentType, Body, HeadersAsIs),
198    {NewHeaders, NewBody};
199
200post_data(_, Headers, _, []) ->
201    {Headers, ""};
202post_data(_, _, _, HeadersAsIs = [_|_]) ->
203    {HeadersAsIs, ""}.
204
205update_body(Headers, Body) ->
206    case Headers#http_request_h.expect of
207        "100-continue" ->
208            "";
209        _ ->
210            Body
211    end.
212
213update_headers(Headers, ContentType, Body, []) ->
214    case Body of
215        [] ->
216            Headers1 = Headers#http_request_h{'content-length' = "0"},
217            handle_content_type(Headers1, ContentType);
218        <<>> ->
219            Headers1 = Headers#http_request_h{'content-length' = "0"},
220            handle_content_type(Headers1, ContentType);
221        {Fun, _Acc} when is_function(Fun, 1) ->
222            %% A client MUST NOT generate a 100-continue expectation in a request
223            %% that does not include a message body. This implies that either the
224            %% Content-Length or the Transfer-Encoding header MUST be present.
225            %% DO NOT send content-type when Body is empty.
226            Headers1 = Headers#http_request_h{'content-type' = ContentType},
227            handle_transfer_encoding(Headers1);
228        _ ->
229            Headers#http_request_h{
230              'content-length' = body_length(Body),
231              'content-type' = ContentType}
232    end;
233update_headers(_, _, _, HeadersAsIs) ->
234    HeadersAsIs.
235
236handle_transfer_encoding(Headers = #http_request_h{'transfer-encoding' = undefined}) ->
237    Headers;
238handle_transfer_encoding(Headers) ->
239    %% RFC7230 3.3.2
240    %% A sender MUST NOT send a 'Content-Length' header field in any message
241    %% that contains a 'Transfer-Encoding' header field.
242    Headers#http_request_h{'content-length' = undefined}.
243
244body_length(Body) when is_binary(Body) ->
245   integer_to_list(size(Body));
246
247body_length(Body) when is_list(Body) ->
248  integer_to_list(length(Body)).
249
250%% Set 'Content-Type' when it is explicitly set.
251handle_content_type(Headers, "") ->
252    Headers;
253handle_content_type(Headers, ContentType) ->
254    Headers#http_request_h{'content-type' = ContentType}.
255
256method(Method) ->
257    http_util:to_upper(atom_to_list(Method)).
258
259version("HTTP/0.9") ->
260    "";
261version(Version) ->
262    Version.
263
264headers(_, "HTTP/0.9") ->
265    "";
266%% HTTP 1.1 headers not present in HTTP 1.0 should be
267%% consider as unknown extension headers that should be
268%% ignored.
269headers(Headers, _) ->
270    Headers.
271
272
273http_headers([], Headers) ->
274    lists:flatten(Headers);
275http_headers([{Key,Value} | Rest], Headers) ->
276    Header = Key ++ ": " ++ Value ++ ?CRLF,
277    http_headers(Rest, [Header | Headers]).
278
279handle_proxy(_, Headers) when is_list(Headers) ->
280    Headers; %% Headers as is option was specified
281handle_proxy(HttpOptions, Headers) ->
282    case HttpOptions#http_options.proxy_auth of
283	undefined ->
284	    Headers;
285	{User, Password} ->
286	    UserPasswd = base64:encode_to_string(User ++ ":" ++ Password),
287	    Headers#http_request_h{'proxy-authorization' =
288				   "Basic " ++ UserPasswd}
289    end.
290
291handle_user_info([], Headers) ->
292    Headers;
293handle_user_info(UserInfo, Headers) ->
294    case string:tokens(UserInfo, ":") of
295	[User, Passwd] ->
296	    UserPasswd = base64:encode_to_string(User ++ ":" ++ Passwd),
297	    Headers#http_request_h{authorization = "Basic " ++ UserPasswd};
298	[User] ->
299	    UserPasswd = base64:encode_to_string(User ++ ":"),
300	    Headers#http_request_h{authorization = "Basic " ++ UserPasswd};
301	_ ->
302	    Headers
303    end.
304