Skip to content

Commit fa50764

Browse files
committed
Backport safe_relative_path/1 from OTP 19.3
1 parent 89ad172 commit fa50764

File tree

2 files changed

+50
-5
lines changed

2 files changed

+50
-5
lines changed

src/elli_static.erl

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ maybe_file(Req, Prefix, Dir) ->
8888

8989
%% santize the path ensuring the request doesn't access any parent
9090
%% directories ... and reattach the slash if deemed safe
91-
SafePath = case filename:safe_relative_path(RawPath) of
91+
SafePath = case safe_relative_path(RawPath) of
9292
unsafe ->
9393
throw(?NOT_FOUND);
9494
%% return type quirk work around
@@ -110,3 +110,43 @@ maybe_file(Req, Prefix, Dir) ->
110110
_ ->
111111
nothing
112112
end.
113+
114+
115+
%% OTP_RELEASE macro was introduced in 21, `filename:safe_relative_path/1' in
116+
%% 19.3.
117+
-ifdef(OTP_RELEASE).
118+
-ifdef(?OTP_RELEASE >= 21).
119+
safe_relative_path(Path) ->
120+
filename:safe_relative_path(Path).
121+
-endif.
122+
-else.
123+
124+
%% @doc Backport of `filename:safe_relative_path/1' from 19.3. This code was
125+
%% lifted from:
126+
%% https://github.com/erlang/otp/blob/master/lib/stdlib/src/filename.erl#L811
127+
-spec safe_relative_path(binary()) -> unsafe | file:name_all().
128+
safe_relative_path(Path) ->
129+
case filename:pathtype(Path) of
130+
relative ->
131+
Cs0 = filename:split(Path),
132+
safe_relative_path_1(Cs0, []);
133+
_ ->
134+
unsafe
135+
end.
136+
137+
safe_relative_path_1([<<".">>|T], Acc) ->
138+
safe_relative_path_1(T, Acc);
139+
safe_relative_path_1([<<"..">>|T], Acc) ->
140+
climb(T, Acc);
141+
safe_relative_path_1([H|T], Acc) ->
142+
safe_relative_path_1(T, [H|Acc]);
143+
safe_relative_path_1([], []) ->
144+
[];
145+
safe_relative_path_1([], Acc) ->
146+
filename:join(lists:reverse(Acc)).
147+
148+
climb(_, []) ->
149+
unsafe;
150+
climb(T, [_|Acc]) ->
151+
safe_relative_path_1(T, Acc).
152+
-endif.

test/elli_static_tests.erl

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,18 @@ not_found() ->
3131
?assertMatch({{"HTTP/1.1",404,"Not Found"}, _Headers, "Not Found"}, Response).
3232

3333
safe_traversal() ->
34-
{ok, Response} = httpc:request("http://localhost:3000/elli_static/"
35-
"../elli_static/README.md"),
3634
{ok, File} = file:read_file("README.md"),
3735
Expected = binary_to_list(File),
36+
37+
{ok, Response1} = httpc:request("http://localhost:3000/elli_static/"
38+
"../elli_static/README.md"),
3839
?assertEqual([integer_to_list(iolist_size(Expected))],
39-
proplists:get_all_values("content-length", element(2, Response))),
40-
?assertMatch({_Status, _Headers, Expected}, Response).
40+
proplists:get_all_values("content-length", element(2, Response1))),
41+
?assertMatch({_Status, _Headers, Expected}, Response1),
42+
43+
44+
%% `Response' should match the same request above
45+
{ok, Response} = httpc:request("http://localhost:3000/elli_static/./README.md").
4146

4247
unsafe_traversal() ->
4348
%% compute the relative path to /etc/passwd

0 commit comments

Comments
 (0)