0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-14 04:10:24 +00:00

Add directory redirection handling to cowboy_static

Directory accesses with no trailing slash will by default be redirected
(301) to their canonical slashed version. New handler option 'directory_index'
can be set to 'false' to disable, or to another status code such as 302.

Directory accesses with a trailing slash can be redirected (301) to an
index file. This is off by default. New handler option 'directory_index' can
be set to 'true' or a status code such as 302, for the default "index.html",
or to a binary string such as <<"index.php">>, or to a pair {Code, Filename}.
This commit is contained in:
Richard Carlsson 2022-11-08 12:41:12 +01:00
parent 30ee75cea1
commit 5ced7c03c0

View file

@ -42,6 +42,10 @@
-type state() :: {binary(), {direct | archive, #file_info{}}
| {error, atom()}, extra()}.
-define(DEFAULT_INDEX, <<"index.html">>).
-define(DEFAULT_REDIR, 301).
%% Resolve the file that will be sent and get its file information.
%% If the handler is configured to manage a directory, check that the
%% requested file is inside the configured directory.
@ -180,7 +184,70 @@ fullpath([Segment|Tail], Acc) ->
init_info(Req, Path, HowToAccess, Extra) ->
Info = read_file_info(Path, HowToAccess),
{cowboy_rest, Req, {Path, Info, Extra}}.
case Info of
{direct, #file_info{type = directory}} ->
ReqPath = cowboy_req:path(Req),
PrefixLen = max(byte_size(ReqPath)-1, 0),
case ReqPath of
<<_:PrefixLen/binary, $/>> ->
dir_with_slash(Req, ReqPath, Path,
Info, Extra);
_ ->
dir_without_slash(Req, ReqPath, Path,
Info, Extra)
end;
_Other ->
{cowboy_rest, Req, {Path, Info, Extra}}
end.
dir_without_slash(Req, ReqPath, FilePath, Info, Extra) ->
%% no ending slash - redirect to slashed version unless disabled
Opts = case lists:keyfind(directory_slash, 1, Extra) of
{directory_slash, X} -> X;
_ -> true % note: enabled by default
end,
case slash_options(Opts) of
false ->
{cowboy_rest, Req, {FilePath, Info, Extra}};
Code when is_integer(Code) ->
local_redirect(<< ReqPath/binary, <<"/">>/binary >>,
Code, Req)
end.
slash_options(false) -> false;
slash_options(true) -> slash_options(?DEFAULT_REDIR);
slash_options(Code) when is_integer(Code) -> Code.
dir_with_slash(Req, ReqPath, FilePath, Info, Extra) ->
%% URL ends in slash - it's a proper directory reference
%% so redirect to index file if enabled
Opts = case lists:keyfind(directory_index, 1, Extra) of
{directory_index, X} -> X;
_ -> false % note: disabled by default
end,
case index_options(Opts) of
false ->
{cowboy_rest, Req, {FilePath, Info, Extra}};
Module when is_atom(Module) ->
{Module, Req, Extra};
{Code, File} when is_binary(File) ->
%% TODO: also allow list of multiple possible file names
local_redirect(<< ReqPath/binary, File/binary >>,
Code, Req)
end.
index_options({_, File}=Opt) when is_binary(File) -> Opt;
index_options(false) -> false;
index_options(true) -> index_options(?DEFAULT_REDIR);
index_options(Module) when is_atom(Module) -> Module;
index_options(Code) when is_integer(Code) -> {Code, ?DEFAULT_INDEX};
index_options(File) -> {?DEFAULT_REDIR, File}.
local_redirect(NewPath, Code, Req) ->
NewURI = cowboy_req:uri(Req, #{path => NewPath}),
{ok,
cowboy_req:reply(Code, #{<<"Location">> => NewURI}, <<>>, Req),
[]}.
read_file_info(Path, direct) ->
case file:read_file_info(Path, [{time, universal}]) of