1%% ``Licensed under the Apache License, Version 2.0 (the "License"); 2%% you may not use this file except in compliance with the License. 3%% You may obtain a copy of the License at 4%% 5%% http://www.apache.org/licenses/LICENSE-2.0 6%% 7%% Unless required by applicable law or agreed to in writing, software 8%% distributed under the License is distributed on an "AS IS" BASIS, 9%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10%% See the License for the specific language governing permissions and 11%% limitations under the License. 12%% 13%% The Initial Developer of the Original Code is Ericsson Utvecklings AB. 14%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings 15%% AB. All Rights Reserved.'' 16%% 17%% $Id: mod_dir.erl,v 1.1 2008/12/17 09:53:35 mikpe Exp $ 18-module(mod_dir). 19-export([do/1]). 20 21-include("httpd.hrl"). 22 23%% do 24 25do(Info) -> 26 ?DEBUG("do -> entry",[]), 27 case Info#mod.method of 28 "GET" -> 29 case httpd_util:key1search(Info#mod.data,status) 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 httpd_util:key1search(Info#mod.data,response) of 36 %% No response has been generated! 37 undefined -> 38 do_dir(Info); 39 %% A response has been generated or sent! 40 Response -> 41 {proceed,Info#mod.data} 42 end 43 end; 44 %% Not a GET method! 45 _ -> 46 {proceed,Info#mod.data} 47 end. 48 49do_dir(Info) -> 50 ?DEBUG("do_dir -> Request URI: ~p",[Info#mod.request_uri]), 51 Path = mod_alias:path(Info#mod.data,Info#mod.config_db, 52 Info#mod.request_uri), 53 DefaultPath = mod_alias:default_index(Info#mod.config_db,Path), 54 %% Is it a directory? 55 case file:read_file_info(DefaultPath) of 56 {ok,FileInfo} when FileInfo#file_info.type == directory -> 57 DecodedRequestURI = 58 httpd_util:decode_hex(Info#mod.request_uri), 59 ?DEBUG("do_dir -> ~n" 60 " Path: ~p~n" 61 " DefaultPath: ~p~n" 62 " DecodedRequestURI: ~p", 63 [Path,DefaultPath,DecodedRequestURI]), 64 case dir(DefaultPath,string:strip(DecodedRequestURI,right,$/),Info#mod.config_db) of 65 {ok, Dir} -> 66 Head=[{content_type,"text/html"}, 67 {content_length,integer_to_list(httpd_util:flatlength(Dir))}, 68 {date,httpd_util:rfc1123_date(FileInfo#file_info.mtime)}, 69 {code,200}], 70 {proceed,[{response,{response,Head,Dir}}, 71 {mime_type,"text/html"}|Info#mod.data]}; 72 {error, Reason} -> 73 ?ERROR("do_dir -> dir operation failed: ~p",[Reason]), 74 {proceed, 75 [{status,{404,Info#mod.request_uri,Reason}}| 76 Info#mod.data]} 77 end; 78 {ok,FileInfo} -> 79 ?DEBUG("do_dir -> ~n" 80 " Path: ~p~n" 81 " DefaultPath: ~p~n" 82 " FileInfo: ~p", 83 [Path,DefaultPath,FileInfo]), 84 {proceed,Info#mod.data}; 85 {error,Reason} -> 86 ?LOG("do_dir -> failed reading file info (~p) for: ~p", 87 [Reason,DefaultPath]), 88 {proceed, 89 [{status,read_file_info_error(Reason,Info,DefaultPath)}| 90 Info#mod.data]} 91 end. 92 93dir(Path,RequestURI,ConfigDB) -> 94 case file:list_dir(Path) of 95 {ok,FileList} -> 96 SortedFileList=lists:sort(FileList), 97 {ok,[header(Path,RequestURI), 98 body(Path,RequestURI,ConfigDB,SortedFileList), 99 footer(Path,SortedFileList)]}; 100 {error,Reason} -> 101 {error,?NICE("Can't open directory "++Path++": "++Reason)} 102 end. 103 104%% header 105 106header(Path,RequestURI) -> 107 Header= 108 "<HTML>\n<HEAD>\n<TITLE>Index of "++RequestURI++"</TITLE>\n</HEAD>\n<BODY>\n<H1>Index of "++ 109 RequestURI++"</H1>\n<PRE><IMG SRC=\""++icon(blank)++ 110 "\" ALT=" "> Name Last modified Size Description 111<HR>\n", 112 case regexp:sub(RequestURI,"[^/]*\$","") of 113 {ok,"/",_} -> 114 Header; 115 {ok,ParentRequestURI,_} -> 116 {ok,ParentPath,_}=regexp:sub(string:strip(Path,right,$/),"[^/]*\$",""), 117 Header++format(ParentPath,ParentRequestURI) 118 end. 119 120format(Path,RequestURI) -> 121 {ok,FileInfo}=file:read_file_info(Path), 122 {{Year,Month,Day},{Hour,Minute,Second}}=FileInfo#file_info.mtime, 123 io_lib:format("<IMG SRC=\"~s\" ALT=\"[~s]\"> <A HREF=\"~s\">Parent directory</A> ~2.2.0w-~s-~w ~2.2.0w:~2.2.0w -\n", 124 [icon(back),"DIR",RequestURI,Day, 125 httpd_util:month(Month),Year,Hour,Minute]). 126 127%% body 128 129body(Path,RequestURI,ConfigDB,[]) -> 130 []; 131body(Path,RequestURI,ConfigDB,[Entry|Rest]) -> 132 [format(Path,RequestURI,ConfigDB,Entry)|body(Path,RequestURI,ConfigDB,Rest)]. 133 134format(Path,RequestURI,ConfigDB,Entry) -> 135 case file:read_file_info(Path++"/"++Entry) of 136 {ok,FileInfo} when FileInfo#file_info.type == directory -> 137 {{Year,Month,Day},{Hour,Minute,Second}}=FileInfo#file_info.mtime, 138 EntryLength=length(Entry), 139 if 140 EntryLength > 21 -> 141 io_lib:format("<IMG SRC=\"~s\" ALT=\"[~s]\"> <A HREF=\"~s\">~-21.s..</A>~2.2.0w-~s-~w ~2.2.0w:~2.2.0w -\n", 142 [icon(folder),"DIR",RequestURI++"/"++Entry++"/",Entry, 143 Day,httpd_util:month(Month),Year,Hour,Minute]); 144 true -> 145 io_lib:format("<IMG SRC=\"~s\" ALT=\"[~s]\"> <A HREF=\"~s\">~s</A>~*.*c~2.2.0w-~s-~w ~2.2.0w:~2.2.0w -\n", 146 [icon(folder),"DIR",RequestURI++"/"++Entry++"/",Entry, 147 23-EntryLength,23-EntryLength,$ ,Day, 148 httpd_util:month(Month),Year,Hour,Minute]) 149 end; 150 {ok,FileInfo} -> 151 {{Year,Month,Day},{Hour,Minute,Second}}=FileInfo#file_info.mtime, 152 Suffix=httpd_util:suffix(Entry), 153 MimeType=httpd_util:lookup_mime(ConfigDB,Suffix,""), 154 EntryLength=length(Entry), 155 if 156 EntryLength > 21 -> 157 io_lib:format("<IMG SRC=\"~s\" ALT=\"[~s]\"> <A HREF=\"~s\">~-21.s..</A>~2.2.0w-~s-~w ~2.2.0w:~2.2.0w~8wk ~s\n", 158 [icon(Suffix,MimeType),Suffix,RequestURI++"/"++Entry, 159 Entry,Day,httpd_util:month(Month),Year,Hour,Minute, 160 trunc(FileInfo#file_info.size/1024+1),MimeType]); 161 true -> 162 io_lib:format("<IMG SRC=\"~s\" ALT=\"[~s]\"> <A HREF=\"~s\">~s</A>~*.*c~2.2.0w-~s-~w ~2.2.0w:~2.2.0w~8wk ~s\n", 163 [icon(Suffix,MimeType),Suffix,RequestURI++"/"++Entry, 164 Entry,23-EntryLength,23-EntryLength,$ ,Day, 165 httpd_util:month(Month),Year,Hour,Minute, 166 trunc(FileInfo#file_info.size/1024+1),MimeType]) 167 end; 168 {error,Reason} -> 169 "" 170 end. 171 172%% footer 173 174footer(Path,FileList) -> 175 case lists:member("README",FileList) of 176 true -> 177 {ok,Body}=file:read_file(Path++"/README"), 178 "</PRE>\n<HR>\n<PRE>\n"++binary_to_list(Body)++ 179 "\n</PRE>\n</BODY>\n</HTML>\n"; 180 false -> 181 "</PRE>\n</BODY>\n</HTML>\n" 182 end. 183 184%% 185%% Icon mappings are hard-wired ala default Apache (Ugly!) 186%% 187 188icon(Suffix,MimeType) -> 189 case icon(Suffix) of 190 undefined -> 191 case MimeType of 192 [$t,$e,$x,$t,$/|_] -> 193 "/icons/text.gif"; 194 [$i,$m,$a,$g,$e,$/|_] -> 195 "/icons/image2.gif"; 196 [$a,$u,$d,$i,$o,$/|_] -> 197 "/icons/sound2.gif"; 198 [$v,$i,$d,$e,$o,$/|_] -> 199 "/icons/movie.gif"; 200 _ -> 201 "/icons/unknown.gif" 202 end; 203 Icon -> 204 Icon 205 end. 206 207icon(blank) -> "/icons/blank.gif"; 208icon(back) -> "/icons/back.gif"; 209icon(folder) -> "/icons/folder.gif"; 210icon("bin") -> "/icons/binary.gif"; 211icon("exe") -> "/icons/binary.gif"; 212icon("hqx") -> "/icons/binhex.gif"; 213icon("tar") -> "/icons/tar.gif"; 214icon("wrl") -> "/icons/world2.gif"; 215icon("wrl.gz") -> "/icons/world2.gif"; 216icon("vrml") -> "/icons/world2.gif"; 217icon("vrm") -> "/icons/world2.gif"; 218icon("iv") -> "/icons/world2.gif"; 219icon("Z") -> "/icons/compressed.gif"; 220icon("z") -> "/icons/compressed.gif"; 221icon("tgz") -> "/icons/compressed.gif"; 222icon("gz") -> "/icons/compressed.gif"; 223icon("zip") -> "/icons/compressed.gif"; 224icon("ps") -> "/icons/a.gif"; 225icon("ai") -> "/icons/a.gif"; 226icon("eps") -> "/icons/a.gif"; 227icon("html") -> "/icons/layout.gif"; 228icon("shtml") -> "/icons/layout.gif"; 229icon("htm") -> "/icons/layout.gif"; 230icon("pdf") -> "/icons/layout.gif"; 231icon("txt") -> "/icons/text.gif"; 232icon("erl") -> "/icons/burst.gif"; 233icon("c") -> "/icons/c.gif"; 234icon("pl") -> "/icons/p.gif"; 235icon("py") -> "/icons/p.gif"; 236icon("for") -> "/icons/f.gif"; 237icon("dvi") -> "/icons/dvi.gif"; 238icon("uu") -> "/icons/uuencoded.gif"; 239icon("conf") -> "/icons/script.gif"; 240icon("sh") -> "/icons/script.gif"; 241icon("shar") -> "/icons/script.gif"; 242icon("csh") -> "/icons/script.gif"; 243icon("ksh") -> "/icons/script.gif"; 244icon("tcl") -> "/icons/script.gif"; 245icon("tex") -> "/icons/tex.gif"; 246icon("core") -> "/icons/tex.gif"; 247icon(_) -> undefined. 248 249 250read_file_info_error(eacces,Info,Path) -> 251 read_file_info_error(403,Info,Path, 252 ": Missing search permissions for one " 253 "of the parent directories"); 254read_file_info_error(enoent,Info,Path) -> 255 read_file_info_error(404,Info,Path,""); 256read_file_info_error(enotdir,Info,Path) -> 257 read_file_info_error(404,Info,Path, 258 ": A component of the file name is not a directory"); 259read_file_info_error(_,Info,Path) -> 260 read_file_info_error(500,none,Path,""). 261 262read_file_info_error(StatusCode,none,Path,Reason) -> 263 {StatusCode,none,?NICE("Can't access "++Path++Reason)}; 264read_file_info_error(StatusCode,Info,Path,Reason) -> 265 {StatusCode,Info#mod.request_uri, 266 ?NICE("Can't access "++Path++Reason)}. 267