Class: HTTP::URI
- Inherits:
-
Object
- Object
- HTTP::URI
- Defined in:
- lib/http/uri.rb,
lib/http/uri/parsing.rb,
lib/http/uri/normalizer.rb
Overview
URI normalization and dot-segment removal
Defined Under Namespace
Classes: InvalidError
Constant Summary collapse
- HTTP_SCHEME =
HTTP scheme string
"http"- HTTPS_SCHEME =
HTTPS scheme string
"https"- PERCENT_ENCODE =
Pattern matching characters requiring percent-encoding
/[^\x21-\x7E]+/- DEFAULT_PORTS =
Default ports for supported URI schemes
{ "http" => 80, "https" => 443, "ws" => 80, "wss" => 443 }.freeze
- NEEDS_ADDRESSABLE =
Pattern for characters that stdlib’s URI.parse silently modifies
/[^\x20-\x7E]/- NORMALIZER =
Default URI normalizer
lambda do |uri| uri = HTTP::URI.parse uri scheme = uri.scheme&.downcase host = uri.normalized_host host = "[#{host}]" if host&.include?(":") default_port = scheme == HTTPS_SCHEME ? 443 : 80 HTTP::URI.new( scheme: scheme, user: uri.user, password: uri.password, host: host, port: (uri.port == default_port ? nil : uri.port), path: uri.path.empty? ? "/" : percent_encode(remove_dot_segments(uri.path)), query: percent_encode(uri.query), fragment: uri.fragment ) end
- DOT_SEGMENTS =
Standalone dot segments that terminate the algorithm
%w[. ..].freeze
- SINGLE_DOT_SEGMENT =
Matches “/.” followed by “/” or end-of-string
%r{\A/\.(?:/|\z)}- DOUBLE_DOT_SEGMENT =
Matches “/..” followed by “/” or end-of-string
%r{\A/\.\.(?:/|\z)}- LAST_SEGMENT =
Matches the last segment in a path (everything after the final “/”)
%r{/[^/]*\z}- FIRST_SEGMENT =
Matches the first path segment, with or without a leading “/”
%r{\A/?[^/]*}
Instance Attribute Summary collapse
-
#fragment ⇒ String?
readonly
URI fragment.
-
#host ⇒ String?
Host, either a domain name or IP address.
-
#normalized_host ⇒ String?
readonly
Normalized host.
-
#password ⇒ String?
readonly
Password component for authentication.
-
#path ⇒ String
URI path component.
-
#query ⇒ String?
URI query string.
-
#scheme ⇒ String?
readonly
URI scheme (e.g. “http”, “https”).
-
#user ⇒ String?
readonly
User component for authentication.
Class Method Summary collapse
-
.form_encode(form_values, sort: false) ⇒ String
Encodes key/value pairs as application/x-www-form-urlencoded.
-
.idna_to_ascii(host) ⇒ String
private
Convert a hostname to ASCII via IDNA (requires addressable).
-
.parse(uri) ⇒ HTTP::URI
Parse the given URI string, returning an HTTP::URI object.
-
.percent_encode(string) ⇒ String
private
Percent-encode matching characters in a string.
-
.require_addressable ⇒ void
private
Loads the addressable gem on first use.
Instance Method Summary collapse
-
#==(other) ⇒ TrueClass, FalseClass
Are these URI objects equal after normalization.
-
#deconstruct_keys(keys) ⇒ Hash{Symbol => Object}
Pattern matching interface.
-
#default_port ⇒ Integer?
Default port for the URI scheme.
-
#dup ⇒ HTTP::URI
Duplicates the URI object.
-
#eql?(other) ⇒ TrueClass, FalseClass
Are these URI objects equal without normalization.
-
#hash ⇒ Integer
Hash value based off the normalized form of a URI.
-
#http? ⇒ True, False
Checks whether the URI scheme is HTTP.
-
#https? ⇒ True, False
Checks whether the URI scheme is HTTPS.
-
#initialize(scheme: nil, user: nil, password: nil, host: nil, port: nil, path: nil, query: nil, fragment: nil) ⇒ HTTP::URI
constructor
Creates an HTTP::URI instance from the given keyword arguments.
-
#inspect ⇒ String
Returns human-readable representation of URI.
-
#join(other) ⇒ HTTP::URI
Resolves another URI against this one per RFC 3986.
-
#normalize ⇒ HTTP::URI
Returns a normalized copy of the URI.
-
#omit(*components) ⇒ HTTP::URI
Returns a new URI with the specified components removed.
-
#origin ⇒ String
The origin (scheme + host + port) per RFC 6454.
-
#port ⇒ Integer?
Port number, either as specified or the default.
-
#request_uri ⇒ String
The path and query for use in an HTTP request line.
-
#to_s ⇒ String
(also: #to_str)
Convert an HTTP::URI to a String.
Constructor Details
#initialize(scheme: nil, user: nil, password: nil, host: nil, port: nil, path: nil, query: nil, fragment: nil) ⇒ HTTP::URI
Creates an HTTP::URI instance from the given keyword arguments
127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/http/uri.rb', line 127 def initialize(scheme: nil, user: nil, password: nil, host: nil, port: nil, path: nil, query: nil, fragment: nil) @scheme = scheme @user = user @password = password @raw_host = host @host = process_ipv6_brackets(host) @normalized_host = normalize_host(@host) @port = port @path = path || "" @query = query @fragment = fragment end |
Instance Attribute Details
#fragment ⇒ String? (readonly)
URI fragment
84 85 86 |
# File 'lib/http/uri.rb', line 84 def fragment @fragment end |
#host ⇒ String?
Host, either a domain name or IP address
48 49 50 |
# File 'lib/http/uri.rb', line 48 def host @host end |
#normalized_host ⇒ String? (readonly)
Normalized host
57 58 59 |
# File 'lib/http/uri.rb', line 57 def normalized_host @normalized_host end |
#password ⇒ String? (readonly)
Password component for authentication
39 40 41 |
# File 'lib/http/uri.rb', line 39 def password @password end |
#path ⇒ String
URI path component
66 67 68 |
# File 'lib/http/uri.rb', line 66 def path @path end |
#query ⇒ String?
URI query string
75 76 77 |
# File 'lib/http/uri.rb', line 75 def query @query end |
#scheme ⇒ String? (readonly)
URI scheme (e.g. “http”, “https”)
21 22 23 |
# File 'lib/http/uri.rb', line 21 def scheme @scheme end |
#user ⇒ String? (readonly)
User component for authentication
30 31 32 |
# File 'lib/http/uri.rb', line 30 def user @user end |
Class Method Details
.form_encode(form_values, sort: false) ⇒ String
Encodes key/value pairs as application/x-www-form-urlencoded
37 38 39 40 41 |
# File 'lib/http/uri/parsing.rb', line 37 def self.form_encode(form_values, sort: false) return ::URI.encode_www_form(form_values) unless sort ::URI.encode_www_form(form_values.sort_by { |k, _| String(k) }) end |
.idna_to_ascii(host) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Convert a hostname to ASCII via IDNA (requires addressable)
72 73 74 75 76 77 |
# File 'lib/http/uri/parsing.rb', line 72 def self.idna_to_ascii(host) return host if host.ascii_only? require_addressable Addressable::IDNA.to_ascii(host) # steep:ignore end |
.parse(uri) ⇒ HTTP::URI
Parse the given URI string, returning an HTTP::URI object
15 16 17 18 19 20 21 22 23 24 25 |
# File 'lib/http/uri/parsing.rb', line 15 def self.parse(uri) return uri if uri.is_a?(self) raise InvalidError, "invalid URI: nil" if uri.nil? uri_string = begin String(uri) rescue TypeError, NoMethodError raise InvalidError, "invalid URI: #{uri.inspect}" end new(**parse_components(uri_string)) end |
.percent_encode(string) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Percent-encode matching characters in a string
49 50 51 52 53 |
# File 'lib/http/uri/parsing.rb', line 49 def self.percent_encode(string) string&.gsub(PERCENT_ENCODE) do |substr| substr.bytes.map { |c| format("%%%02X", c) }.join end end |
.require_addressable ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Loads the addressable gem on first use
60 61 62 63 64 65 |
# File 'lib/http/uri/parsing.rb', line 60 def self.require_addressable return if defined?(@addressable_loaded) require "addressable/uri" @addressable_loaded = true end |
Instance Method Details
#==(other) ⇒ TrueClass, FalseClass
Are these URI objects equal after normalization
150 151 152 |
# File 'lib/http/uri.rb', line 150 def ==(other) other.is_a?(URI) && String(normalize).eql?(String(other.normalize)) end |
#deconstruct_keys(keys) ⇒ Hash{Symbol => Object}
Pattern matching interface
367 368 369 370 371 |
# File 'lib/http/uri.rb', line 367 def deconstruct_keys(keys) hash = { scheme: @scheme, host: @host, port: port, path: @path, query: @query, fragment: @fragment, user: @user, password: @password } keys ? hash.slice(*keys) : hash end |
#default_port ⇒ Integer?
Default port for the URI scheme
212 213 214 |
# File 'lib/http/uri.rb', line 212 def default_port DEFAULT_PORTS[@scheme&.downcase] end |
#dup ⇒ HTTP::URI
Duplicates the URI object
323 324 325 326 327 328 |
# File 'lib/http/uri.rb', line 323 def dup self.class.new( scheme: @scheme, user: @user, password: @password, host: @raw_host, port: @port, path: @path, query: @query, fragment: @fragment ) end |
#eql?(other) ⇒ TrueClass, FalseClass
Are these URI objects equal without normalization
164 165 166 |
# File 'lib/http/uri.rb', line 164 def eql?(other) other.is_a?(URI) && String(self).eql?(String(other)) end |
#hash ⇒ Integer
Hash value based off the normalized form of a URI
175 176 177 |
# File 'lib/http/uri.rb', line 175 def hash @hash ||= [self.class, String(self)].hash end |
#http? ⇒ True, False
Checks whether the URI scheme is HTTP
300 301 302 |
# File 'lib/http/uri.rb', line 300 def http? HTTP_SCHEME.eql?(@scheme) end |
#https? ⇒ True, False
Checks whether the URI scheme is HTTPS
312 313 314 |
# File 'lib/http/uri.rb', line 312 def https? HTTPS_SCHEME.eql?(@scheme) end |
#inspect ⇒ String
Returns human-readable representation of URI
355 356 357 |
# File 'lib/http/uri.rb', line 355 def inspect format("#<%s:0x%014x URI:%s>", self.class, object_id << 1, self) end |
#join(other) ⇒ HTTP::URI
Resolves another URI against this one per RFC 3986
263 264 265 266 267 |
# File 'lib/http/uri.rb', line 263 def join(other) base = self.class.percent_encode(String(self)) ref = self.class.percent_encode(String(other)) self.class.parse(::URI.join(base, ref)) end |
#normalize ⇒ HTTP::URI
Returns a normalized copy of the URI
Lowercases scheme and host, strips default port. Used by #== to compare URIs for equivalence.
279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/http/uri.rb', line 279 def normalize self.class.new( scheme: @scheme&.downcase, user: @user, password: @password, host: @raw_host&.downcase, port: (@port unless port.eql?(default_port)), path: @path.empty? && @raw_host ? "/" : @path, query: @query, fragment: @fragment ) end |
#omit(*components) ⇒ HTTP::URI
Returns a new URI with the specified components removed
247 248 249 250 251 252 |
# File 'lib/http/uri.rb', line 247 def omit(*components) self.class.new( **{ scheme: @scheme, user: @user, password: @password, host: @raw_host, port: @port, path: @path, query: @query, fragment: @fragment }.except(*components) ) end |
#origin ⇒ String
The origin (scheme + host + port) per RFC 6454
223 224 225 226 |
# File 'lib/http/uri.rb', line 223 def origin port_suffix = ":#{port}" unless port.eql?(default_port) "#{String(@scheme).downcase}://#{String(@raw_host).downcase}#{port_suffix}" end |
#port ⇒ Integer?
Port number, either as specified or the default
201 202 203 |
# File 'lib/http/uri.rb', line 201 def port @port || default_port end |
#request_uri ⇒ String
The path and query for use in an HTTP request line
235 236 237 |
# File 'lib/http/uri.rb', line 235 def request_uri "#{'/' if @path.empty?}#{@path}#{"?#{@query}" if @query}" end |
#to_s ⇒ String Also known as: to_str
Convert an HTTP::URI to a String
337 338 339 340 341 342 343 344 345 |
# File 'lib/http/uri.rb', line 337 def to_s str = +"" str << "#{@scheme}:" if @scheme str << if @raw_host str << @path str << "?#{@query}" if @query str << "##{@fragment}" if @fragment str end |