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
39 changes: 28 additions & 11 deletions lib/webrick/httprequest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -210,11 +210,17 @@ def parse(socket=nil)
@accept_encoding = HTTPUtils.parse_qvalues(self['accept-encoding'])
@accept_language = HTTPUtils.parse_qvalues(self['accept-language'])
end
return if @request_method == "CONNECT"
return if @unparsed_uri == "*"

setup_forwarded_info

if @request_method == "CONNECT" || @unparsed_uri == "*"
# For CONNECT and OPTIONS * requests, we still need to extract
# host and port from the Host header for CGI meta variables.
@host, @port = extract_host_port
return
end

begin
setup_forwarded_info
@request_uri = parse_uri(@unparsed_uri)
@path = HTTPUtils::unescape(@request_uri.path)
@path = HTTPUtils::normalize_path(@path)
Expand Down Expand Up @@ -500,25 +506,36 @@ def read_header(socket)
end
end

def parse_uri(str, scheme="http")
if @config[:Escape8bitURI]
str = HTTPUtils::escape8bit(str)
end
str.sub!(%r{\A/+}o, '/')
uri = URI::parse(str)
return uri if uri.absolute?
# Extracts host and port from request headers (Host, X-Forwarded-Host, etc.)
# or falls back to socket address or server config.
# Returns [host, port] where port is an Integer or nil.
def extract_host_port
if @forwarded_host
host, port = @forwarded_host, @forwarded_port
elsif self["host"]
host, port = parse_host_request_line(self["host"])
port = port.to_i if port
elsif @addr.size > 0
host, port = @addr[2], @addr[1]
else
host, port = @config[:ServerName], @config[:Port]
end

[host, port]
end

def parse_uri(str, scheme="http")
if @config[:Escape8bitURI]
str = HTTPUtils::escape8bit(str)
end
str.sub!(%r{\A/+}, '/')
uri = URI::parse(str)
return uri if uri.absolute?

host, port = extract_host_port
uri.scheme = @forwarded_proto || scheme
uri.host = host
uri.port = port ? port.to_i : nil
uri.port = port
return URI::parse(uri.to_s)
end

Expand Down
38 changes: 38 additions & 0 deletions test/webrick/test_httprequest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -701,4 +701,42 @@ def test_cookie_join
assert_equal 2, req.cookies.length
assert_equal 'a=1; b=2', req['cookie']
end

def test_options_asterisk
# Test that OPTIONS * requests properly extract host and port from Host header
msg = <<~HTTP.gsub("\n", "\r\n")
OPTIONS * HTTP/1.1
Host: test.ruby-lang.org:8080

HTTP
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
req.parse(StringIO.new(msg))
assert_equal("OPTIONS", req.request_method)
assert_equal("*", req.unparsed_uri)
assert_equal("test.ruby-lang.org", req.host)
assert_equal(8080, req.port)

# Verify meta_vars includes correct SERVER_NAME and SERVER_PORT
meta = req.meta_vars
assert_equal("test.ruby-lang.org", meta["SERVER_NAME"])
assert_equal("8080", meta["SERVER_PORT"])
end

def test_options_asterisk_default_port
# Test OPTIONS * with Host header without explicit port
msg = <<~HTTP.gsub("\n", "\r\n")
OPTIONS * HTTP/1.1
Host: test.ruby-lang.org

HTTP
req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
req.parse(StringIO.new(msg))
assert_equal("OPTIONS", req.request_method)
assert_equal("*", req.unparsed_uri)
assert_equal("test.ruby-lang.org", req.host)
assert_nil(req.port) # Port is nil when not specified

meta = req.meta_vars
assert_equal("test.ruby-lang.org", meta["SERVER_NAME"])
end
end
Loading