1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2001-2016. 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 22-module(mod_responsecontrol). 23-export([do/1]). 24 25-include("httpd.hrl"). 26-include("httpd_internal.hrl"). 27 28do(Info) -> 29 case proplists:get_value(status, Info#mod.data) of 30 %% A status code has been generated! 31 {_StatusCode, _PhraseArgs, _Reason} -> 32 {proceed, Info#mod.data}; 33 %% No status code has been generated! 34 undefined -> 35 case proplists:get_value(response, Info#mod.data) of 36 %% No response has been generated! 37 undefined -> 38 case do_responsecontrol(Info) of 39 continue -> 40 {proceed, Info#mod.data}; 41 Response -> 42 {proceed,[Response | Info#mod.data]} 43 end; 44 %% A response has been generated or sent! 45 _Response -> 46 {proceed, Info#mod.data} 47 end 48 end. 49 50%%---------------------------------------------------------------------- 51%%Control that the request header did not contians any limitations 52%%wheather a response shall be createed or not 53%%---------------------------------------------------------------------- 54do_responsecontrol(Info) -> 55 Path = mod_alias:path(Info#mod.data, Info#mod.config_db, 56 Info#mod.request_uri), 57 case file:read_file_info(Path) of 58 {ok, FileInfo} -> 59 control(Path, Info, FileInfo); 60 _ -> 61 %% The requested asset is not a plain file and then it must 62 %% be generated everytime its requested 63 continue 64 end. 65 66%%---------------------------------------------------------------------- 67%%Control the If-Match, If-None-Match, and If-Modified-Since 68%%---------------------------------------------------------------------- 69 70 71%% If a client sends more then one of the if-XXXX fields in a request 72%% The standard says it does not specify the behaviour so I specified it :-) 73%% The priority between the fields is 74%% 1.If-modified 75%% 2.If-Unmodified 76%% 3.If-Match 77%% 4.If-Nomatch 78 79%% This means if more than one of the fields are in the request the 80%% field with highest priority will be used 81 82%%If the request is a range request the If-Range field will be the winner. 83 84control(Path, Info, FileInfo) -> 85 case control_range(Path, Info, FileInfo) of 86 undefined -> 87 case control_Etag(Path, Info, FileInfo) of 88 undefined -> 89 case control_modification(Path, Info, FileInfo) of 90 continue -> 91 continue; 92 ReturnValue -> 93 send_return_value(ReturnValue, FileInfo) 94 end; 95 continue -> 96 continue; 97 ReturnValue -> 98 send_return_value(ReturnValue, FileInfo) 99 end; 100 Response-> 101 Response 102 end. 103 104%%---------------------------------------------------------------------- 105%%If there are both a range and an if-range field control if 106%%---------------------------------------------------------------------- 107control_range(Path,Info,FileInfo) -> 108 case proplists:get_value("range", Info#mod.parsed_header) of 109 undefined-> 110 undefined; 111 _Range -> 112 case proplists:get_value("if-range", Info#mod.parsed_header) of 113 undefined -> 114 undefined; 115 EtagOrDate -> 116 control_if_range(Path,Info,FileInfo,EtagOrDate) 117 end 118 end. 119 120control_if_range(_Path, Info, FileInfo, EtagOrDate) -> 121 case httpd_util:convert_request_date(strip_date(EtagOrDate)) of 122 bad_date -> 123 FileEtag=httpd_util:create_etag(FileInfo), 124 case FileEtag of 125 EtagOrDate -> 126 continue; 127 _ -> 128 {if_range,send_file} 129 end; 130 _ErlDate -> 131 %%We got the date in the request if it is 132 case control_modification_data(Info, FileInfo#file_info.mtime, 133 "if-range") of 134 modified -> 135 {if_range,send_file}; 136 _UnmodifiedOrUndefined-> 137 continue 138 end 139 end. 140 141%%---------------------------------------------------------------------- 142%%Controls the values of the If-Match and I-None-Mtch 143%%---------------------------------------------------------------------- 144control_Etag(Path, Info, FileInfo)-> 145 FileEtag = httpd_util:create_etag(FileInfo), 146 %%Control if the E-Tag for the resource matches one of the Etags in 147 %%the -if-match header field 148 case control_match(Info, FileInfo, "if-match", FileEtag) of 149 nomatch -> 150 %%None of the Etags in the if-match field matched the current 151 %%Etag for the resource return a 304 152 {412, Info, Path}; 153 match -> 154 continue; 155 undefined -> 156 case control_match(Info, FileInfo, "if-none-match", FileEtag) of 157 nomatch -> 158 continue; 159 match -> 160 case Info#mod.method of 161 "GET" -> 162 {304, Info, Path}; 163 "HEAD" -> 164 {304, Info, Path}; 165 _OtherrequestMethod -> 166 {412, Info, Path} 167 end; 168 undefined -> 169 undefined 170 end 171 end. 172 173%%---------------------------------------------------------------------- 174%%Control if there are any Etags for HeaderField in the request if so 175%%Control if they match the Etag for the requested file 176%%---------------------------------------------------------------------- 177control_match(Info, _FileInfo, HeaderField, FileEtag)-> 178 case split_etags(proplists:get_value(HeaderField, 179 Info#mod.parsed_header)) of 180 undefined-> 181 undefined; 182 Etags-> 183 %%Control that the match any star not is availible 184 case lists:member("*",Etags) of 185 true-> 186 match; 187 false-> 188 compare_etags(FileEtag, Etags) 189 end 190 end. 191 192%%---------------------------------------------------------------------- 193%%Split the etags from the request 194%%---------------------------------------------------------------------- 195split_etags(undefined)-> 196 undefined; 197split_etags(Tags) -> 198 string:tokens(Tags,", "). 199 200%%---------------------------------------------------------------------- 201%%Control if the etag for the file is in the list 202%%---------------------------------------------------------------------- 203compare_etags(Tag,Etags) -> 204 case lists:member(Tag,Etags) of 205 true -> 206 match; 207 _ -> 208 nomatch 209 end. 210 211%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 212%% %% 213%% Control if the file is modified %% 214%% %% 215%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 216 217%%---------------------------------------------------------------------- 218%% Control the If-Modified-Since and If-Not-Modified-Since header fields 219%%---------------------------------------------------------------------- 220control_modification(Path,Info,FileInfo)-> 221 case control_modification_data(Info, 222 FileInfo#file_info.mtime, 223 "if-modified-since") of 224 modified-> 225 continue; 226 unmodified-> 227 {304, Info, Path}; 228 {bad_date, _} = BadDate-> 229 {400, Info, BadDate}; 230 undefined -> 231 case control_modification_data(Info, 232 FileInfo#file_info.mtime, 233 "if-unmodified-since") of 234 modified -> 235 {412, Info, Path}; 236 _ContinueUndefined -> 237 continue 238 end 239 end. 240 241%%---------------------------------------------------------------------- 242%%Controls the date from the http-request if-modified-since and 243%%if-not-modified-since against the modification data of the 244%%File 245%%---------------------------------------------------------------------- 246%%Info is the record about the request 247%%ModificationTime is the time the file was edited last 248%%Header Field is the name of the field to control 249 250control_modification_data(Info, ModificationTime, HeaderField)-> 251 case strip_date(proplists:get_value(HeaderField, 252 Info#mod.parsed_header)) of 253 undefined-> 254 undefined; 255 LastModified0 -> 256 case httpd_util:convert_request_date(LastModified0) of 257 bad_date -> 258 {bad_date, LastModified0}; 259 ConvertedReqDate -> 260 LastModified = calendar:universal_time_to_local_time(ConvertedReqDate), 261 FileTime = 262 calendar:datetime_to_gregorian_seconds(ModificationTime), 263 FieldTime = 264 calendar:datetime_to_gregorian_seconds(LastModified), 265 if 266 FileTime =< FieldTime -> 267 unmodified; 268 FileTime >= FieldTime -> 269 modified 270 end 271 272 end 273 end. 274 275%% IE4 & NS4 sends an extra '; length=xxxx' string at the end of the If-Modified-Since 276%% header, we detect this and ignore it (the RFCs does not mention this). 277strip_date(undefined) -> 278 undefined; 279strip_date([]) -> 280 []; 281strip_date([$;,$ | _]) -> 282 []; 283strip_date([C | Rest]) -> 284 [C | strip_date(Rest)]. 285 286send_return_value({412,_,_}, _FileInfo)-> 287 {status,{412,none,"Precondition Failed"}}; 288 289send_return_value({400,_, {bad_date, BadDate}}, _FileInfo)-> 290 {status, {400, none, "Bad date: " ++ BadDate}}; 291 292send_return_value({304,Info,Path}, FileInfo)-> 293 Suffix = httpd_util:strip_extension_dot(Path), 294 MimeType = httpd_util:lookup_mime_default(Info#mod.config_db,Suffix, 295 "text/plain"), 296 LastModified = 297 case (catch httpd_util:rfc1123_date(FileInfo#file_info.mtime)) of 298 Date when is_list(Date) -> 299 [{last_modified, Date}]; 300 _ -> %% This will rarly happen, but could happen 301 %% if a computer is wrongly configured. 302 [] 303 end, 304 305 Header = [{code,304}, 306 {etag, httpd_util:create_etag(FileInfo)}, 307 {content_length,"0"}, {mime_type, MimeType} | LastModified], 308 {response, {response, Header, nobody}}. 309