Skip to content
Closed
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
12 changes: 8 additions & 4 deletions lib/utopia/static/local_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ def modified?(request)
# Serve.
# @parameter request [Utopia::Request] The request.
# @parameter response_headers [Hash] The response headers.
# @parameter ranges [Array | Nil] The resolved byte ranges.
# @returns [Protocol::HTTP::Response] The response.
def serve(request, response_headers)
ranges = byte_ranges(request)
def serve(request, response_headers, ranges: byte_ranges(request))
size = bytesize

# A valid byte-range request with no satisfiable ranges cannot be fulfilled:
Expand All @@ -76,8 +76,7 @@ def serve(request, response_headers)

return Response[416, response_headers, []]
elsif ranges == nil or ranges.size != 1
# No ranges, or multiple ranges (which we don't support).
# TODO: Support multiple byte-ranges, for now just send entire file:
# With no range, or multiple unsupported ranges, send the entire file:
status = 200
response_headers[CONTENT_LENGTH] = size.to_s
range = nil
Expand Down Expand Up @@ -114,6 +113,11 @@ def byte_ranges(request)
end

return range.resolve(bytesize)
rescue Protocol::HTTP::Header::Range::ParseError
# Ignore malformed range headers and serve the complete representation:
request.headers.extract(["range"])

return nil
end

# Check whether an If-Range validator strongly matches this file.
Expand Down
3 changes: 2 additions & 1 deletion lib/utopia/static/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,11 @@ def respond(request, path, extension, content_type, localization: request.locali
end

if file = fetch_file(local_path)
ranges = file.byte_ranges(request)
response_headers = self.response_headers_for(file, content_type)

if file.modified?(request)
return file.serve(request, response_headers)
return file.serve(request, response_headers, ranges: ranges)
else
return Response[304, response_headers, []]
end
Expand Down
1 change: 1 addition & 0 deletions releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- **Security** Authenticate encrypted session cookies using AES-256-GCM. Existing session cookies are invalidated.
- Constrain content node local paths to the configured content root.
- **Security** Redact sensitive exception report fields and make bounded request body attachments opt-in.
- Ignore malformed `Range` headers when serving static files.
- Return `416 Range Not Satisfiable` for unsatisfiable static file byte ranges.

## v3.0.0
Expand Down
10 changes: 6 additions & 4 deletions test/utopia/static.rb
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,12 @@
expect(last_response.read).to be == "Hello World!"
end

it "should reject malformed ranges" do
expect do
client.get "/test.txt", {"range" => "bytes=4-1"}
end.to raise_exception(Protocol::HTTP::Header::Range::ParseError)
it "ignores malformed ranges" do
client.get "/test.txt", {"range" => "bytes=4-1"}

expect(last_response.status).to be == 200
expect(last_response.headers["content-range"]).to be_nil
expect(last_response.read).to be == "Hello World!"
end

it "expands relative roots during initialization" do
Expand Down
Loading