1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2005-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(http_request).
22
23-include("http_internal.hrl").
24
25-export([headers/2, http_headers/1, is_absolut_uri/1, key_value/1, normalize_host/3]).
26
27
28key_value(KeyValueStr) ->
29    case lists:splitwith(fun($:) -> false; (_) -> true end, KeyValueStr) of
30	{Key, [$: | Value]} when Key =/= [] ->
31            %% RFC 7230 - 3.2.4 ... No whitespace is allowed between the header field-name and colon.
32            case string:strip(Key, right) of
33                Key ->
34                    {http_util:to_lower(string:strip(Key, left)),  string:strip(Value)};
35                 _ ->
36                    %% Ignore invalid header
37                    undefined
38            end;
39	{_, []} ->
40	    undefined;
41        _ ->
42            undefined
43    end.
44%%-------------------------------------------------------------------------
45%% headers(HeaderList, #http_request_h{}) -> #http_request_h{}
46%%   HeaderList - ["HeaderField:Value"]
47%%   HeaderField - string()
48%%   Value - string()
49%%
50%% Description: Creates a http_request_h-record used internally to
51%%              handle http-headers.
52%%-------------------------------------------------------------------------
53headers([], Headers) ->
54    Headers;
55headers([{Key, Value} | Tail], Headers) ->
56    headers(Tail, headers(Key, Value, Headers));
57headers([undefined], Headers) ->
58    Headers;
59headers(KeyValues, Headers) ->
60    headers([key_value(KeyValue) || KeyValue <-  KeyValues], Headers).
61
62%%-------------------------------------------------------------------------
63%% headers(#http_request_h{}) -> HeaderList
64%%   HeaderList - ["HeaderField:Value"]
65%%   HeaderField - string()
66%%   Value - string()
67%%
68%% Description: Creates a HTTP header string.
69%%-------------------------------------------------------------------------
70http_headers(Headers = #http_request_h{other = Other}) ->
71    HeaderFields = record_info(fields, http_request_h) -- [other],
72    HeaderStr = lists:foldl(fun(Key, Acc) ->
73				    case key_value_str(Key, Headers) of
74					undefined ->
75					    Acc;
76					Str ->
77					    [Str | Acc]
78				    end
79			    end,
80			    [], HeaderFields),
81
82    lists:flatten([HeaderStr | headers_other(Other, [])]).
83
84%%-------------------------------------------------------------------------
85%% is_absolut_uri(URI) -> true | false
86%%   URI - string()
87%%
88%% Description: Checks if an URI is absolute or relative
89%%-------------------------------------------------------------------------
90is_absolut_uri("http://" ++ _) ->
91    true;
92is_absolut_uri("https://" ++ _) ->
93    true;
94is_absolut_uri(_) ->
95    false.
96
97%%-------------------------------------------------------------------------
98%% normalize_host(Scheme, Host, Port) -> string()
99%%   Scheme - http | https
100%%   Host - string()
101%%   Port - integer()
102%%
103%% Description: returns a normalized Host header value, with the port
104%% number omitted for well-known ports
105%%-------------------------------------------------------------------------
106normalize_host(https, Host, Port) when Port =:= 443 orelse
107                                       Port =:= undefined ->
108    Host;
109normalize_host(http, Host, Port) when Port =:= 80 orelse
110                                      Port =:= undefined ->
111    Host;
112normalize_host(_Scheme, Host, Port) ->
113    Host ++ ":" ++ integer_to_list(Port).
114
115%%%========================================================================
116%%% Internal functions
117%%%========================================================================
118
119%%% --- Request headers
120headers("accept", Value, Headers) ->
121    Headers#http_request_h{accept = Value};
122headers("accept-charset", Value, Headers) ->
123    Headers#http_request_h{'accept-charset' = Value};
124headers("accept-encoding", Value, Headers) ->
125    Headers#http_request_h{'accept-encoding' = Value};
126headers("accept-language", Value, Headers) ->
127    Headers#http_request_h{'accept-language' = Value};
128headers("authorization", Value, Headers) ->
129    Headers#http_request_h{authorization = Value};
130headers("expect", Value, Headers) ->
131    Headers#http_request_h{expect = Value};
132headers("from", Value, Headers) ->
133    Headers#http_request_h{from = Value};
134headers("host", Value, Headers) ->
135    Headers#http_request_h{host = Value};
136headers("if-match", Value, Headers) ->
137    Headers#http_request_h{'if-match' = Value};
138headers("if-modified-since", Value, Headers) ->
139    Headers#http_request_h{'if-modified-since' = Value};
140headers("if-none-match", Value, Headers) ->
141    Headers#http_request_h{'if-none-match' = Value};
142headers("if-range", Value, Headers) ->
143    Headers#http_request_h{'if-range' = Value};
144headers("if-unmodified-since", Value, Headers) ->
145    Headers#http_request_h{'if-unmodified-since' = Value};
146headers("max-forwards", Value, Headers) ->
147    Headers#http_request_h{'max-forwards' = Value};
148headers("proxy-authorization", Value, Headers) ->
149    Headers#http_request_h{'proxy-authorization' = Value};
150headers("range", Value, Headers) ->
151    Headers#http_request_h{range = Value};
152headers("referer", Value, Headers) ->
153    Headers#http_request_h{referer = Value};
154headers("te", Value, Headers) ->
155    Headers#http_request_h{te = Value};
156headers("user-agent", Value, Headers) ->
157    Headers#http_request_h{'user-agent' = Value};
158
159%% General-Headers
160headers("cache-control", Value, Headers) ->
161    Headers#http_request_h{'cache-control' = Value};
162headers("connection", Value, Headers) ->
163    Headers#http_request_h{connection = Value};
164headers("date", Value, Headers) ->
165    Headers#http_request_h{date = Value};
166headers("pragma", Value, Headers) ->
167    Headers#http_request_h{pragma = Value};
168headers("trailer", Value, Headers) ->
169    Headers#http_request_h{trailer = Value};
170headers("transfer-encoding", Value, Headers) ->
171    Headers#http_request_h{'transfer-encoding' = Value};
172headers("upgrade", Value, Headers) ->
173    Headers#http_request_h{upgrade = Value};
174headers("via", Value, Headers) ->
175    Headers#http_request_h{via = Value};
176headers("warning", Value, Headers) ->
177    Headers#http_request_h{warning = Value};
178
179%% Entity header
180headers("allow", Value, Headers) ->
181    Headers#http_request_h{allow = Value};
182headers("content-encoding", Value, Headers) ->
183    Headers#http_request_h{'content-encoding' = Value};
184headers("content-language", Value, Headers) ->
185    Headers#http_request_h{'content-language' = Value};
186headers("content-length", Value, Headers) ->
187    Headers#http_request_h{'content-length' = Value};
188headers("content-location", Value, Headers) ->
189    Headers#http_request_h{'content-location' = Value};
190headers("content-md5", Value, Headers) ->
191    Headers#http_request_h{'content-md5' = Value};
192headers("content-range", Value, Headers) ->
193    Headers#http_request_h{'content-range' = Value};
194headers("content-type", Value, Headers) ->
195    Headers#http_request_h{'content-type' = Value};
196headers("expires", Value, Headers) ->
197    Headers#http_request_h{expires = Value};
198headers("last-modified", Value, Headers) ->
199    Headers#http_request_h{'last-modified' = Value};
200headers(Key, Value, Headers) ->
201    Headers#http_request_h{other=
202			   [{Key, Value} | Headers#http_request_h.other]}.
203
204key_value_str(Key = 'cache-control', Headers) ->
205    key_value_str(atom_to_list(Key), Headers#http_request_h.'cache-control');
206key_value_str(Key = connection, Headers) ->
207    key_value_str(atom_to_list(Key), Headers#http_request_h.connection);
208key_value_str(Key = date, Headers) ->
209    key_value_str(atom_to_list(Key), Headers#http_request_h.date);
210key_value_str(Key = pragma, Headers) ->
211    key_value_str(atom_to_list(Key), Headers#http_request_h.pragma);
212key_value_str(Key = trailer, Headers) ->
213    key_value_str(atom_to_list(Key), Headers#http_request_h.trailer);
214key_value_str(Key = 'transfer-encoding', Headers) ->
215    key_value_str(atom_to_list(Key),
216		    Headers#http_request_h.'transfer-encoding');
217key_value_str(Key = upgrade, Headers) ->
218    key_value_str(atom_to_list(Key), Headers#http_request_h.upgrade);
219key_value_str(Key = via, Headers) ->
220    key_value_str(atom_to_list(Key), Headers#http_request_h.via);
221key_value_str(Key = warning, Headers) ->
222    key_value_str(atom_to_list(Key), Headers#http_request_h.warning);
223key_value_str(Key = accept, Headers) ->
224    key_value_str(atom_to_list(Key), Headers#http_request_h.accept);
225key_value_str(Key = 'accept-charset', Headers) ->
226    key_value_str(atom_to_list(Key), Headers#http_request_h.'accept-charset');
227key_value_str(Key = 'accept-encoding', Headers) ->
228    key_value_str(atom_to_list(Key), Headers#http_request_h.'accept-encoding');
229key_value_str(Key = 'accept-language', Headers) ->
230    key_value_str(atom_to_list(Key), Headers#http_request_h.'accept-language');
231key_value_str(Key = authorization, Headers) ->
232    key_value_str(atom_to_list(Key),
233		    Headers#http_request_h.authorization);
234key_value_str(Key = expect, Headers) ->
235    key_value_str(atom_to_list(Key), Headers#http_request_h.expect);
236key_value_str(Key = from, Headers) ->
237    key_value_str(atom_to_list(Key), Headers#http_request_h.from);
238key_value_str(Key = host, Headers) ->
239    key_value_str(atom_to_list(Key), Headers#http_request_h.host);
240key_value_str(Key = 'if-match', Headers) ->
241    key_value_str(atom_to_list(Key),
242		    Headers#http_request_h.'if-match');
243key_value_str(Key = 'if-modified-since', Headers) ->
244    key_value_str(atom_to_list(Key),
245		    Headers#http_request_h.'if-modified-since');
246key_value_str(Key = 'if-none-match', Headers) ->
247    key_value_str(atom_to_list(Key),
248		    Headers#http_request_h.'if-none-match');
249key_value_str(Key = 'if-range', Headers) ->
250    key_value_str(atom_to_list(Key),
251		    Headers#http_request_h.'if-range');
252key_value_str(Key = 'if-unmodified-since', Headers) ->
253    key_value_str(atom_to_list(Key),
254		    Headers#http_request_h.'if-unmodified-since');
255key_value_str(Key = 'max-forwards', Headers) ->
256    key_value_str(atom_to_list(Key),
257		    Headers#http_request_h.'max-forwards');
258key_value_str(Key = 'proxy-authorization', Headers) ->
259    key_value_str(atom_to_list(Key),
260		    Headers#http_request_h.'proxy-authorization');
261key_value_str(Key = range, Headers) ->
262    key_value_str(atom_to_list(Key),
263		    Headers#http_request_h.range);
264key_value_str(Key = referer, Headers) ->
265    key_value_str(atom_to_list(Key),
266		    Headers#http_request_h.referer);
267key_value_str(Key = te, Headers) ->
268    key_value_str(atom_to_list(Key),
269		    Headers#http_request_h.te);
270key_value_str(Key = 'user-agent', Headers) ->
271    key_value_str(atom_to_list(Key),
272		    Headers#http_request_h.'user-agent');
273key_value_str(Key = allow, Headers) ->
274    key_value_str(atom_to_list(Key), Headers#http_request_h.allow);
275key_value_str(Key = 'content-encoding', Headers) ->
276    key_value_str(atom_to_list(Key),
277		    Headers#http_request_h.'content-encoding');
278key_value_str(Key = 'content-language', Headers) ->
279    key_value_str(atom_to_list(Key),
280		    Headers#http_request_h.'content-language');
281key_value_str(Key = 'content-length', Headers) ->
282    key_value_str(atom_to_list(Key),
283		    Headers#http_request_h.'content-length');
284key_value_str(Key = 'content-location', Headers) ->
285    key_value_str(atom_to_list(Key),
286		    Headers#http_request_h.'content-location');
287key_value_str(Key = 'content-md5', Headers) ->
288    key_value_str(atom_to_list(Key),
289		    Headers#http_request_h.'content-md5');
290key_value_str(Key = 'content-range', Headers) ->
291    key_value_str(atom_to_list(Key), Headers#http_request_h.'content-range');
292key_value_str(Key = 'content-type', Headers) ->
293    key_value_str(atom_to_list(Key), Headers#http_request_h.'content-type');
294key_value_str(Key = expires, Headers) ->
295    key_value_str(atom_to_list(Key), Headers#http_request_h.expires);
296key_value_str(Key = 'last-modified', Headers) ->
297    key_value_str(atom_to_list(Key), Headers#http_request_h.'last-modified');
298key_value_str(_, undefined) ->
299    undefined;
300key_value_str(Key, Value)  ->
301    Key ++ ": " ++ Value ++ ?CRLF.
302
303headers_other([], Headers) ->
304    Headers;
305headers_other([{Key,Value} | Rest], Headers) ->
306    Header = Key ++ ": " ++ Value ++ ?CRLF,
307    headers_other(Rest, [Header | Headers]).
308