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
3 changes: 2 additions & 1 deletion .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Metrics/AbcSize:
- 'lib/http/request.rb'
- 'lib/http/response.rb'

# Offense count: 69
# Offense count: 70
# Configuration parameters: CountComments, Max, CountAsOne, ExcludedMethods, IgnoredMethods.
# IgnoredMethods: refine
Metrics/BlockLength:
Expand All @@ -98,6 +98,7 @@ Metrics/BlockLength:
- 'spec/lib/http/response/parser_spec.rb'
- 'spec/lib/http/response/status_spec.rb'
- 'spec/lib/http/response_spec.rb'
- 'spec/lib/http/uri_spec.rb'
- 'spec/lib/http_spec.rb'
- 'spec/support/http_handling_shared.rb'

Expand Down
47 changes: 46 additions & 1 deletion lib/http/uri.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ class URI
def_delegators :@uri, :scheme, :normalized_scheme, :scheme=
def_delegators :@uri, :user, :normalized_user, :user=
def_delegators :@uri, :password, :normalized_password, :password=
def_delegators :@uri, :host, :normalized_host, :host=
def_delegators :@uri, :authority, :normalized_authority, :authority=
def_delegators :@uri, :origin, :origin=
def_delegators :@uri, :normalized_port, :port=
Expand All @@ -20,6 +19,18 @@ class URI
def_delegators :@uri, :fragment, :normalized_fragment, :fragment=
def_delegators :@uri, :omit, :join, :normalize

# Host, either a domain name or IP address. If the host is an IPv6 address, it will be returned
# without brackets surrounding it.
#
# @return [String] The host of the URI
attr_reader :host

# Normalized host, either a domain name or IP address. If the host is an IPv6 address, it will
# be returned without brackets surrounding it.
#
# @return [String] The normalized host of the URI
attr_reader :normalized_host

# @private
HTTP_SCHEME = "http"

Expand Down Expand Up @@ -83,6 +94,9 @@ def initialize(options_or_uri = {})
else
raise TypeError, "expected Hash for options, got #{options_or_uri.class}"
end

@host = process_ipv6_brackets(@uri.host)
@normalized_host = process_ipv6_brackets(@uri.normalized_host)
end

# Are these URI objects equal? Normalizes both URIs prior to comparison
Expand Down Expand Up @@ -110,6 +124,17 @@ def hash
@hash ||= to_s.hash * -1
end

# Sets the host component for the URI.
#
# @param [String, #to_str] new_host The new host component.
# @return [void]
def host=(new_host)
@uri.host = process_ipv6_brackets(new_host, :brackets => true)

@host = process_ipv6_brackets(@uri.host)
@normalized_host = process_ipv6_brackets(@uri.normalized_host)
end

# Port number, either as specified or the default if unspecified
#
# @return [Integer] port number
Expand Down Expand Up @@ -146,5 +171,25 @@ def to_s
def inspect
format("#<%s:0x%014x URI:%s>", self.class.name, object_id << 1, to_s)
end

private

# Process a URI host, adding or removing surrounding brackets if the host is an IPv6 address.
#
# @param [Boolean] brackets When true, brackets will be added to IPv6 addresses if missing. When
# false, they will be removed if present.
#
# @return [String] Host with IPv6 address brackets added or removed
def process_ipv6_brackets(raw_host, brackets: false)
ip = IPAddr.new(raw_host)

if ip.ipv6?
brackets ? "[#{ip}]" : ip.to_s
else
raw_host
end
rescue IPAddr::Error
raw_host
end
end
end
39 changes: 39 additions & 0 deletions spec/lib/http/uri_spec.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
# frozen_string_literal: true

RSpec.describe HTTP::URI do
let(:example_ipv6_address) { "2606:2800:220:1:248:1893:25c8:1946" }

let(:example_http_uri_string) { "http://example.com" }
let(:example_https_uri_string) { "https://example.com" }
let(:example_ipv6_uri_string) { "https://[#{example_ipv6_address}]" }

subject(:http_uri) { described_class.parse(example_http_uri_string) }
subject(:https_uri) { described_class.parse(example_https_uri_string) }
subject(:ipv6_uri) { described_class.parse(example_ipv6_uri_string) }

it "knows URI schemes" do
expect(http_uri.scheme).to eq "http"
Expand All @@ -20,6 +24,41 @@
expect(https_uri.port).to eq 443
end

describe "#host" do
it "strips brackets from IPv6 addresses" do
expect(ipv6_uri.host).to eq("2606:2800:220:1:248:1893:25c8:1946")
end
end

describe "#normalized_host" do
it "strips brackets from IPv6 addresses" do
expect(ipv6_uri.normalized_host).to eq("2606:2800:220:1:248:1893:25c8:1946")
end
end

describe "#host=" do
it "updates cached values for #host and #normalized_host" do
expect(http_uri.host).to eq("example.com")
expect(http_uri.normalized_host).to eq("example.com")

http_uri.host = "[#{example_ipv6_address}]"

expect(http_uri.host).to eq(example_ipv6_address)
expect(http_uri.normalized_host).to eq(example_ipv6_address)
end

it "ensures IPv6 addresses are bracketed in the inner Addressable::URI" do
expect(http_uri.host).to eq("example.com")
expect(http_uri.normalized_host).to eq("example.com")

http_uri.host = example_ipv6_address

expect(http_uri.host).to eq(example_ipv6_address)
expect(http_uri.normalized_host).to eq(example_ipv6_address)
expect(http_uri.instance_variable_get(:@uri).host).to eq("[#{example_ipv6_address}]")
end
end

describe "#dup" do
it "doesn't share internal value between duplicates" do
duplicated_uri = http_uri.dup
Expand Down