diff --git a/lib/utopia/static/local_file.rb b/lib/utopia/static/local_file.rb index 556b2847..1336eb39 100644 --- a/lib/utopia/static/local_file.rb +++ b/lib/utopia/static/local_file.rb @@ -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: @@ -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 @@ -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. diff --git a/lib/utopia/static/middleware.rb b/lib/utopia/static/middleware.rb index e18e5470..cb37430d 100644 --- a/lib/utopia/static/middleware.rb +++ b/lib/utopia/static/middleware.rb @@ -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 diff --git a/releases.md b/releases.md index dbf7cb73..c4902c3f 100644 --- a/releases.md +++ b/releases.md @@ -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 diff --git a/test/utopia/static.rb b/test/utopia/static.rb index 09974da6..da6a2b4a 100755 --- a/test/utopia/static.rb +++ b/test/utopia/static.rb @@ -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