123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479 |
- -module(cowboy_static).
- -include_lib("kernel/include/file.hrl").
- -export([init/3]).
- -export([rest_init/2]).
- -export([allowed_methods/2]).
- -export([malformed_request/2]).
- -export([resource_exists/2]).
- -export([forbidden/2]).
- -export([last_modified/2]).
- -export([generate_etag/2]).
- -export([content_types_provided/2]).
- -export([file_contents/2]).
- -export([path_to_mimetypes/2]).
- -type dirpath() :: string() | binary() | [binary()].
- -type dirspec() :: dirpath() | {priv, atom(), dirpath()}.
- -type mimedef() :: {binary(), binary(), [{binary(), binary()}]}.
- -type etagarg() :: {filepath, binary()} | {mtime, calendar:datetime()}
- | {inode, non_neg_integer()} | {filesize, non_neg_integer()}.
- -record(state, {
- filepath :: binary() | error,
- fileinfo :: {ok, #file_info{}} | {error, _} | error,
- mimetypes :: {fun((binary(), T) -> [mimedef()]), T} | undefined,
- etag_fun :: {fun(([etagarg()], T) ->
- undefined | {strong | weak, binary()}), T}}).
- init({_Transport, http}, _Req, _Opts) ->
- {upgrade, protocol, cowboy_rest}.
- -spec rest_init(Req, list()) -> {ok, Req, #state{}} when Req::cowboy_req:req().
- rest_init(Req, Opts) ->
- Directory = proplists:get_value(directory, Opts),
- Directory1 = directory_path(Directory),
- Mimetypes = proplists:get_value(mimetypes, Opts, []),
- Mimetypes1 = case Mimetypes of
- {{M, F}, E} -> {fun M:F/2, E};
- {_, _} -> Mimetypes;
- [] -> {fun path_to_mimetypes/2, []};
- [_|_] -> {fun path_to_mimetypes/2, Mimetypes}
- end,
- ETagFunction = case proplists:get_value(etag, Opts) of
- default -> {fun no_etag_function/2, undefined};
- undefined -> {fun no_etag_function/2, undefined};
- {attributes, []} -> {fun no_etag_function/2, undefined};
- {attributes, Attrs} -> {fun attr_etag_function/2, Attrs};
- {_, _}=ETagFunction1 -> ETagFunction1
- end,
- {Filepath, Req1} = case lists:keyfind(file, 1, Opts) of
- {_, Filepath2} -> {filepath_path(Filepath2), Req};
- false -> cowboy_req:path_info(Req)
- end,
- State = case check_path(Filepath) of
- error ->
- #state{filepath=error, fileinfo=error, mimetypes=undefined,
- etag_fun=ETagFunction};
- ok ->
- Filepath1 = join_paths(Directory1, Filepath),
- Fileinfo = file:read_file_info(Filepath1),
- #state{filepath=Filepath1, fileinfo=Fileinfo, mimetypes=Mimetypes1,
- etag_fun=ETagFunction}
- end,
- {ok, Req1, State}.
- -spec allowed_methods(Req, #state{})
- -> {[binary()], Req, #state{}} when Req::cowboy_req:req().
- allowed_methods(Req, State) ->
- {[<<"GET">>, <<"HEAD">>], Req, State}.
- -spec malformed_request(Req, #state{})
- -> {boolean(), Req, #state{}} when Req::cowboy_req:req().
- malformed_request(Req, #state{filepath=error}=State) ->
- {true, Req, State};
- malformed_request(Req, State) ->
- {false, Req, State}.
- -spec resource_exists(Req, #state{})
- -> {boolean(), Req, #state{}} when Req::cowboy_req:req().
- resource_exists(Req, #state{fileinfo={error, _}}=State) ->
- {false, Req, State};
- resource_exists(Req, #state{fileinfo={ok, Fileinfo}}=State) ->
- {Fileinfo#file_info.type =:= regular, Req, State}.
- -spec forbidden(Req, #state{})
- -> {boolean(), Req, #state{}} when Req::cowboy_req:req().
- forbidden(Req, #state{fileinfo={_, #file_info{type=directory}}}=State) ->
- {true, Req, State};
- forbidden(Req, #state{fileinfo={error, eacces}}=State) ->
- {true, Req, State};
- forbidden(Req, #state{fileinfo={error, _}}=State) ->
- {false, Req, State};
- forbidden(Req, #state{fileinfo={ok, #file_info{access=Access}}}=State) ->
- {not (Access =:= read orelse Access =:= read_write), Req, State}.
- -spec last_modified(Req, #state{})
- -> {calendar:datetime(), Req, #state{}} when Req::cowboy_req:req().
- last_modified(Req, #state{fileinfo={ok, #file_info{mtime=Modified}}}=State) ->
- {Modified, Req, State}.
- -spec generate_etag(Req, #state{})
- -> {undefined | binary(), Req, #state{}} when Req::cowboy_req:req().
- generate_etag(Req, #state{fileinfo={_, #file_info{type=regular, inode=INode,
- mtime=Modified, size=Filesize}}, filepath=Filepath,
- etag_fun={ETagFun, ETagData}}=State) ->
- ETagArgs = [
- {filepath, Filepath}, {filesize, Filesize},
- {inode, INode}, {mtime, Modified}],
- {ETagFun(ETagArgs, ETagData), Req, State};
- generate_etag(Req, State) ->
- {undefined, Req, State}.
- -spec content_types_provided(cowboy_req:req(), #state{}) -> tuple().
- content_types_provided(Req, #state{filepath=Filepath,
- mimetypes={MimetypesFun, MimetypesData}}=State) ->
- Mimetypes = [{T, file_contents}
- || T <- MimetypesFun(Filepath, MimetypesData)],
- {Mimetypes, Req, State}.
- -spec file_contents(cowboy_req:req(), #state{}) -> tuple().
- file_contents(Req, #state{filepath=Filepath,
- fileinfo={ok, #file_info{size=Filesize}}}=State) ->
- Writefile = fun(Socket, Transport) ->
- {ok, _} = Transport:sendfile(Socket, Filepath),
- ok
- end,
- {{stream, Filesize, Writefile}, Req, State}.
- -spec directory_path(dirspec()) -> dirpath().
- directory_path({priv_dir, App, []}) ->
- priv_dir_path(App);
- directory_path({priv_dir, App, [H|_]=Path}) when is_integer(H) ->
- filename:join(priv_dir_path(App), Path);
- directory_path({priv_dir, App, [H|_]=Path}) when is_binary(H) ->
- filename:join(filename:split(priv_dir_path(App)) ++ Path);
- directory_path({priv_dir, App, Path}) when is_binary(Path) ->
- filename:join(priv_dir_path(App), Path);
- directory_path(Path) ->
- Path.
- -spec filepath_path(dirpath()) -> Path::[binary()].
- filepath_path([H|_]=Path) when is_integer(H) ->
- filename:split(list_to_binary(Path));
- filepath_path(Path) when is_binary(Path) ->
- filename:split(Path);
- filepath_path([H|_]=Path) when is_binary(H) ->
- Path.
- -spec check_path(Path::[binary()]) -> ok | error.
- check_path([]) -> ok;
- check_path([<<"">>|_T]) -> error;
- check_path([<<".">>|_T]) -> error;
- check_path([<<"..">>|_T]) -> error;
- check_path([H|T]) ->
- case binary:match(H, <<"/">>) of
- {_, _} -> error;
- nomatch -> check_path(T)
- end.
- -spec join_paths(dirpath(), [binary()]) -> binary().
- join_paths([H|_]=Dirpath, Filepath) when is_integer(H) ->
- filename:join(filename:split(Dirpath) ++ Filepath);
- join_paths([H|_]=Dirpath, Filepath) when is_binary(H) ->
- filename:join(Dirpath ++ Filepath);
- join_paths(Dirpath, Filepath) when is_binary(Dirpath) ->
- filename:join([Dirpath] ++ Filepath);
- join_paths([], Filepath) ->
- filename:join(Filepath).
- -spec priv_dir_path(atom()) -> string().
- priv_dir_path(App) ->
- case code:priv_dir(App) of
- {error, bad_name} -> priv_dir_mod(App);
- Dir -> Dir
- end.
- -spec priv_dir_mod(atom()) -> string().
- priv_dir_mod(Mod) ->
- case code:which(Mod) of
- File when not is_list(File) -> "../priv";
- File -> filename:join([filename:dirname(File),"../priv"])
- end.
- -spec path_to_mimetypes(binary(), [{binary(), [mimedef()]}]) ->
- [mimedef()].
- path_to_mimetypes(Filepath, Extensions) when is_binary(Filepath) ->
- Ext = filename:extension(Filepath),
- case Ext of
- <<>> -> default_mimetype();
- _Ext -> path_to_mimetypes_(Ext, Extensions)
- end.
- -spec path_to_mimetypes_(binary(), [{binary(), [mimedef()]}]) -> [mimedef()].
- path_to_mimetypes_(Ext, Extensions) ->
- case lists:keyfind(cowboy_bstr:to_lower(Ext), 1, Extensions) of
- {_, MTs} -> MTs;
- _Unknown -> default_mimetype()
- end.
- -spec default_mimetype() -> [mimedef()].
- default_mimetype() ->
- [{<<"application">>, <<"octet-stream">>, []}].
- -spec no_etag_function([etagarg()], undefined) -> undefined.
- no_etag_function(_Args, undefined) ->
- undefined.
- -type fileattr() :: filepath | filesize | mtime | inode.
- -spec attr_etag_function([etagarg()], [fileattr()]) -> {strong, binary()}.
- attr_etag_function(Args, Attrs) ->
- [[_|H]|T] = [begin
- {_,Pair} = {_,{_,_}} = {Attr,lists:keyfind(Attr, 1, Args)},
- [$-|integer_to_list(erlang:phash2(Pair, 1 bsl 32), 16)]
- end || Attr <- Attrs],
- {strong, list_to_binary([H|T])}.
- -ifdef(TEST).
- -include_lib("eunit/include/eunit.hrl").
- -define(_eq(E, I), ?_assertEqual(E, I)).
- check_path_test_() ->
- C = fun check_path/1,
- [?_eq(error, C([<<>>])),
- ?_eq(ok, C([<<"abc">>])),
- ?_eq(error, C([<<".">>])),
- ?_eq(error, C([<<"..">>])),
- ?_eq(error, C([<<"/">>]))
- ].
- join_paths_test_() ->
- P = fun join_paths/2,
- [?_eq(<<"a">>, P([], [<<"a">>])),
- ?_eq(<<"a/b/c">>, P(<<"a/b">>, [<<"c">>])),
- ?_eq(<<"a/b/c">>, P("a/b", [<<"c">>])),
- ?_eq(<<"a/b/c">>, P([<<"a">>, <<"b">>], [<<"c">>]))
- ].
- directory_path_test_() ->
- P = fun directory_path/1,
- PL = fun(I) -> length(filename:split(P(I))) end,
- Base = PL({priv_dir, cowboy, []}),
- [?_eq(Base + 1, PL({priv_dir, cowboy, "a"})),
- ?_eq(Base + 1, PL({priv_dir, cowboy, <<"a">>})),
- ?_eq(Base + 1, PL({priv_dir, cowboy, [<<"a">>]})),
- ?_eq(Base + 2, PL({priv_dir, cowboy, "a/b"})),
- ?_eq(Base + 2, PL({priv_dir, cowboy, <<"a/b">>})),
- ?_eq(Base + 2, PL({priv_dir, cowboy, [<<"a">>, <<"b">>]})),
- ?_eq("a/b", P("a/b"))
- ].
- filepath_path_test_() ->
- P = fun filepath_path/1,
- [?_eq([<<"a">>], P("a")),
- ?_eq([<<"a">>], P(<<"a">>)),
- ?_eq([<<"a">>], P([<<"a">>])),
- ?_eq([<<"a">>, <<"b">>], P("a/b")),
- ?_eq([<<"a">>, <<"b">>], P(<<"a/b">>)),
- ?_eq([<<"a">>, <<"b">>], P([<<"a">>, <<"b">>]))
- ].
- -endif.
|