Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions lib/plug/static.ex
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ defmodule Plug.Static do
:forbidden ->
conn

status ->
:allowed ->
segments = Enum.map(segments, &URI.decode/1)

if invalid_path?(segments) do
Expand All @@ -215,17 +215,19 @@ defmodule Plug.Static do
range = get_req_header(conn, "range")

case file_encoding(conn, path, range, encodings) do
:error ->
conn
:error -> conn
triplet -> serve_static(triplet, conn, segments, range, options)
end

triplet ->
if status == :raise do
raise InvalidPathError,
"static file exists but is not in the :only list: #{Enum.join(segments, "/")}. " <>
"Add it to the :only list or use :only_matching for prefix matching"
end
:raise ->
segments = Enum.map(segments, &URI.decode/1)

serve_static(triplet, conn, segments, range, options)
if not invalid_path?(segments) and regular_file_info(path(from, segments)) do
raise InvalidPathError,
"static file exists but is not in the :only list: #{Enum.join(segments, "/")}. " <>
"Add it to the :only list or use :only_matching for prefix matching"
else
conn
end
end
end
Expand Down
65 changes: 65 additions & 0 deletions test/plug/static_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -895,4 +895,69 @@ defmodule Plug.StaticTest do

assert conn.status == 200
end

describe "raise_on_missing_only" do
test "passes through when path has colons and does not match :only" do
conn =
conn(:get, "/public/resource:identifier")
|> call(only: ["assets"], raise_on_missing_only: true)

assert conn.status == 404
assert conn.resp_body == "Passthrough"
end

test "passes through when path has dot-dot segments and does not match :only" do
conn =
conn(:get, "/public/..%2Fsecret")
|> call(only: ["assets"], raise_on_missing_only: true)

assert conn.status == 404
assert conn.resp_body == "Passthrough"
end

test "passes through when path has null bytes and does not match :only" do
conn =
conn(:get, "/public/file%00.txt")
|> call(only: ["assets"], raise_on_missing_only: true)

assert conn.status == 404
assert conn.resp_body == "Passthrough"
end

test "passes through when non-existent file does not match :only" do
conn =
conn(:get, "/public/nonexistent.txt")
|> call(only: ["assets"], raise_on_missing_only: true)

assert conn.status == 404
assert conn.resp_body == "Passthrough"
end

test "passes through with only_matching when path does not match prefix" do
conn =
conn(:get, "/public/resource:identifier")
|> call(only_matching: ["assets"], raise_on_missing_only: true)

assert conn.status == 404
assert conn.resp_body == "Passthrough"
end

test "raises when existing file is not in :only list" do
assert_raise Plug.Static.InvalidPathError,
~r/static file exists but is not in the :only list/,
fn ->
conn(:get, "/public/fixtures/static.txt")
|> call(only: ["assets"], raise_on_missing_only: true)
end
end

test "serves file normally when it matches :only list" do
conn =
conn(:get, "/public/fixtures/static.txt")
|> call(only: ["fixtures"], raise_on_missing_only: true)

assert conn.status == 200
assert conn.resp_body == "HELLO"
end
end
end