Skip to content

Commit af657c0

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

File tree

1 file changed

+75
-1
lines changed

1 file changed

+75
-1
lines changed

src/elli_static.erl

Lines changed: 75 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,77 @@ maybe_file(Req, Prefix, Dir) ->
110110
_ ->
111111
nothing
112112
end.
113+
114+
%% @doc Backport of `filename:safe_relative_path/1' from 19.3. This code was
115+
%% lifted from:
116+
%% https://github.com/erlang/otp/blob/master/lib/stdlib/src/filename.erl#L811
117+
-spec safe_relative_path(file:name_all()) -> unsafe | file:name_all().
118+
safe_relative_path(Path) ->
119+
%% prefer the OTP version of `safe_relative_path/1' if it exists
120+
case lists:member({safe_relative_path, 1},
121+
filename:module_info(exports)) of
122+
true ->
123+
%% to suppress xref warnings on 19.2 and lower
124+
Mod = filename,
125+
Mod:safe_relative_path(Path);
126+
false ->
127+
case filename:pathtype(Path) of
128+
relative ->
129+
Cs0 = filename:split(Path),
130+
safe_relative_path_1(Cs0, []);
131+
_ ->
132+
unsafe
133+
end
134+
end.
135+
136+
safe_relative_path_1(["."|T], Acc) ->
137+
safe_relative_path_1(T, Acc);
138+
safe_relative_path_1([<<".">>|T], Acc) ->
139+
safe_relative_path_1(T, Acc);
140+
safe_relative_path_1([".."|T], Acc) ->
141+
climb(T, Acc);
142+
safe_relative_path_1([<<"..">>|T], Acc) ->
143+
climb(T, Acc);
144+
safe_relative_path_1([H|T], Acc) ->
145+
safe_relative_path_1(T, [H|Acc]);
146+
safe_relative_path_1([], []) ->
147+
[];
148+
safe_relative_path_1([], Acc) ->
149+
filename:join(lists:reverse(Acc)).
150+
151+
climb(_, []) ->
152+
unsafe;
153+
climb(T, [_|Acc]) ->
154+
safe_relative_path_1(T, Acc).
155+
156+
157+
-ifdef(TEST).
158+
-include_lib("eunit/include/eunit.hrl").
159+
160+
safe_relative_path_test() ->
161+
%% binary args
162+
?assertMatch([], safe_relative_path(<<>>)),
163+
?assertMatch(unsafe, safe_relative_path(<<"/">>)),
164+
?assertMatch(unsafe, safe_relative_path(<<"/root">>)),
165+
?assertMatch(unsafe, safe_relative_path(<<"..">>)),
166+
?assertMatch(unsafe, safe_relative_path(<<"dir/../../dir">>)),
167+
?assertMatch(<<"dir">>, safe_relative_path(<<"dir">>)),
168+
?assertMatch(<<"dir">>, safe_relative_path(<<"dir/../dir">>)),
169+
?assertMatch(<<"dir/sub">>, safe_relative_path(<<"dir/sub/../sub">>)),
170+
?assertMatch(<<"dir/sub">>, safe_relative_path(<<"dir/./sub">>)),
171+
?assertMatch(<<"dir/sub">>, safe_relative_path(<<"dir/././../dir/././sub">>)),
172+
173+
%% string args
174+
%% (these are here for completeness since `filename:safe_relative_path'
175+
%% supports string arguments
176+
?assertMatch([], safe_relative_path("")),
177+
?assertMatch(unsafe, safe_relative_path("/")),
178+
?assertMatch(unsafe, safe_relative_path("/root")),
179+
?assertMatch(unsafe, safe_relative_path("..")),
180+
?assertMatch(unsafe, safe_relative_path("dir/../../dir")),
181+
?assertMatch("dir", safe_relative_path("dir")),
182+
?assertMatch("dir", safe_relative_path("dir/../dir")),
183+
?assertMatch("dir/sub", safe_relative_path("dir/sub/../sub")),
184+
?assertMatch("dir/sub", safe_relative_path("dir/./sub")),
185+
?assertMatch("dir/sub", safe_relative_path("dir/././../dir/././sub")).
186+
-endif.

0 commit comments

Comments
 (0)