-
Notifications
You must be signed in to change notification settings - Fork 30
Expand file tree
/
Copy pathnova_file_controller.erl
More file actions
143 lines (128 loc) · 5.45 KB
/
Copy pathnova_file_controller.erl
File metadata and controls
143 lines (128 loc) · 5.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
-module(nova_file_controller).
-export([
get_file/1,
get_dir/1
]).
-include_lib("kernel/include/file.hrl").
get_file(#{extra_state := #{static := File, options := Options}, headers := Headers}) ->
Filepath = get_filepath(File),
MimeType =
case maps:get(mimetype, Options, undefined) of
undefined ->
{T, V, _} = cow_mimetypes:web(unicode:characters_to_binary(Filepath)),
<<T/binary, "/", V/binary>>;
MType ->
MType
end,
%% Fetch file size
#{size := Size} = file_info("", Filepath),
%% Check if Range header is present
case maps:get(<<"range">>, Headers, undefined) of
undefined ->
%% No range header, return full file
{sendfile, 200, #{}, {0, Size, Filepath}, MimeType};
<<"bytes=", RangeSpec/binary>> ->
%% Handle Range Request
case parse_range(RangeSpec, Size) of
{ok, {Start, End}} when Start < Size, End < Size, Start =< End ->
Length = End - Start + 1,
RespHeaders = #{<<"content-range">> => <<"bytes ", (integer_to_binary(Start))/binary, "-",
(integer_to_binary(End))/binary, "/",
(integer_to_binary(Size))/binary>>,
<<"content-length">> => integer_to_binary(Length)},
{sendfile, 206, RespHeaders, {Start, Length, Filepath}, MimeType};
_ ->
%% Invalid range request
{status, 416, #{<<"content-range">> => <<"bytes */", (integer_to_binary(Size))/binary>>}}
end
end;
get_file(_Req) ->
{status, 404}.
get_dir(#{extra_state := #{pathinfo := Pathinfo, static := Dir, options := Options}} = Req) ->
%% This case will be invoked if a directory was set with wildcard - pathinfo will then
%% contain the segments of the wildcard value
Filepath = get_filepath(Dir),
Filepath0 = lists:foldl(fun(F, Acc) -> filename:join(Acc, unicode:characters_to_list(uri_string:unquote(F))) end, Filepath, Pathinfo),
case filelib:is_dir(Filepath0) of
false ->
%% Check if it's a file
case filelib:is_file(Filepath0) of
true ->
%% It's a file
get_file(Req#{extra_state => #{static => {file, Filepath0}, options => Options}});
false ->
{status, 404}
end;
true ->
get_dir(Req#{extra_state => #{static => {dir, Filepath0}, options => Options}})
end;
get_dir(#{path := Path, extra_state := #{static := Dir, options := Options}} = Req) ->
Filepath = get_filepath(Dir),
{ok, Files} = file:list_dir(Filepath),
case get_index_file(Files, maps:get(index_files, Options, ["index.html"])) of
{ok, IndexFile} ->
get_file(Req#{extra_state => #{static => {file, filename:join(Filepath, IndexFile)}, options => Options}});
false ->
case maps:get(list_dir, Options, false) of
false ->
%% We will not show the directory listing
{status, 403};
true ->
{ok, Files} = file:list_dir(Filepath),
FileInfos = [file_info(Filepath, F) || F <- Files],
ParentDir = case re:replace(Path , "/[^/]+/?$", "") of
[[]] -> undefined;
Parent -> Parent
end,
{ok, #{date => calendar:local_time(), parent_dir => ParentDir, path => Path, files => FileInfos}}
end
end;
get_dir(_Req) ->
{status, 404}.
%%%%%%%%%%%%%%%%%%%%%%%%
%% Internal functions %%
%%%%%%%%%%%%%%%%%%%%%%%%
parse_range(RangeSpec, FileSize) ->
%% Parse the "bytes=" range header value
case binary:split(RangeSpec, <<"-">>, [global]) of
[StartBin, EndBin] when EndBin =/= <<>> ->
case {binary_to_integer(StartBin), binary_to_integer(EndBin)} of
{Start, End} when Start >= 0, End >= Start, End < FileSize ->
{ok, {Start, End}};
_ ->
error
end;
[StartBin, <<>>] when StartBin =/= <<>> ->
case binary_to_integer(StartBin) of
Start when Start >= 0, Start < FileSize ->
{ok, {Start, FileSize - 1}};
_ ->
error
end;
_ ->
error
end.
get_index_file([], _) -> false;
get_index_file([File|Tl], IndexFiles) ->
case lists:member(File, IndexFiles) of
true ->
{ok, File};
false ->
get_index_file(Tl, IndexFiles)
end.
get_filepath({file, LocalFile}) ->
LocalFile;
get_filepath({priv_file, App, PrivFile}) ->
filename:join(code:priv_dir(App), PrivFile);
get_filepath({dir, LocalPath}) ->
LocalPath;
get_filepath({priv_dir, App, LocalPath}) ->
filename:join(code:priv_dir(App), LocalPath).
file_info(Filepath, Filename) ->
case file:read_file_info(filename:join(Filepath, Filename)) of
{ok, #file_info{type = Type, size = Size, mtime = LastModified}} ->
#{type => Type, size => Size,
last_modified => LastModified, filename => unicode:characters_to_binary(Filename, utf8)};
_ ->
undefined
end.