1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 1997-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(httpd_response). 22-export([generate_and_send_response/1, send_status/3, send_status/4, send_header/3, 23 send_body/3, send_chunk/3, send_final_chunk/2, send_final_chunk/3, 24 split_header/2, is_disable_chunked_send/1, cache_headers/2, handle_continuation/1]). 25-export([map_status_code/2]). 26 27-include_lib("inets/src/inets_app/inets_internal.hrl"). 28-include_lib("inets/include/httpd.hrl"). 29-include_lib("inets/src/http_lib/http_internal.hrl"). 30-include_lib("inets/src/http_server/httpd_internal.hrl"). 31-include_lib("kernel/include/logger.hrl"). 32 33-define(VMODULE,"RESPONSE"). 34 35handle_continuation(Mod) -> 36 generate_and_send_response(Mod). 37 38%% If peername does not exist the client already discarded the 39%% request so we do not need to send a reply. 40generate_and_send_response(#mod{init_data = 41 #init_data{peername = {_,"unknown"}}}) -> 42 ok; 43generate_and_send_response(#mod{config_db = ConfigDB} = ModData) -> 44 Modules = httpd_util:lookup(ConfigDB, modules, ?DEFAULT_MODS), 45 case traverse_modules(ModData, Modules) of 46 {continue, _} = Continue -> 47 Continue; 48 done -> 49 ok; 50 {proceed, Data} -> 51 case proplists:get_value(status, Data) of 52 {StatusCode, PhraseArgs, _Reason} -> 53 send_status(ModData, StatusCode, PhraseArgs), 54 ok; 55 undefined -> 56 case proplists:get_value(response, Data) of 57 {already_sent, _StatusCode, _Size} -> 58 ok; 59 {response, Header, Body} -> %% New way 60 send_response(ModData, Header, Body), 61 ok; 62 {StatusCode, Response} -> %% Old way 63 send_response_old(ModData, StatusCode, Response), 64 ok; 65 undefined -> 66 %% Happens when no mod_* 67 %% handles the request 68 send_status(ModData, 501, {ModData#mod.method, 69 ModData#mod.request_uri, 70 ModData#mod.http_version}), 71 ok 72 end 73 end 74 end. 75 76 77%% traverse_modules 78 79traverse_modules(ModData,[]) -> 80 {proceed,ModData#mod.data}; 81traverse_modules(ModData,[Module|Rest]) -> 82 try apply(Module, do, [ModData]) of 83 {continue, _} = Continue -> 84 Continue; 85 done -> 86 done; 87 {break, NewData} -> 88 {proceed, NewData}; 89 {proceed, NewData} -> 90 traverse_modules(ModData#mod{data = NewData}, Rest) 91 catch 92 T:E:Stacktrace -> 93 httpd_util:error_log(ModData#mod.config_db, 94 httpd_logger:error_report('HTTP', 95 [{module, Module}, 96 {class, T}, 97 {error, E}, 98 {stacktrace, Stacktrace}], ModData, ?LOCATION)), 99 send_status(ModData, 500, none), 100 done 101 end. 102 103%% send_status %% 104 105 106send_status(ModData, 100, _PhraseArgs) -> 107 send_header(ModData, 100, [{content_length, "0"}]); 108 109send_status(ModData, StatusCode, PhraseArgs) -> 110 send_status(ModData, StatusCode, PhraseArgs, undefined). 111 112send_status(#mod{socket_type = SocketType, 113 socket = Socket, 114 config_db = ConfigDB} = ModData, StatusCode, PhraseArgs, Details) -> 115 116 ReasonPhrase = httpd_util:reason_phrase(StatusCode), 117 Message = httpd_util:message(StatusCode, PhraseArgs, ConfigDB), 118 Body = get_body(ReasonPhrase, Message), 119 120 send_header(ModData, StatusCode, 121 [{content_type, "text/html"}, 122 {content_length, integer_to_list(length(Body))}]), 123 124 if StatusCode >= 400 -> 125 case Details of 126 undefined -> 127 httpd_util:error_log(ConfigDB, httpd_logger:error_report('HTTP', 128 [{statuscode, StatusCode}, {description, ReasonPhrase}], 129 ModData, ?LOCATION)); 130 _ -> 131 httpd_util:error_log(ConfigDB, httpd_logger:error_report('HTTP', 132 [{statuscode,StatusCode}, {description, ReasonPhrase}, 133 {details, Details}], ModData, 134 ?LOCATION)) 135 end; 136 true -> 137 ok 138 end, 139 httpd_socket:deliver(SocketType, Socket, Body). 140 141 142get_body(ReasonPhrase, Message)-> 143 "<HTML> 144 <HEAD> 145 <TITLE>"++ReasonPhrase++"</TITLE> 146 </HEAD> 147 <BODY> 148 <H1>"++ReasonPhrase++"</H1>\n"++Message++"\n</BODY> 149 </HTML>\n". 150 151 152send_response(ModData, Header, Body) -> 153 case proplists:get_value(code, Header) of 154 undefined -> 155 %% No status code 156 %% Ooops this must be very bad: 157 %% generate a 404 content not availible 158 send_status(ModData, 404, "The file is not availible"); 159 StatusCode -> 160 case send_header(ModData, StatusCode, lists:keydelete(code, 1, 161 Header)) of 162 ok -> 163 send_body(ModData, StatusCode, Body); 164 _ -> 165 done 166 end 167 end. 168 169send_header(#mod{socket_type = Type, 170 socket = Sock, 171 http_version = Ver, 172 connection = Conn, 173 config_db = ConfigDb} = _ModData, 174 StatusCode, KeyValueTupleHeaders) -> 175 Headers = create_header(ConfigDb, 176 lists:map(fun transform/1, KeyValueTupleHeaders)), 177 NewVer = case {Ver, StatusCode} of 178 {[], _} -> 179 %% May be implicit! 180 "HTTP/0.9"; 181 {unknown, 408} -> 182 %% This will proably never happen! It means the 183 %% server has timed out the request without 184 %% receiving a version for the request! Send the 185 %% lowest version so to ensure that the client 186 %% will be able to handle it, probably the 187 %% sensible thing to do! 188 "HTTP/0.9"; 189 {undefined,_} -> 190 "HTTP/1.0"; %% See rfc2145 2.3 last paragraph 191 _ -> 192 Ver 193 end, 194 NewStatusCode = map_status_code(NewVer, StatusCode), 195 StatusLine = [NewVer, " ", io_lib:write(NewStatusCode), " ", 196 httpd_util:reason_phrase(NewStatusCode), ?CRLF], 197 ConnectionHeader = get_connection(Conn, NewVer), 198 Head = [StatusLine, Headers, ConnectionHeader , ?CRLF], 199 httpd_socket:deliver(Type, Sock, Head). 200 201map_status_code("HTTP/1.0", Code) 202 when ((Code div 100) =:= 2) andalso (Code > 204) -> 203 403; 204map_status_code("HTTP/1.0", Code) 205 when ((Code div 100) =:= 3) andalso (Code > 304) -> 206 403; 207map_status_code("HTTP/1.0", Code) 208 when ((Code div 100) =:= 4) andalso (Code > 404) -> 209 403; 210map_status_code("HTTP/1.0", Code) 211 when ((Code div 100) =:= 5) andalso (Code > 503) -> 212 403; 213map_status_code(_, Code) -> 214 Code. 215 216send_body(#mod{socket_type = Type, socket = Socket}, _, nobody) -> 217 httpd_socket:close(Type, Socket), 218 ok; 219 220send_body(#mod{socket_type = Type, socket = Sock}, 221 _StatusCode, Body) when is_list(Body) -> 222 case httpd_socket:deliver(Type, Sock, Body) of 223 socket_closed -> 224 done; 225 Else -> 226 Else 227 end; 228 229send_body(#mod{socket_type = Type, socket = Sock} = ModData, 230 StatusCode, {Fun, Args}) -> 231 case (catch apply(Fun, Args)) of 232 close -> 233 httpd_socket:close(Type, Sock), 234 done; 235 236 sent -> 237 {proceed,[{response,{already_sent, StatusCode, 238 proplists:get_value(content_length, 239 ModData#mod.data)}}]}; 240 {ok, Body} -> 241 case httpd_socket:deliver(Type, Sock, Body) of 242 ok -> 243 {proceed,[{response, 244 {already_sent, StatusCode, 245 proplists:get_value(content_length, 246 ModData#mod.data)}}]}; 247 _ -> 248 done 249 end; 250 251 _ -> 252 done 253 end. 254 255split_header([$: | Value], AccName) -> 256 Name = http_util:to_lower(string:strip(AccName)), 257 {lists:reverse(Name), 258 string:strip(string:strip(string:strip(Value, right, ?LF), right, ?CR))}; 259split_header([Char | Rest], AccName) -> 260 split_header(Rest, [Char | AccName]). 261 262send_chunk(_, <<>>, _) -> 263 ok; 264send_chunk(_, [], _) -> 265 ok; 266send_chunk(#mod{http_version = "HTTP/1.1", 267 socket_type = Type, socket = Sock}, Response0, false) -> 268 Response = http_chunk:encode(Response0), 269 httpd_socket:deliver(Type, Sock, Response); 270 271send_chunk(#mod{socket_type = Type, socket = Sock} = _ModData, Response, _) -> 272 httpd_socket:deliver(Type, Sock, Response). 273 274send_final_chunk(Mod, IsDisableChunkedSend) -> 275 send_final_chunk(Mod, [], IsDisableChunkedSend). 276 277send_final_chunk(#mod{http_version = "HTTP/1.1", 278 socket_type = Type, socket = Sock}, Trailers, false) -> 279 httpd_socket:deliver(Type, Sock, http_chunk:encode_last(Trailers)); 280send_final_chunk(#mod{socket_type = Type, socket = Sock}, _, _) -> 281 httpd_socket:close(Type, Sock). 282 283is_disable_chunked_send(Db) -> 284 httpd_util:lookup(Db, disable_chunked_transfer_encoding_send, false). 285 286%% Return a HTTP-header field that indicates that the 287%% connection will be inpersistent 288get_connection(true,"HTTP/1.0")-> 289 "Connection:close\r\n"; 290get_connection(false,"HTTP/1.1") -> 291 "Connection:close\r\n"; 292get_connection(_,_) -> 293 "". 294 295cache_headers(#mod{config_db = Db}, NoCacheType) -> 296 case httpd_util:lookup(Db, NoCacheType, false) of 297 true -> 298 Date = httpd_util:rfc1123_date(), 299 [{"cache-control", "no-cache"}, 300 {"pragma", "no-cache"}, 301 {"expires", Date}]; 302 false -> 303 [] 304 end. 305 306create_header(ConfigDb, KeyValueTupleHeaders) -> 307 Date = httpd_util:rfc1123_date(), 308 ContentType = "text/html", 309 Server = server(ConfigDb), 310 CustomizeCB = httpd_util:lookup(ConfigDb, customize, httpd_custom), 311 312 CustomDefaults = httpd_custom:response_default_headers(CustomizeCB), 313 SystemDefaultes = ([{"date", Date}, 314 {"content-type", ContentType} 315 | if Server=="" -> []; 316 true -> [{"server", Server}] 317 end 318 ]), 319 320 %% System defaults not present in custom defaults will be added 321 %% to defaults 322 Defaults = add_default_headers(SystemDefaultes, CustomDefaults), 323 324 Headers0 = add_default_headers(Defaults, KeyValueTupleHeaders), 325 lists:filtermap(fun(H) -> 326 httpd_custom:customize_headers(CustomizeCB, response_header, H) 327 end, 328 [Header || Header <- Headers0]). 329server(ConfigDb) -> 330 httpd_util:lookup(ConfigDb, server, ?SERVER_SOFTWARE). 331 332add_default_headers([], Headers) -> 333 Headers; 334 335add_default_headers([Header = {Default, _} | Defaults], Headers) -> 336 case lists:keysearch(Default, 1, Headers) of 337 {value, _} -> 338 add_default_headers(Defaults, Headers); 339 _ -> 340 add_default_headers(Defaults, [Header | Headers]) 341 end. 342 343transform({content_type, Value}) -> 344 {"content-type", Value}; 345transform({accept_ranges, Value}) -> 346 {"accept-ranges", Value}; 347transform({cache_control, Value}) -> 348 {"cache-control",Value}; 349transform({transfer_encoding, Value}) -> 350 {"transfer-encoding", Value}; 351transform({content_encoding, Value}) -> 352 {"content-encoding", Value}; 353transform({content_language, Value}) -> 354 {"content-language", Value}; 355transform({retry_after, Value}) -> 356 {"retry-after", Value}; 357transform({content_location, Value}) -> 358 {"Content-Location:", Value}; 359transform({content_length, Value}) -> 360 {"content-length", Value}; 361transform({content_MD5, Value}) -> 362 {"content-md5", Value}; 363transform({content_range, Value}) -> 364 {"content-range", Value}; 365transform({last_modified, Value}) -> 366 {"last-modified", Value}; 367transform({Field, Value}) when is_atom(Field) -> 368 {atom_to_list(Field), Value}; 369transform({Field, Value}) when is_list(Field) -> 370 {Field, Value}. 371 372%%---------------------------------------------------------------------- 373%% This is the old way of sending data it is strongly encouraged to 374%% Leave this method and go on to the newer form of response 375%% OTP-4408 376%%---------------------------------------------------------------------- 377send_response_old(#mod{method = "HEAD"} = ModData, 378 StatusCode, Response) -> 379 380 NewResponse = lists:flatten(Response), 381 382 case httpd_util:split(NewResponse, [?CR, ?LF, ?CR, ?LF],2) of 383 {ok, [Head, Body]} -> 384 {ok, NewHead} = handle_headers(string:tokens(Head, [?CR,?LF]), []), 385 send_header(ModData, StatusCode, [{content_length, 386 content_length(Body)} | NewHead]); 387 {ok, [NewResponse]} -> 388 send_header(ModData, StatusCode, [{content_length, 389 content_length(NewResponse)}]); 390 _Error -> 391 send_status(ModData, 500, "Internal Server Error") 392 end; 393 394send_response_old(#mod{socket_type = Type, 395 socket = Sock} = ModData, 396 StatusCode, Response) -> 397 398 NewResponse = lists:flatten(Response), 399 400 case httpd_util:split(NewResponse, [?CR, ?LF, ?CR, ?LF], 2) of 401 {ok, [Head, Body]} -> 402 {ok, NewHead} = handle_headers(string:tokens(Head, 403 [?CR,?LF]), []), 404 send_header(ModData, StatusCode, [{content_length, 405 content_length(Body)} | 406 NewHead]), 407 httpd_socket:deliver(Type, Sock, Body); 408 {ok, [NewResponse]} -> 409 send_header(ModData, StatusCode, [{content_length, 410 content_length(NewResponse)}]), 411 httpd_socket:deliver(Type, Sock, NewResponse); 412 _ -> 413 send_status(ModData, 500, "Internal Server Error") 414 end. 415 416content_length(Body)-> 417 integer_to_list(httpd_util:flatlength(Body)). 418 419handle_headers([], NewHeaders) -> 420 {ok, NewHeaders}; 421 422handle_headers([Header | Headers], NewHeaders) -> 423 {FieldName, FieldValue} = split_header(Header, []), 424 handle_headers(Headers, 425 [{FieldName, FieldValue}| NewHeaders]). 426 427