Skip to content

Commit 4bb005a

Browse files
authored
Return 416 for invalid range requests in Plug.Static (#1317)
1 parent 4803d6c commit 4bb005a

3 files changed

Lines changed: 62 additions & 2 deletions

File tree

lib/plug/static.ex

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ defmodule Plug.Static do
289289
{range_start, range_end} <- start_and_end(bytes, file_size) do
290290
send_range(conn, path, range_start, range_end, file_size, options)
291291
else
292+
:unsatisfiable -> send_unsatisfiable_range(conn, file_size, options)
292293
_ -> send_entire_file(conn, path, options)
293294
end
294295
end
@@ -297,6 +298,8 @@ defmodule Plug.Static do
297298
send_entire_file(conn, path, options)
298299
end
299300

301+
defp start_and_end(_range, 0), do: :error
302+
300303
defp start_and_end("-" <> rest, file_size) do
301304
case Integer.parse(rest) do
302305
{last, ""} when last > 0 and last <= file_size -> {file_size - last, file_size - 1}
@@ -306,15 +309,18 @@ defmodule Plug.Static do
306309

307310
defp start_and_end(range, file_size) do
308311
case Integer.parse(range) do
309-
{first, "-"} when first >= 0 ->
312+
{first, "-"} when first >= 0 and first < file_size ->
310313
{first, file_size - 1}
311314

312-
{first, "-" <> rest} when first >= 0 ->
315+
{first, "-" <> rest} when first >= 0 and first < file_size ->
313316
case Integer.parse(rest) do
314317
{last, ""} when last >= first -> {first, min(last, file_size - 1)}
315318
_ -> :error
316319
end
317320

321+
{first, "-" <> _} when first >= file_size ->
322+
:unsatisfiable
323+
318324
_ ->
319325
:error
320326
end
@@ -333,6 +339,14 @@ defmodule Plug.Static do
333339
|> halt()
334340
end
335341

342+
defp send_unsatisfiable_range(conn, file_size, options) do
343+
conn
344+
|> maybe_add_vary(options)
345+
|> put_resp_header("content-range", "bytes */#{file_size}")
346+
|> send_resp(416, "")
347+
|> halt()
348+
end
349+
336350
defp send_entire_file(conn, path, options) do
337351
conn
338352
|> maybe_add_vary(options)

test/fixtures/empty.txt

Whitespace-only changes.

test/plug/static_test.exs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,52 @@ defmodule Plug.StaticTest do
468468
assert get_resp_header(conn, "content-type") == ["text/plain"]
469469
end
470470

471+
test "returns 416 if range start is past the end of the file" do
472+
conn =
473+
conn(:get, "/public/fixtures/static.txt", [])
474+
|> put_req_header("range", "bytes=10-20")
475+
|> call()
476+
477+
assert conn.status == 416
478+
assert conn.resp_body == ""
479+
assert get_resp_header(conn, "content-range") == ["bytes */5"]
480+
assert get_resp_header(conn, "accept-ranges") == ["bytes"]
481+
end
482+
483+
test "returns 416 if open-ended range start is past the end of the file" do
484+
conn =
485+
conn(:get, "/public/fixtures/static.txt", [])
486+
|> put_req_header("range", "bytes=10-")
487+
|> call()
488+
489+
assert conn.status == 416
490+
assert get_resp_header(conn, "content-range") == ["bytes */5"]
491+
end
492+
493+
test "returns 416 if range start equals the file size" do
494+
for range <- ["bytes=5-", "bytes=5-9"] do
495+
conn =
496+
conn(:get, "/public/fixtures/static.txt", [])
497+
|> put_req_header("range", range)
498+
|> call()
499+
500+
assert conn.status == 416, "expected 416 for #{range}"
501+
assert get_resp_header(conn, "content-range") == ["bytes */5"]
502+
end
503+
end
504+
505+
test "ignores range and serves the file when it is zero bytes" do
506+
for range <- ["bytes=0-", "bytes=1-", "bytes=10-20"] do
507+
conn =
508+
conn(:get, "/public/fixtures/empty.txt", [])
509+
|> put_req_header("range", range)
510+
|> call()
511+
512+
assert conn.status == 200, "expected 200 for #{range}"
513+
assert conn.resp_body == ""
514+
end
515+
end
516+
471517
test "performs etag negotiation" do
472518
conn =
473519
conn(:get, "/public/fixtures/static.txt")

0 commit comments

Comments
 (0)