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