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(mod_alias). 22 23-export([do/1, 24 real_name/3, 25 real_script_name/3, 26 default_index/2, 27 store/2, 28 path/3]). 29 30-include("httpd.hrl"). 31-include("httpd_internal.hrl"). 32-include("inets_internal.hrl"). 33 34-define(VMODULE,"ALIAS"). 35 36%% do 37 38do(#mod{data = Data} = Info) -> 39 case proplists:get_value(status, Data) of 40 %% A status code has been generated! 41 {_StatusCode, _PhraseArgs, _Reason} -> 42 {proceed, Data}; 43 %% No status code has been generated! 44 undefined -> 45 case proplists:get_value(response, Data) of 46 %% No response has been generated! 47 undefined -> 48 do_alias(Info); 49 %% A response has been generated or sent! 50 _Response -> 51 {proceed, Data} 52 end 53 end. 54 55do_alias(#mod{config_db = ConfigDB, 56 request_uri = ReqURI, 57 socket_type = SocketType, 58 data = Data}) -> 59 {ShortPath, Path, AfterPath} = 60 real_name(ConfigDB, ReqURI, which_alias(ConfigDB)), 61 %% Relocate if a trailing slash is missing else proceed! 62 LastChar = lists:last(ShortPath), 63 case file:read_file_info(ShortPath) of 64 {ok, FileInfo} when ((FileInfo#file_info.type =:= directory) andalso 65 (LastChar =/= $/)) -> 66 ServerName = which_server_name(ConfigDB), 67 Port = port_string(which_port(ConfigDB)), 68 Protocol = get_protocol(SocketType), 69 URL = Protocol ++ ServerName ++ Port ++ ReqURI ++ "/", 70 ReasonPhrase = httpd_util:reason_phrase(301), 71 Message = httpd_util:message(301, URL, ConfigDB), 72 {proceed, 73 [{response, 74 {301, ["Location: ", URL, "\r\n" 75 "Content-Type: text/html\r\n", 76 "\r\n", 77 "<HTML>\n<HEAD>\n<TITLE>",ReasonPhrase, 78 "</TITLE>\n</HEAD>\n" 79 "<BODY>\n<H1>",ReasonPhrase, 80 "</H1>\n", Message, 81 "\n</BODY>\n</HTML>\n"]}}| 82 [{real_name, {Path, AfterPath}} | Data]]}; 83 _NoFile -> 84 {proceed, [{real_name, {Path, AfterPath}} | Data]} 85 end. 86 87port_string(80) -> 88 ""; 89port_string(Port) -> 90 ":" ++ integer_to_list(Port). 91 92get_protocol(ip_comm) -> 93 "http://"; 94get_protocol(_) -> 95 %% Should clean up to have only one ssl type essl vs ssl is not relevant any more 96 "https://". 97 98%% real_name 99 100real_name(ConfigDB, RequestURI, []) -> 101 {Prefix, DocumentRoot} = which_document_root(ConfigDB), 102 RealName = DocumentRoot ++ RequestURI, 103 {ShortPath, _AfterPath} = httpd_util:split_path(RealName), 104 {Path, AfterPath} = 105 httpd_util:split_path(default_index(ConfigDB, RealName)), 106 {Prefix ++ ShortPath, Prefix ++ Path, AfterPath}; 107real_name(ConfigDB, RequestURI, [{MP,Replacement}| _] = Aliases) 108 when element(1, MP) =:= re_pattern -> 109 case longest_match(Aliases, RequestURI) of 110 {match, {MP, Replacement}} -> 111 NewURI = re:replace(RequestURI, MP, Replacement, [{return,list}]), 112 {ShortPath,_} = httpd_util:split_path(NewURI), 113 {Path,AfterPath} = 114 httpd_util:split_path(default_index(ConfigDB, NewURI)), 115 {ShortPath, Path, AfterPath}; 116 nomatch -> 117 real_name(ConfigDB, RequestURI, []) 118 end; 119 120real_name(ConfigDB, RequestURI, [{_,_}|_] = Aliases) -> 121 case longest_match(Aliases, RequestURI) of 122 {match, {FakeName, RealName}} -> 123 ActualName = re:replace(RequestURI, 124 "^" ++ FakeName, RealName, [{return,list}]), 125 {ShortPath, _AfterPath} = httpd_util:split_path(ActualName), 126 {Path, AfterPath} = 127 httpd_util:split_path(default_index(ConfigDB, ActualName)), 128 {ShortPath, Path, AfterPath}; 129 nomatch -> 130 real_name(ConfigDB, RequestURI, []) 131 end. 132 133longest_match(Aliases, RequestURI) -> 134 longest_match(Aliases, RequestURI, _LongestNo = 0, _LongestAlias = undefined). 135 136longest_match([{FakeName, RealName} | Rest], RequestURI, LongestNo, LongestAlias) -> 137 case re:run(RequestURI, "^" ++ FakeName, [{capture, first}]) of 138 {match, [{_, Length}]} -> 139 if 140 Length > LongestNo -> 141 longest_match(Rest, RequestURI, Length, {FakeName, RealName}); 142 true -> 143 longest_match(Rest, RequestURI, LongestNo, LongestAlias) 144 end; 145 nomatch -> 146 longest_match(Rest, RequestURI, LongestNo, LongestAlias) 147 end; 148longest_match([], _RequestURI, 0, _LongestAlias) -> 149 nomatch; 150longest_match([], _RequestURI, _LongestNo, LongestAlias) -> 151 {match, LongestAlias}. 152 153%% real_script_name 154 155real_script_name(_ConfigDB, _RequestURI, []) -> 156 not_a_script; 157real_script_name(ConfigDB, RequestURI, [{FakeName,RealName} | Rest]) -> 158 case re:run(RequestURI, "^" ++ FakeName, [{capture, none}]) of 159 match -> 160 ActualName0 = 161 re:replace(RequestURI, "^" ++ FakeName, RealName, [{return,list}]), 162 ActualName = abs_script_path(ConfigDB, ActualName0), 163 httpd_util:split_script_path(default_index(ConfigDB, ActualName)); 164 nomatch -> 165 real_script_name(ConfigDB, RequestURI, Rest) 166 end. 167 168%% ERL-574: relative path in script_alias property results in malformed url 169abs_script_path(ConfigDB, [$.|_] = RelPath) -> 170 Root = httpd_util:lookup(ConfigDB, server_root), 171 Root ++ "/" ++ RelPath; 172abs_script_path(_, RelPath) -> 173 RelPath. 174 175%% default_index 176 177default_index(ConfigDB, Path) -> 178 case file:read_file_info(Path) of 179 {ok, FileInfo} when FileInfo#file_info.type =:= directory -> 180 DirectoryIndex = which_directory_index(ConfigDB), 181 append_index(Path, DirectoryIndex); 182 _ -> 183 Path 184 end. 185 186append_index(RealName, []) -> 187 RealName; 188append_index(RealName, [Index | Rest]) -> 189 case file:read_file_info(filename:join(RealName, Index)) of 190 {error, _Reason} -> 191 append_index(RealName, Rest); 192 _ -> 193 filename:join(RealName, Index) 194 end. 195 196%% path 197 198path(Data, ConfigDB, RequestURI0) -> 199 case proplists:get_value(real_name, Data) of 200 undefined -> 201 {Prefix, DocumentRoot} = which_document_root(ConfigDB), 202 RequestURI = percent_decode_path(RequestURI0), 203 {Path, _AfterPath} = 204 httpd_util:split_path(DocumentRoot ++ RequestURI), 205 Prefix ++ Path; 206 {Path, _AfterPath} -> 207 Path 208 end. 209 210percent_decode_path(InitPath) -> 211 case uri_string:percent_decode(InitPath) of 212 {error, _} -> 213 InitPath; 214 Path0 -> %% Protect against vulnerabilities 215 case uri_string:normalize(Path0) of 216 {error, _, _} -> 217 InitPath; 218 Path -> 219 Path 220 end 221 end. 222%% 223%% Configuration 224%% 225store({directory_index, Value} = Conf, _) when is_list(Value) -> 226 case is_directory_index_list(Value) of 227 true -> 228 {ok, Conf}; 229 false -> 230 {error, {wrong_type, {directory_index, Value}}} 231 end; 232store({directory_index, Value}, _) -> 233 {error, {wrong_type, {directory_index, Value}}}; 234store({alias, {Fake, Real}} = Conf, _) 235 when is_list(Fake), is_list(Real) -> 236 {ok, Conf}; 237store({alias, Value}, _) -> 238 {error, {wrong_type, {alias, Value}}}; 239store({re_write, {Re, Replacement}} = Conf, _) 240 when is_list(Re), is_list(Replacement) -> 241 case re:compile(Re) of 242 {ok, MP} -> 243 {ok, {alias, {MP, Replacement}}}; 244 {error,_} -> 245 {error, {re_compile, Conf}} 246 end; 247store({re_write, _} = Conf, _) -> 248 {error, {wrong_type, Conf}}; 249store({script_alias, {Fake, Real}} = Conf, _) 250 when is_list(Fake), is_list(Real) -> 251 {ok, Conf}; 252store({script_alias, Value}, _) -> 253 {error, {wrong_type, {script_alias, Value}}}; 254store({script_re_write, {Re, Replacement}} = Conf, _) 255 when is_list(Re), is_list(Replacement) -> 256 case re:compile(Re) of 257 {ok, MP} -> 258 {ok, {script_alias, {MP, Replacement}}}; 259 {error,_} -> 260 {error, {re_compile, Conf}} 261 end; 262store({script_re_write, _} = Conf, _) -> 263 {error, {wrong_type, Conf}}. 264 265is_directory_index_list([]) -> 266 true; 267is_directory_index_list([Head | Tail]) when is_list(Head) -> 268 is_directory_index_list(Tail); 269is_directory_index_list(_) -> 270 false. 271 272 273%% --------------------------------------------------------------------- 274 275which_alias(ConfigDB) -> 276 httpd_util:multi_lookup(ConfigDB, alias). 277 278which_server_name(ConfigDB) -> 279 httpd_util:lookup(ConfigDB, server_name). 280 281which_port(ConfigDB) -> 282 httpd_util:lookup(ConfigDB, port, 80). 283 284which_document_root(ConfigDB) -> 285 Root = httpd_util:lookup(ConfigDB, document_root, ""), 286 case string:tokens(Root, ":") of 287 [Prefix, Path] -> 288 {Prefix ++ ":", Path}; 289 [Path] -> 290 {"", Path} 291 end. 292 293which_directory_index(ConfigDB) -> 294 httpd_util:lookup(ConfigDB, directory_index, []). 295