%%% BEGIN openflax/handler/ls.erl %%%
%%%
%%% openflax - Open Source web server for Erlang/OTP
%%% Copyright (c)2003 Cat's Eye Technologies. All rights reserved.
%%%
%%% Redistribution and use in source and binary forms, with or without
%%% modification, are permitted provided that the following conditions
%%% are met:
%%%
%%% Redistributions of source code must retain the above copyright
%%% notice, this list of conditions and the following disclaimer.
%%%
%%% Redistributions in binary form must reproduce the above copyright
%%% notice, this list of conditions and the following disclaimer in
%%% the documentation and/or other materials provided with the
%%% distribution.
%%%
%%% Neither the name of Cat's Eye Technologies nor the names of its
%%% contributors may be used to endorse or promote products derived
%%% from this software without specific prior written permission.
%%%
%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
%%% CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
%%% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
%%% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
%%% DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
%%% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
%%% OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
%%% PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
%%% OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
%%% ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
%%% OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
%%% OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
%%% POSSIBILITY OF SUCH DAMAGE.
%% @doc Simple directory listing handler for OpenFlax.
%%
%%
This handler implements the displaying of simple directory listings
%% in a web page.
%%
%% This handler requires openflax.handler.file
%% and openflax.handler.template.
%%
%% This handler supports the following configuration options in
%% the conf() passed to it:
%%
%% cfg_ls_index_file
%% filename().
%% A filename to be directed to if it exists in the directory.
%% Typically index.html, but may be changed.
%% cfg_ls_index_ctype
%% content_type().
%% The MIME content-type of cfg_ls_index_file.
%% Typically text/html, but may be changed.
%% cfg_ls_filter
%% [{regexp(), boolean()}].
%% A list of pairs of regular expressions and boolean values
%% indicating which files to list and which to not list.
%% By default, all files are listed, and filters later
%% in the list override filters earlier in the list.
%% For example, to only list text files which do not start with
%% "BACKUP", one might say
%% [{"*", false}, {"*.txt", true}, {"BACKUP*", false}].
%% cfg_template_body
%% template().
%% The template for the page to be generated to display a directory.
%% It should display the tpl_ls_body item.
%% cfg_ls_template_each_file
%% template().
%% A mini-template for each file to be displayed in the directory
%% listing. It can display tpl_ls_filename,
%% tpl_ls_filesize, etc.
%% cfg_ls_template_each_subdir
%% template().
%% A mini-template for each subdirectory to be displayed in the directory
%% listing. It can display tpl_ls_filename,
%% tpl_ls_filesize, etc.
%%
%%
%% @end
-module(openflax.handler.ls).
-vsn('$Id: ls.erl 31 2004-04-23 07:00:11Z catseye $').
-author('cpressey@catseye.mine.nu').
-copyright('Copyright (c)2003 Cat`s Eye Technologies. All rights reserved.').
-behaviour(openflax.handler).
-export([start/1, serve/1, stop/1]). % behaviour
-import(lists).
-import(string).
-import(regexp).
-import(filename).
-import(file).
-import(filelib).
%% @spec start(conf()) -> conf()
%% @doc Initializes the directory server.
start(Conf) ->
openflax.conf:new().
%% @spec stop(conf()) -> conf()
%% @doc Shuts down the directory server.
stop(Conf) ->
Conf.
%% @spec serve(conf()) -> {response(), conf()}
%% @doc Serves a simple listing of a world-readable,
%% world-executable directory to the connected user agent.
serve(Conf) ->
% Ensure directory is world-readable and -executable
DirName = openflax.handler.file:document(Conf),
case {openflax.handler.file:is_world_readable(DirName),
openflax.handler.file:is_world_executable(DirName)} of
{true, true} ->
serve1(Conf, DirName);
_ ->
{not_found, Conf}
end.
serve1(Conf, DirName) ->
% Serve index file in this directory, if it exists
IndexFile = openflax.conf:get_string(cfg_ls_index_file, Conf),
FileName = filename:join(DirName, IndexFile),
case openflax.handler.file:is_accessible(FileName, Conf) of
true ->
% set basic-resource and content-type
BasicResource = openflax.conf:get_string(sreq_basic_resource, Conf),
ContentType = openflax.conf:get_string(cfg_ls_index_ctype, Conf),
Conf0 = openflax.conf:put_value(sreq_basic_resource,
filename:join(BasicResource, IndexFile), Conf),
Conf1 = openflax.conf:put_value(res_content_type, ContentType, Conf0),
{openflax.handler.file, Conf1};
false ->
serve2(Conf, DirName)
end.
serve2(Conf, DirName) ->
% read index.pub, if it exists
IndexPub = filename:join([DirName, "index.pub"]),
Conf0 = case file:consult(IndexPub) of
{ok, List} ->
lists:foldl(fun({Key, Value}, Acc) ->
openflax.conf:put_value(Key, Value, Acc)
end, Conf, List);
_ ->
Conf
end,
serve3(Conf0, DirName).
serve3(Conf, DirName) ->
% Serve the actual directory listing
BasicResource = openflax.conf:get_string(sreq_basic_resource, Conf),
EachFile = openflax.conf:get_string(cfg_ls_template_each_file, Conf),
EachDir = openflax.conf:get_string(cfg_ls_template_each_subdir, Conf),
Filter = openflax.conf:get_string(cfg_ls_filter, Conf),
Keyword = openflax.conf:get_string(arg_keyword, Conf),
FileDescs = case openflax.conf:get_value(cfg_ls_filedescs, Conf) of
{ok, FD} -> FD;
_ -> []
end,
Links = case openflax.conf:get_value(cfg_ls_links, Conf) of
{ok, LS} -> LS;
_ -> []
end,
{ok, Dir} = file:list_dir(DirName),
Body = lists:foldl(fun
(FileName, Acc) ->
AppName = case string:rchr(FileName, $-) of
0 -> FileName;
N -> string:left(FileName, N-1)
end,
FullFileName = filename:join([DirName, FileName]),
Conf0 = openflax.conf:put_value(tpl_ls_filename, FileName, Conf),
FileLink = urlize(FileName),
Conf1 = openflax.conf:put_value(tpl_ls_filelink, FileLink, Conf0),
Conf2 = openflax.conf:put_value(tpl_ls_filesize,
openflax.string:from_term(filelib:file_size(FullFileName)), Conf1),
Conf3 = openflax.conf:put_value(tpl_ls_filedate,
openflax.string:from_term(filelib:last_modified(FullFileName)), Conf2),
{FileDesc, Keywords} = case lists:keysearch(AppName, 1, FileDescs) of
{value, {_, F}} ->
{F, []};
{value, {_, F, Prereq, K}} ->
{F, K};
{value, {_, F, Prereq, K, _}} ->
{F, K};
_ ->
{"", []}
end,
Conf4 = openflax.conf:put_value(tpl_ls_filedesc, FileDesc, Conf3),
KeywordsString = [ ["",
atom_to_list(KW), " "] || KW <- Keywords ],
Conf5 = openflax.conf:put_value(tpl_ls_filekeywords, KeywordsString, Conf4),
FConf = Conf5,
case {matches(Keyword, Keywords),
filelib:is_dir(FullFileName),
openflax.handler.file:is_world_readable(FullFileName),
openflax.handler.file:is_world_executable(FullFileName),
openflax.handler.file:is_world_writeable(FullFileName),
filter(FileName, Filter, true)} of
{true, true, true, true, false, true} ->
[Acc, openflax.handler.template:fill_out(EachDir, FConf)];
{true, false, true, _, false, true} ->
[Acc, openflax.handler.template:fill_out(EachFile, FConf)];
_ ->
Acc
end
end, "", lists:sort(Dir)),
LinksBody = lists:foldl(fun
({LinkURL, LinkDesc, LinkKeywords}, Acc) ->
case matches(Keyword, LinkKeywords) of
true ->
[Acc, "", LinkDesc, "
"];
false ->
Acc
end;
(_, Acc) ->
Acc
end, "", Links),
Conf0 = openflax.conf:put_value(tpl_ls_body, Body, Conf),
Conf1 = openflax.conf:put_value(tpl_ls_links, LinksBody, Conf0),
Conf2 = openflax.conf:put_value(tpl_ls_hierarchy,
html_hierarchy(BasicResource), Conf1),
KeywordNote = case Keyword of
"" ->
"";
_ ->
"showing entries with keyword " ++ Keyword ++
"; show all"
end,
Conf3 = openflax.conf:put_value(tpl_ls_keyword, KeywordNote, Conf2),
{openflax.handler.template, Conf3}.
filter(FileName, [], Acc) ->
Acc;
filter(FileName, [{Pattern, Boolean} | Tail], Acc) ->
Acc0 = case regexp:first_match(FileName, regexp:sh_to_awk(Pattern)) of
{match, _, _} ->
Boolean;
_ ->
Acc
end,
filter(FileName, Tail, Acc0).
matches("", _) ->
true;
matches(Keyword, Keywords) ->
lists:member(list_to_atom(Keyword), Keywords).
urlize(FileName) ->
FileName0 = lists:map(fun
($ ) ->
"%20";
($?) ->
"%3f";
($#) ->
"%23";
(Else) ->
Else
end, FileName),
lists:flatten(FileName0).
html_hierarchy(Root) ->
DirList = filename:split(Root),
{Text, _} = lists:foldl(fun(X, {A, B}) ->
Y = case X of
"/" -> "/";
O -> O ++ "/"
end,
G = B ++ Y,
{A ++ "" ++ Y ++ " ", G}
end, {"", ""}, DirList),
lists:flatten(Text).
escape_hex(List) -> lists:reverse(escape_hex(List, [])).
escape_hex([$ | T], Acc) ->
escape_hex(T, [$0, $2, $% | Acc]);
escape_hex([H | T], Acc) ->
escape_hex(T, [H | Acc]).
%%% END of openflax/handler/ls.erl %%%