1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 1997-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-module(mod_dir). 22 23-include("httpd.hrl"). 24-include("httpd_internal.hrl"). 25 26-export([do/1]). 27 28%% do 29 30do(Info) -> 31 case Info#mod.method of 32 "GET" -> 33 case proplists:get_value(status, Info#mod.data) of 34 %% A status code has been generated! 35 {_StatusCode, _PhraseArgs, _Reason} -> 36 {proceed,Info#mod.data}; 37 %% No status code has been generated! 38 undefined -> 39 case proplists:get_value(response, Info#mod.data) of 40 %% No response has been generated! 41 undefined -> 42 do_dir(Info); 43 %% A response has been generated or sent! 44 _Response -> 45 {proceed,Info#mod.data} 46 end 47 end; 48 %% Not a GET method! 49 _ -> 50 {proceed,Info#mod.data} 51 end. 52 53do_dir(Info) -> 54 Path = mod_alias:path(Info#mod.data,Info#mod.config_db, 55 Info#mod.request_uri), 56 DefaultPath = mod_alias:default_index(Info#mod.config_db,Path), 57 %% Is it a directory? 58 case file:read_file_info(DefaultPath) of 59 {ok,FileInfo} when FileInfo#file_info.type == directory -> 60 case dir(DefaultPath,string:strip( Info#mod.request_uri,right,$/), 61 Info#mod.config_db) of 62 {ok, DirUnicode} -> 63 Dir = unicode:characters_to_binary(DirUnicode), 64 LastModified = 65 case (catch httpd_util:rfc1123_date( 66 FileInfo#file_info.mtime)) of 67 Date when is_list(Date) -> 68 [{"date", Date}]; 69 _ -> %% This will rarly happen, but could happen 70 %% if a computer is wrongly configured. 71 [] 72 end, 73 Head=[{content_type,"text/html; charset=UTF-8"}, 74 {content_length, 75 integer_to_list(erlang:iolist_size(Dir))}, 76 {code,200} | LastModified], 77 {proceed,[{response,{response, Head, Dir}}, 78 {mime_type,"text/html"} | Info#mod.data]}; 79 {error, Reason} -> 80 {proceed, 81 [{status,{404,Info#mod.request_uri,Reason}}| 82 Info#mod.data]} 83 end; 84 {ok, _FileInfo} -> 85 {proceed,Info#mod.data}; 86 {error,Reason} -> 87 Status = httpd_file:handle_error(Reason, "access", Info, 88 DefaultPath), 89 {proceed, [{status, Status}| Info#mod.data]} 90 end. 91 92dir(Path,RequestURI,ConfigDB) -> 93 case file:list_dir(Path) of 94 {ok,FileList} -> 95 SortedFileList=lists:sort(FileList), 96 {ok,[header(Path,RequestURI), 97 body(Path,RequestURI,ConfigDB,SortedFileList), 98 footer(Path,SortedFileList)]}; 99 {error,Reason} -> 100 {error,?NICE("Can't open directory "++Path++": "++ 101 file:format_error(Reason))} 102 end. 103 104encode_html_entity(FileName) -> 105 Enc = fun($&) -> "&"; 106 ($<) -> "<"; 107 ($>) -> ">"; 108 ($") -> """; 109 ($') -> "'"; 110 ($/) -> "/"; 111 (C) -> C 112 end, 113 unicode:characters_to_list([Enc(C) || C <- FileName]). 114 115%% header 116 117header(Path,RequestURI) -> 118 DisplayURI = case RequestURI of 119 "" -> "/"; 120 _ -> RequestURI 121 end, 122 Header = "<!DOCTYPE html>\n" 123 "<HTML>\n<HEAD>\n" 124 "<meta charset=\"UTF-8\">" 125 "<TITLE>Index of " ++ DisplayURI ++ "</TITLE>\n" 126 "</HEAD>\n<BODY>\n<H1>Index of "++ 127 DisplayURI ++ "</H1>\n<PRE><span>" ++ icon(blank) ++ 128 "</span> Name Last modified " 129 "Size Description <HR>\n", 130 case RequestURI of 131 "" -> 132 Header; 133 _ -> 134 ParentRequestURI = re:replace(RequestURI,"[^/]*\$", "", 135 [{return,list}]), 136 ParentPath = 137 re:replace(string:strip(Path,right,$/),"[^/]*\$","", 138 [{return,list}]), 139 Header++format(ParentPath,ParentRequestURI) 140 end. 141 142format(Path,RequestURI) -> 143 {ok,FileInfo}=file:read_file_info(Path), 144 {{Year, Month, Day},{Hour, Minute, _Second}} = FileInfo#file_info.mtime, 145 io_lib:format("<span title=\"~s\">~s</span>" 146 " <A HREF=\"~ts\">Parent directory</A> " 147 " ~2.2.0w-~s-~w ~2.2.0w:~2.2.0w -\n", 148 ["DIR",icon(back),RequestURI,Day, 149 httpd_util:month(Month),Year,Hour,Minute]). 150 151%% body 152 153body(_Path, _RequestURI, _ConfigDB, []) -> 154 []; 155body(Path, RequestURI, ConfigDB, [Entry | Rest]) -> 156 [format(Path, RequestURI, ConfigDB, Entry)| 157 body(Path, RequestURI, ConfigDB, Rest)]. 158 159format(Path,RequestURI,ConfigDB,InitEntry) -> 160 Entry = encode_html_entity(InitEntry), 161 case file:read_file_info(Path++"/"++InitEntry) of 162 {ok,FileInfo} when FileInfo#file_info.type == directory -> 163 {{Year, Month, Day},{Hour, Minute, _Second}} = 164 FileInfo#file_info.mtime, 165 EntryLength = string:length(InitEntry), 166 if 167 EntryLength > 21 -> 168 TruncatedEntry = encode_html_entity( 169 string:slice(InitEntry, 0, 19)), 170 io_lib:format("<span title=\"[~s]\">~s</span> " 171 "<A HREF=\"~ts\">~ts..</A>" 172 "~*.*c~2.2.0w-~s-~w ~2.2.0w:~2.2.0w" 173 " -\n", 174 ["DIR", icon(folder), 175 RequestURI ++ "/" ++ percent_encode(InitEntry) ++ "/", 176 TruncatedEntry, 23-21, 23-21, $ , 177 Day, httpd_util:month(Month), 178 Year,Hour,Minute]); 179 true -> 180 io_lib:format("<span title=\"[~s]\">~s</span>" 181 " <A HREF=\"~ts\">~ts</A>~*.*c~2.2.0" 182 "w-~s-~w ~2.2.0w:~2.2.0w -\n", 183 ["DIR", icon(folder), 184 RequestURI ++ "/" ++ percent_encode(InitEntry) ++ "/",Entry, 185 23-EntryLength,23-EntryLength,$ ,Day, 186 httpd_util:month(Month),Year,Hour,Minute]) 187 end; 188 {ok,FileInfo} -> 189 {{Year, Month, Day},{Hour, Minute,_Second}} = 190 FileInfo#file_info.mtime, 191 Suffix=httpd_util:strip_extension_dot(Entry), 192 MimeType=httpd_util:lookup_mime(ConfigDB,Suffix,""), 193 EntryLength = string:length(InitEntry), 194 if 195 EntryLength > 21 -> 196 TruncatedEntry = encode_html_entity( 197 string:slice(InitEntry, 0, 19)), 198 io_lib:format("<span title=\"[~s]\">~s</span>" 199 " <A HREF=\"~ts\">~ts..</A>~*.*c~2.2.0" 200 "w-~s-~w ~2.2.0w:~2.2.0w~8wk ~s\n", 201 [Suffix, icon(Suffix, MimeType), 202 RequestURI ++ "/" ++ percent_encode(InitEntry), 203 TruncatedEntry, 23-21, 23-21, $ , Day, 204 httpd_util:month(Month),Year,Hour,Minute, 205 trunc(FileInfo#file_info.size/1024+1), 206 MimeType]); 207 true -> 208 io_lib:format("<span title=\"[~s]\">~s</span> " 209 "<A HREF=\"~ts\">~ts</A>~*.*c~2.2.0w-~s-~w" 210 " ~2.2.0w:~2.2.0w~8wk ~s\n", 211 [Suffix, icon(Suffix, MimeType), 212 RequestURI ++ "/" ++ percent_encode(InitEntry), Entry, 23-EntryLength, 213 23-EntryLength, $ ,Day, 214 httpd_util:month(Month),Year,Hour,Minute, 215 trunc(FileInfo#file_info.size/1024+1), 216 MimeType]) 217 end; 218 {error, _Reason} -> 219 "" 220 end. 221 222percent_encode(URI) when is_list(URI) -> 223 Reserved = reserved(), 224 lists:append([uri_encode(Char, Reserved) || Char <- URI]). 225 226reserved() -> 227 sets:from_list([$;, $:, $@, $&, $=, $+, $,, $?, $/, 228 $#, $[, $], $<, $>, $\", ${, $}, $|, %" 229 $\\, $', $^, $%, $ ]). 230 231uri_encode(Char, Reserved) -> 232 case sets:is_element(Char, Reserved) of 233 true -> 234 [ $% | http_util:integer_to_hexlist(Char)]; 235 false -> 236 [Char] 237 end. 238 239%% footer 240 241footer(Path,FileList) -> 242 case lists:member("README",FileList) of 243 true -> 244 {ok,Body}=file:read_file(Path++"/README"), 245 "</PRE>\n<HR>\n<PRE>\n"++binary_to_list(Body)++ 246 "\n</PRE>\n</BODY>\n</HTML>\n"; 247 false -> 248 "</PRE>\n</BODY>\n</HTML>\n" 249 end. 250 251%% 252%% Icon mappings (Emoji) 253%% 254 255-define(package, "📦"). % package 256-define(image, "📷"). % camera 257-define(audio, "📢"). % loudspeaker 258-define(video, "🎥"). % movie camera 259 260-define(page, "📄"). % page 261-define(page2, "📃"). % page, curled 262-define(world, "🌍"). % globe 263-define(unknown, ?page). 264-define(text, "📝"). % page with pencil 265-define(sourcecode, "📜"). % scroll 266 267icon(Suffix,MimeType) -> 268 case icon(Suffix) of 269 undefined -> 270 case MimeType of 271 [$t,$e,$x,$t,$/|_] -> 272 ?text; 273 [$i,$m,$a,$g,$e,$/|_] -> 274 ?image; 275 [$a,$u,$d,$i,$o,$/|_] -> 276 ?audio; 277 [$v,$i,$d,$e,$o,$/|_] -> 278 ?video; 279 _ -> 280 ?unknown 281 end; 282 Icon -> 283 Icon 284 end. 285 286icon(blank) -> "📂"; % open folder 287icon(back) -> "🔙"; % back arrow 288icon(folder) -> "📁"; % closed folder 289icon("bin") -> ?page2; 290icon("exe") -> ?page2; 291icon("hqx") -> ?page2; 292icon("tar") -> ?package; 293icon("wrl") -> ?world; 294icon("wrl.gz") -> ?world; 295icon("vrml") -> ?world; 296icon("vrm") -> ?world; 297icon("iv") -> ?world; 298icon("Z") -> ?package; 299icon("z") -> ?package; 300icon("tgz") -> ?package; 301icon("gz") -> ?package; 302icon("zip") -> ?package; 303icon("bz2") -> ?package; 304icon("ps") -> ?page; 305icon("ai") -> ?image; 306icon("eps") -> ?image; 307icon("html") -> ?text; 308icon("shtml") -> ?text; 309icon("htm") -> ?text; 310icon("pdf") -> ?text; 311icon("txt") -> ?text; 312icon("erl") -> ?sourcecode; 313icon("c") -> ?sourcecode; 314icon("pl") -> ?sourcecode; 315icon("py") -> ?sourcecode; 316icon("for") -> ?sourcecode; 317icon("dvi") -> ?text; 318icon("conf") -> ?sourcecode; 319icon("sh") -> ?sourcecode; 320icon("shar") -> ?sourcecode; 321icon("csh") -> ?sourcecode; 322icon("ksh") -> ?sourcecode; 323icon("tcl") -> ?sourcecode; 324icon("tex") -> ?sourcecode; 325icon("core") -> ?sourcecode; 326icon("xml") -> ?sourcecode; 327icon("jpg") -> ?image; 328icon("JPG") -> ?image; 329icon("jpeg") -> ?image; 330icon("png") -> ?image; 331icon("gif") -> ?image; 332icon("avi") -> ?video; 333icon("mp4") -> ?video; 334icon("m4a") -> ?audio; 335icon("mp3") -> ?audio; 336icon("aac") -> ?audio; 337icon("flac") -> ?audio; 338icon(_) -> undefined. 339