1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
|
# frozen_string_literal: true
# Released under the MIT License.
# Copyright, 2025, by Samuel Williams.
require "protocol/http/accept_encoding"
describe Protocol::HTTP::AcceptEncoding do
let(:delegate) do
proc do |request|
Protocol::HTTP::Response[200, Protocol::HTTP::Headers["content-type" => "text/plain"], ["Hello World!"]]
end
end
let(:middleware) {Protocol::HTTP::AcceptEncoding.new(delegate)}
with "known encodings" do
it "can decode gzip responses" do
# Mock a response with gzip encoding
gzip_delegate = proc do |request|
Protocol::HTTP::Response[200,
Protocol::HTTP::Headers[
"content-type" => "text/plain",
"content-encoding" => "gzip"
],
["Hello World!"]
]
end
gzip_middleware = Protocol::HTTP::AcceptEncoding.new(gzip_delegate)
request = Protocol::HTTP::Request["GET", "/"]
response = gzip_middleware.call(request)
expect(response.headers).not.to have_keys("content-encoding")
expect(response.body).to be_a(Protocol::HTTP::Body::Inflate)
end
end
with "unknown encodings" do
it "preserves unknown content-encoding headers" do
# Mock a response with brotli encoding (not in DEFAULT_WRAPPERS)
br_delegate = proc do |request|
Protocol::HTTP::Response[200,
Protocol::HTTP::Headers[
"content-type" => "text/plain",
"content-encoding" => "br"
],
["Hello World!"] # This would actually be brotli-encoded in reality
]
end
br_middleware = Protocol::HTTP::AcceptEncoding.new(br_delegate)
request = Protocol::HTTP::Request["GET", "/"]
response = br_middleware.call(request)
# The bug: this currently fails because content-encoding gets removed
# when the middleware encounters an unknown encoding
expect(response.headers).to have_keys("content-encoding")
expect(response.headers["content-encoding"]).to be == ["br"]
# The body should remain untouched since we can't decode it
expect(response.body).not.to be_a(Protocol::HTTP::Body::Inflate)
end
it "preserves mixed known and unknown encodings" do
# Mock a response with multiple encodings where some are unknown
mixed_delegate = proc do |request|
Protocol::HTTP::Response[200,
Protocol::HTTP::Headers[
"content-type" => "text/plain",
"content-encoding" => "gzip, br" # gzip is known, br is unknown
],
["Hello World!"]
]
end
mixed_middleware = Protocol::HTTP::AcceptEncoding.new(mixed_delegate)
request = Protocol::HTTP::Request["GET", "/"]
response = mixed_middleware.call(request)
# The bug: this currently fails because the entire content-encoding
# header gets removed when ANY unknown encoding is present
expect(response.headers).to have_keys("content-encoding")
expect(response.headers["content-encoding"]).to be == ["gzip", "br"]
# The body should remain untouched since we can't decode the br part
expect(response.body).not.to be_a(Protocol::HTTP::Body::Inflate)
end
it "handles case-insensitive encoding names" do
# Mock a response with uppercase encoding name
uppercase_delegate = proc do |request|
Protocol::HTTP::Response[200,
Protocol::HTTP::Headers[
"content-type" => "text/plain",
"content-encoding" => "GZIP"
],
["Hello World!"]
]
end
uppercase_middleware = Protocol::HTTP::AcceptEncoding.new(uppercase_delegate)
request = Protocol::HTTP::Request["GET", "/"]
response = uppercase_middleware.call(request)
# This might also be a bug - encoding names should be case-insensitive
# but the current implementation uses exact string matching
expect(response.headers).not.to have_keys("content-encoding")
expect(response.body).to be_a(Protocol::HTTP::Body::Inflate)
end
end
with "issue #86 - transparent proxy scenario" do
it "preserves unknown content-encoding when acting as transparent proxy" do
# This test simulates the exact scenario described in issue #86
# where a transparent proxy fetches content with brotli encoding
# but the AcceptEncoding middleware doesn't know about brotli
# Mock upstream server that returns brotli-encoded content
upstream_delegate = proc do |request|
# Simulate a server responding with brotli encoding
Protocol::HTTP::Response[200,
Protocol::HTTP::Headers[
"content-type" => "text/html",
"content-encoding" => "br" # Server chose brotli
],
["<compressed brotli content>"] # This would be actual brotli data
]
end
# Proxy middleware that only knows about gzip
proxy_middleware = Protocol::HTTP::AcceptEncoding.new(upstream_delegate)
# Client request that accepts both gzip and brotli
request = Protocol::HTTP::Request["GET", "/some/resource"]
response = proxy_middleware.call(request)
# BUG: The content-encoding header should be preserved
# so the client knows the content is still brotli-encoded
expect(response.headers).to have_keys("content-encoding")
expect(response.headers["content-encoding"]).to be == ["br"]
# The body should remain untouched since proxy can't decode brotli
expect(response.body).not.to be_a(Protocol::HTTP::Body::Inflate)
expect(response.read).to be == "<compressed brotli content>"
end
end
with "empty or identity encodings" do
it "handles identity encoding correctly" do
identity_delegate = proc do |request|
Protocol::HTTP::Response[200,
Protocol::HTTP::Headers[
"content-type" => "text/plain",
"content-encoding" => "identity"
],
["Hello World!"]
]
end
identity_middleware = Protocol::HTTP::AcceptEncoding.new(identity_delegate)
request = Protocol::HTTP::Request["GET", "/"]
response = identity_middleware.call(request)
# Identity encoding means no encoding, so header should be removed
expect(response.headers).not.to have_keys("content-encoding")
expect(response.body).not.to be_a(Protocol::HTTP::Body::Inflate)
end
end
end
|