summaryrefslogtreecommitdiff
path: root/lib/uri/common.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/uri/common.rb')
-rw-r--r--lib/uri/common.rb1211
1 files changed, 447 insertions, 764 deletions
diff --git a/lib/uri/common.rb b/lib/uri/common.rb
index e1bc3582c7..a2fb531631 100644
--- a/lib/uri/common.rb
+++ b/lib/uri/common.rb
@@ -1,555 +1,65 @@
+# frozen_string_literal: true
#--
# = uri/common.rb
#
# Author:: Akira Yamada <akira@ruby-lang.org>
-# Revision:: $Id$
# License::
# You can redistribute it and/or modify it under the same term as Ruby.
#
# See URI for general documentation
#
-module URI
- #
- # Includes URI::REGEXP::PATTERN
- #
- module REGEXP
- #
- # Patterns used to parse URI's
- #
- module PATTERN
- # :stopdoc:
-
- # RFC 2396 (URI Generic Syntax)
- # RFC 2732 (IPv6 Literal Addresses in URL's)
- # RFC 2373 (IPv6 Addressing Architecture)
-
- # alpha = lowalpha | upalpha
- ALPHA = "a-zA-Z"
- # alphanum = alpha | digit
- ALNUM = "#{ALPHA}\\d"
-
- # hex = digit | "A" | "B" | "C" | "D" | "E" | "F" |
- # "a" | "b" | "c" | "d" | "e" | "f"
- HEX = "a-fA-F\\d"
- # escaped = "%" hex hex
- ESCAPED = "%[#{HEX}]{2}"
- # mark = "-" | "_" | "." | "!" | "~" | "*" | "'" |
- # "(" | ")"
- # unreserved = alphanum | mark
- UNRESERVED = "\\-_.!~*'()#{ALNUM}"
- # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
- # "$" | ","
- # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
- # "$" | "," | "[" | "]" (RFC 2732)
- RESERVED = ";/?:@&=+$,\\[\\]"
-
- # domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
- DOMLABEL = "(?:[#{ALNUM}](?:[-#{ALNUM}]*[#{ALNUM}])?)"
- # toplabel = alpha | alpha *( alphanum | "-" ) alphanum
- TOPLABEL = "(?:[#{ALPHA}](?:[-#{ALNUM}]*[#{ALNUM}])?)"
- # hostname = *( domainlabel "." ) toplabel [ "." ]
- HOSTNAME = "(?:#{DOMLABEL}\\.)*#{TOPLABEL}\\.?"
-
- # :startdoc:
- end # PATTERN
-
- # :startdoc:
- end # REGEXP
-
- # class that Parses String's into URI's
- #
- # It contains a Hash set of patterns and Regexp's that match and validate.
- #
- class Parser
- include REGEXP
-
- #
- # == Synopsis
- #
- # URI::Parser.new([opts])
- #
- # == Args
- #
- # The constructor accepts a hash as options for parser.
- # Keys of options are pattern names of URI components
- # and values of options are pattern strings.
- # The constructor generates set of regexps for parsing URIs.
- #
- # You can use the following keys:
- #
- # * :ESCAPED (URI::PATTERN::ESCAPED in default)
- # * :UNRESERVED (URI::PATTERN::UNRESERVED in default)
- # * :DOMLABEL (URI::PATTERN::DOMLABEL in default)
- # * :TOPLABEL (URI::PATTERN::TOPLABEL in default)
- # * :HOSTNAME (URI::PATTERN::HOSTNAME in default)
- #
- # == Examples
- #
- # p = URI::Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})")
- # u = p.parse("http://example.jp/%uABCD") #=> #<URI::HTTP:0xb78cf4f8 URL:http://example.jp/%uABCD>
- # URI.parse(u.to_s) #=> raises URI::InvalidURIError
- #
- # s = "http://examle.com/ABCD"
- # u1 = p.parse(s) #=> #<URI::HTTP:0xb78c3220 URL:http://example.com/ABCD>
- # u2 = URI.parse(s) #=> #<URI::HTTP:0xb78b6d54 URL:http://example.com/ABCD>
- # u1 == u2 #=> true
- # u1.eql?(u2) #=> false
- #
- def initialize(opts = {})
- @pattern = initialize_pattern(opts)
- @pattern.each_value {|v| v.freeze}
- @pattern.freeze
-
- @regexp = initialize_regexp(@pattern)
- @regexp.each_value {|v| v.freeze}
- @regexp.freeze
- end
-
- # The Hash of patterns.
- #
- # see also URI::Parser.initialize_pattern
- attr_reader :pattern
-
- # The Hash of Regexp
- #
- # see also URI::Parser.initialize_regexp
- attr_reader :regexp
-
- # Returns a split URI against regexp[:ABS_URI]
- def split(uri)
- case uri
- when ''
- # null uri
-
- when @regexp[:ABS_URI]
- scheme, opaque, userinfo, host, port,
- registry, path, query, fragment = $~[1..-1]
-
- # URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
-
- # absoluteURI = scheme ":" ( hier_part | opaque_part )
- # hier_part = ( net_path | abs_path ) [ "?" query ]
- # opaque_part = uric_no_slash *uric
-
- # abs_path = "/" path_segments
- # net_path = "//" authority [ abs_path ]
-
- # authority = server | reg_name
- # server = [ [ userinfo "@" ] hostport ]
-
- if !scheme
- raise InvalidURIError,
- "bad URI(absolute but no scheme): #{uri}"
- end
- if !opaque && (!path && (!host && !registry))
- raise InvalidURIError,
- "bad URI(absolute but no path): #{uri}"
- end
-
- when @regexp[:REL_URI]
- scheme = nil
- opaque = nil
-
- userinfo, host, port, registry,
- rel_segment, abs_path, query, fragment = $~[1..-1]
- if rel_segment && abs_path
- path = rel_segment + abs_path
- elsif rel_segment
- path = rel_segment
- elsif abs_path
- path = abs_path
- end
-
- # URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
-
- # relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ]
+require_relative "rfc2396_parser"
+require_relative "rfc3986_parser"
- # net_path = "//" authority [ abs_path ]
- # abs_path = "/" path_segments
- # rel_path = rel_segment [ abs_path ]
-
- # authority = server | reg_name
- # server = [ [ userinfo "@" ] hostport ]
-
- else
- raise InvalidURIError, "bad URI(is not URI?): #{uri}"
- end
-
- path = '' if !path && !opaque # (see RFC2396 Section 5.2)
- ret = [
- scheme,
- userinfo, host, port, # X
- registry, # X
- path, # Y
- opaque, # Y
- query,
- fragment
- ]
- return ret
- end
-
- #
- # == Args
- #
- # +uri+::
- # String
- #
- # == Description
- #
- # parses +uri+ and constructs either matching URI scheme object
- # (FTP, HTTP, HTTPS, LDAP, LDAPS, or MailTo) or URI::Generic
- #
- # == Usage
- #
- # p = URI::Parser.new
- # p.parse("ldap://ldap.example.com/dc=example?user=john")
- # #=> #<URI::LDAP:0x00000000b9e7e8 URL:ldap://ldap.example.com/dc=example?user=john>
- #
- def parse(uri)
- scheme, userinfo, host, port,
- registry, path, opaque, query, fragment = self.split(uri)
-
- if scheme && URI.scheme_list.include?(scheme.upcase)
- URI.scheme_list[scheme.upcase].new(scheme, userinfo, host, port,
- registry, path, opaque, query,
- fragment, self)
- else
- Generic.new(scheme, userinfo, host, port,
- registry, path, opaque, query,
- fragment, self)
- end
- end
-
-
- #
- # == Args
- #
- # +uris+::
- # an Array of Strings
- #
- # == Description
- #
- # Attempts to parse and merge a set of URIs
- #
- def join(*uris)
- uris[0] = convert_to_uri(uris[0])
- uris.inject :merge
- end
-
- #
- # :call-seq:
- # extract( str )
- # extract( str, schemes )
- # extract( str, schemes ) {|item| block }
- #
- # == Args
- #
- # +str+::
- # String to search
- # +schemes+::
- # Patterns to apply to +str+
- #
- # == Description
- #
- # Attempts to parse and merge a set of URIs
- # If no +block+ given , then returns the result,
- # else it calls +block+ for each element in result.
- #
- # see also URI::Parser.make_regexp
- #
- def extract(str, schemes = nil)
- if block_given?
- str.scan(make_regexp(schemes)) { yield $& }
- nil
- else
- result = []
- str.scan(make_regexp(schemes)) { result.push $& }
- result
- end
- end
-
- # returns Regexp that is default self.regexp[:ABS_URI_REF],
- # unless +schemes+ is provided. Then it is a Regexp.union with self.pattern[:X_ABS_URI]
- def make_regexp(schemes = nil)
- unless schemes
- @regexp[:ABS_URI_REF]
- else
- /(?=#{Regexp.union(*schemes)}:)#{@pattern[:X_ABS_URI]}/x
- end
- end
-
- #
- # :call-seq:
- # escape( str )
- # escape( str, unsafe )
- #
- # == Args
- #
- # +str+::
- # String to make safe
- # +unsafe+::
- # Regexp to apply. Defaults to self.regexp[:UNSAFE]
- #
- # == Description
- #
- # constructs a safe String from +str+, removing unsafe characters,
- # replacing them with codes.
- #
- def escape(str, unsafe = @regexp[:UNSAFE])
- unless unsafe.kind_of?(Regexp)
- # perhaps unsafe is String object
- unsafe = Regexp.new("[#{Regexp.quote(unsafe)}]", false)
- end
- str.gsub(unsafe) do
- us = $&
- tmp = ''
- us.each_byte do |uc|
- tmp << sprintf('%%%02X', uc)
- end
- tmp
- end.force_encoding(Encoding::US_ASCII)
- end
-
- #
- # :call-seq:
- # unescape( str )
- # unescape( str, unsafe )
- #
- # == Args
- #
- # +str+::
- # String to remove escapes from
- # +unsafe+::
- # Regexp to apply. Defaults to self.regexp[:ESCAPED]
- #
- # == Description
- #
- # Removes escapes from +str+
- #
- def unescape(str, escaped = @regexp[:ESCAPED])
- str.gsub(escaped) { [$&[1, 2].hex].pack('C') }.force_encoding(str.encoding)
- end
-
- @@to_s = Kernel.instance_method(:to_s)
- def inspect
- @@to_s.bind(self).call
- end
-
- private
-
- # Constructs the default Hash of patterns
- def initialize_pattern(opts = {})
- ret = {}
- ret[:ESCAPED] = escaped = (opts.delete(:ESCAPED) || PATTERN::ESCAPED)
- ret[:UNRESERVED] = unreserved = opts.delete(:UNRESERVED) || PATTERN::UNRESERVED
- ret[:RESERVED] = reserved = opts.delete(:RESERVED) || PATTERN::RESERVED
- ret[:DOMLABEL] = opts.delete(:DOMLABEL) || PATTERN::DOMLABEL
- ret[:TOPLABEL] = opts.delete(:TOPLABEL) || PATTERN::TOPLABEL
- ret[:HOSTNAME] = hostname = opts.delete(:HOSTNAME)
-
- # RFC 2396 (URI Generic Syntax)
- # RFC 2732 (IPv6 Literal Addresses in URL's)
- # RFC 2373 (IPv6 Addressing Architecture)
-
- # uric = reserved | unreserved | escaped
- ret[:URIC] = uric = "(?:[#{unreserved}#{reserved}]|#{escaped})"
- # uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" |
- # "&" | "=" | "+" | "$" | ","
- ret[:URIC_NO_SLASH] = uric_no_slash = "(?:[#{unreserved};?:@&=+$,]|#{escaped})"
- # query = *uric
- ret[:QUERY] = query = "#{uric}*"
- # fragment = *uric
- ret[:FRAGMENT] = fragment = "#{uric}*"
-
- # hostname = *( domainlabel "." ) toplabel [ "." ]
- # reg-name = *( unreserved / pct-encoded / sub-delims ) # RFC3986
- unless hostname
- ret[:HOSTNAME] = hostname = "(?:[a-zA-Z0-9\\-.]|%\\h\\h)+"
- end
-
- # RFC 2373, APPENDIX B:
- # IPv6address = hexpart [ ":" IPv4address ]
- # IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT
- # hexpart = hexseq | hexseq "::" [ hexseq ] | "::" [ hexseq ]
- # hexseq = hex4 *( ":" hex4)
- # hex4 = 1*4HEXDIG
- #
- # XXX: This definition has a flaw. "::" + IPv4address must be
- # allowed too. Here is a replacement.
- #
- # IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT
- ret[:IPV4ADDR] = ipv4addr = "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"
- # hex4 = 1*4HEXDIG
- hex4 = "[#{PATTERN::HEX}]{1,4}"
- # lastpart = hex4 | IPv4address
- lastpart = "(?:#{hex4}|#{ipv4addr})"
- # hexseq1 = *( hex4 ":" ) hex4
- hexseq1 = "(?:#{hex4}:)*#{hex4}"
- # hexseq2 = *( hex4 ":" ) lastpart
- hexseq2 = "(?:#{hex4}:)*#{lastpart}"
- # IPv6address = hexseq2 | [ hexseq1 ] "::" [ hexseq2 ]
- ret[:IPV6ADDR] = ipv6addr = "(?:#{hexseq2}|(?:#{hexseq1})?::(?:#{hexseq2})?)"
-
- # IPv6prefix = ( hexseq1 | [ hexseq1 ] "::" [ hexseq1 ] ) "/" 1*2DIGIT
- # unused
-
- # ipv6reference = "[" IPv6address "]" (RFC 2732)
- ret[:IPV6REF] = ipv6ref = "\\[#{ipv6addr}\\]"
-
- # host = hostname | IPv4address
- # host = hostname | IPv4address | IPv6reference (RFC 2732)
- ret[:HOST] = host = "(?:#{hostname}|#{ipv4addr}|#{ipv6ref})"
- # port = *digit
- port = '\d*'
- # hostport = host [ ":" port ]
- ret[:HOSTPORT] = hostport = "#{host}(?::#{port})?"
-
- # userinfo = *( unreserved | escaped |
- # ";" | ":" | "&" | "=" | "+" | "$" | "," )
- ret[:USERINFO] = userinfo = "(?:[#{unreserved};:&=+$,]|#{escaped})*"
-
- # pchar = unreserved | escaped |
- # ":" | "@" | "&" | "=" | "+" | "$" | ","
- pchar = "(?:[#{unreserved}:@&=+$,]|#{escaped})"
- # param = *pchar
- param = "#{pchar}*"
- # segment = *pchar *( ";" param )
- segment = "#{pchar}*(?:;#{param})*"
- # path_segments = segment *( "/" segment )
- ret[:PATH_SEGMENTS] = path_segments = "#{segment}(?:/#{segment})*"
-
- # server = [ [ userinfo "@" ] hostport ]
- server = "(?:#{userinfo}@)?#{hostport}"
- # reg_name = 1*( unreserved | escaped | "$" | "," |
- # ";" | ":" | "@" | "&" | "=" | "+" )
- ret[:REG_NAME] = reg_name = "(?:[#{unreserved}$,;:@&=+]|#{escaped})+"
- # authority = server | reg_name
- authority = "(?:#{server}|#{reg_name})"
-
- # rel_segment = 1*( unreserved | escaped |
- # ";" | "@" | "&" | "=" | "+" | "$" | "," )
- ret[:REL_SEGMENT] = rel_segment = "(?:[#{unreserved};@&=+$,]|#{escaped})+"
-
- # scheme = alpha *( alpha | digit | "+" | "-" | "." )
- ret[:SCHEME] = scheme = "[#{PATTERN::ALPHA}][\\-+.#{PATTERN::ALPHA}\\d]*"
-
- # abs_path = "/" path_segments
- ret[:ABS_PATH] = abs_path = "/#{path_segments}"
- # rel_path = rel_segment [ abs_path ]
- ret[:REL_PATH] = rel_path = "#{rel_segment}(?:#{abs_path})?"
- # net_path = "//" authority [ abs_path ]
- ret[:NET_PATH] = net_path = "//#{authority}(?:#{abs_path})?"
-
- # hier_part = ( net_path | abs_path ) [ "?" query ]
- ret[:HIER_PART] = hier_part = "(?:#{net_path}|#{abs_path})(?:\\?(?:#{query}))?"
- # opaque_part = uric_no_slash *uric
- ret[:OPAQUE_PART] = opaque_part = "#{uric_no_slash}#{uric}*"
-
- # absoluteURI = scheme ":" ( hier_part | opaque_part )
- ret[:ABS_URI] = abs_uri = "#{scheme}:(?:#{hier_part}|#{opaque_part})"
- # relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ]
- ret[:REL_URI] = rel_uri = "(?:#{net_path}|#{abs_path}|#{rel_path})(?:\\?#{query})?"
-
- # URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
- ret[:URI_REF] = "(?:#{abs_uri}|#{rel_uri})?(?:##{fragment})?"
-
- ret[:X_ABS_URI] = "
- (#{scheme}): (?# 1: scheme)
- (?:
- (#{opaque_part}) (?# 2: opaque)
- |
- (?:(?:
- //(?:
- (?:(?:(#{userinfo})@)? (?# 3: userinfo)
- (?:(#{host})(?::(\\d*))?))? (?# 4: host, 5: port)
- |
- (#{reg_name}) (?# 6: registry)
- )
- |
- (?!//)) (?# XXX: '//' is the mark for hostport)
- (#{abs_path})? (?# 7: path)
- )(?:\\?(#{query}))? (?# 8: query)
- )
- (?:\\#(#{fragment}))? (?# 9: fragment)
- "
-
- ret[:X_REL_URI] = "
- (?:
- (?:
- //
- (?:
- (?:(#{userinfo})@)? (?# 1: userinfo)
- (#{host})?(?::(\\d*))? (?# 2: host, 3: port)
- |
- (#{reg_name}) (?# 4: registry)
- )
- )
- |
- (#{rel_segment}) (?# 5: rel_segment)
- )?
- (#{abs_path})? (?# 6: abs_path)
- (?:\\?(#{query}))? (?# 7: query)
- (?:\\#(#{fragment}))? (?# 8: fragment)
- "
-
- ret
- end
-
- # Constructs the default Hash of Regexp's
- def initialize_regexp(pattern)
- ret = {}
-
- # for URI::split
- ret[:ABS_URI] = Regexp.new('\A\s*' + pattern[:X_ABS_URI] + '\s*\z', Regexp::EXTENDED)
- ret[:REL_URI] = Regexp.new('\A\s*' + pattern[:X_REL_URI] + '\s*\z', Regexp::EXTENDED)
-
- # for URI::extract
- ret[:URI_REF] = Regexp.new(pattern[:URI_REF])
- ret[:ABS_URI_REF] = Regexp.new(pattern[:X_ABS_URI], Regexp::EXTENDED)
- ret[:REL_URI_REF] = Regexp.new(pattern[:X_REL_URI], Regexp::EXTENDED)
-
- # for URI::escape/unescape
- ret[:ESCAPED] = Regexp.new(pattern[:ESCAPED])
- ret[:UNSAFE] = Regexp.new("[^#{pattern[:UNRESERVED]}#{pattern[:RESERVED]}]")
-
- # for Generic#initialize
- ret[:SCHEME] = Regexp.new("\\A#{pattern[:SCHEME]}\\z")
- ret[:USERINFO] = Regexp.new("\\A#{pattern[:USERINFO]}\\z")
- ret[:HOST] = Regexp.new("\\A#{pattern[:HOST]}\\z")
- ret[:PORT] = Regexp.new("\\A#{pattern[:PORT]}\\z")
- ret[:OPAQUE] = Regexp.new("\\A#{pattern[:OPAQUE_PART]}\\z")
- ret[:REGISTRY] = Regexp.new("\\A#{pattern[:REG_NAME]}\\z")
- ret[:ABS_PATH] = Regexp.new("\\A#{pattern[:ABS_PATH]}\\z")
- ret[:REL_PATH] = Regexp.new("\\A#{pattern[:REL_PATH]}\\z")
- ret[:QUERY] = Regexp.new("\\A#{pattern[:QUERY]}\\z")
- ret[:FRAGMENT] = Regexp.new("\\A#{pattern[:FRAGMENT]}\\z")
-
- ret
- end
-
- def convert_to_uri(uri)
- if uri.is_a?(URI::Generic)
- uri
- elsif uri = String.try_convert(uri)
- parse(uri)
- else
- raise ArgumentError,
- "bad argument (expected URI object or URI string)"
- end
+module URI
+ # The default parser instance for RFC 2396.
+ RFC2396_PARSER = RFC2396_Parser.new
+ Ractor.make_shareable(RFC2396_PARSER) if defined?(Ractor)
+
+ # The default parser instance for RFC 3986.
+ RFC3986_PARSER = RFC3986_Parser.new
+ Ractor.make_shareable(RFC3986_PARSER) if defined?(Ractor)
+
+ # The default parser instance.
+ DEFAULT_PARSER = RFC3986_PARSER
+ Ractor.make_shareable(DEFAULT_PARSER) if defined?(Ractor)
+
+ # Set the default parser instance.
+ def self.parser=(parser = RFC3986_PARSER)
+ remove_const(:Parser) if defined?(::URI::Parser)
+ const_set("Parser", parser.class)
+
+ remove_const(:PARSER) if defined?(::URI::PARSER)
+ const_set("PARSER", parser)
+
+ remove_const(:REGEXP) if defined?(::URI::REGEXP)
+ remove_const(:PATTERN) if defined?(::URI::PATTERN)
+ if Parser == RFC2396_Parser
+ const_set("REGEXP", URI::RFC2396_REGEXP)
+ const_set("PATTERN", URI::RFC2396_REGEXP::PATTERN)
end
- end # class Parser
-
- # URI::Parser.new
- DEFAULT_PARSER = Parser.new
- DEFAULT_PARSER.pattern.each_pair do |sym, str|
- unless REGEXP::PATTERN.const_defined?(sym)
- REGEXP::PATTERN.const_set(sym, str)
+ Parser.new.regexp.each_pair do |sym, str|
+ remove_const(sym) if const_defined?(sym, false)
+ const_set(sym, str)
end
end
- DEFAULT_PARSER.regexp.each_pair do |sym, str|
- const_set(sym, str)
+ self.parser = RFC3986_PARSER
+
+ def self.const_missing(const) # :nodoc:
+ if const == :REGEXP
+ warn "URI::REGEXP is obsolete. Use URI::RFC2396_REGEXP explicitly.", uplevel: 1 if $VERBOSE
+ URI::RFC2396_REGEXP
+ elsif value = RFC2396_PARSER.regexp[const]
+ warn "URI::#{const} is obsolete. Use URI::RFC2396_PARSER.regexp[#{const.inspect}] explicitly.", uplevel: 1 if $VERBOSE
+ value
+ elsif value = RFC2396_Parser.const_get(const)
+ warn "URI::#{const} is obsolete. Use URI::RFC2396_Parser::#{const} explicitly.", uplevel: 1 if $VERBOSE
+ value
+ else
+ super
+ end
end
module Util # :nodoc:
@@ -575,7 +85,7 @@ module URI
end
else
raise ArgumentError,
- "expected Array of or Hash of components of #{klass.to_s} (#{klass.component[1..-1].join(', ')})"
+ "expected Array of or Hash of components of #{klass} (#{klass.component[1..-1].join(', ')})"
end
tmp[:scheme] = klass.to_s.sub(/\A.*::/, '').downcase
@@ -584,80 +94,104 @@ module URI
module_function :make_components_hash
end
- # module for escaping unsafe characters with codes.
- module Escape
- #
- # == Synopsis
- #
- # URI.escape(str [, unsafe])
- #
- # == Args
- #
- # +str+::
- # String to replaces in.
- # +unsafe+::
- # Regexp that matches all symbols that must be replaced with codes.
- # By default uses <tt>REGEXP::UNSAFE</tt>.
- # When this argument is a String, it represents a character set.
- #
- # == Description
- #
- # Escapes the string, replacing all unsafe characters with codes.
- #
- # == Usage
- #
- # require 'uri'
- #
- # enc_uri = URI.escape("http://example.com/?a=\11\15")
- # p enc_uri
- # # => "http://example.com/?a=%09%0D"
- #
- # p URI.unescape(enc_uri)
- # # => "http://example.com/?a=\t\r"
- #
- # p URI.escape("@?@!", "!?")
- # # => "@%3F@%21"
- #
- def escape(*arg)
- warn "#{caller(1)[0]}: warning: URI.escape is obsolete" if $VERBOSE
- DEFAULT_PARSER.escape(*arg)
- end
- alias encode escape
- #
- # == Synopsis
- #
- # URI.unescape(str)
- #
- # == Args
- #
- # +str+::
- # Unescapes the string.
- #
- # == Usage
- #
- # require 'uri'
- #
- # enc_uri = URI.escape("http://example.com/?a=\11\15")
- # p enc_uri
- # # => "http://example.com/?a=%09%0D"
- #
- # p URI.unescape(enc_uri)
- # # => "http://example.com/?a=\t\r"
- #
- def unescape(*arg)
- warn "#{caller(1)[0]}: warning: URI.unescape is obsolete" if $VERBOSE
- DEFAULT_PARSER.unescape(*arg)
+ module Schemes # :nodoc:
+ class << self
+ ReservedChars = ".+-"
+ EscapedChars = "\u01C0\u01C1\u01C2"
+ # Use Lo category chars as escaped chars for TruffleRuby, which
+ # does not allow Symbol categories as identifiers.
+
+ def escape(name)
+ unless name and name.ascii_only?
+ return nil
+ end
+ name.upcase.tr(ReservedChars, EscapedChars)
+ end
+
+ def unescape(name)
+ name.tr(EscapedChars, ReservedChars).encode(Encoding::US_ASCII).upcase
+ end
+
+ def find(name)
+ const_get(name, false) if name and const_defined?(name, false)
+ end
+
+ def register(name, klass)
+ unless scheme = escape(name)
+ raise ArgumentError, "invalid character as scheme - #{name}"
+ end
+ const_set(scheme, klass)
+ end
+
+ def list
+ constants.map { |name|
+ [unescape(name.to_s), const_get(name)]
+ }.to_h
+ end
end
- alias decode unescape
- end # module Escape
+ end
+ private_constant :Schemes
- extend Escape
- include REGEXP
+ # Registers the given +klass+ as the class to be instantiated
+ # when parsing a \URI with the given +scheme+:
+ #
+ # URI.register_scheme('MS_SEARCH', URI::Generic) # => URI::Generic
+ # URI.scheme_list['MS_SEARCH'] # => URI::Generic
+ #
+ # Note that after calling String#upcase on +scheme+, it must be a valid
+ # constant name.
+ def self.register_scheme(scheme, klass)
+ Schemes.register(scheme, klass)
+ end
- @@schemes = {}
- # Returns a Hash of the defined schemes
+ # Returns a hash of the defined schemes:
+ #
+ # URI.scheme_list
+ # # =>
+ # {"MAILTO"=>URI::MailTo,
+ # "LDAPS"=>URI::LDAPS,
+ # "WS"=>URI::WS,
+ # "HTTP"=>URI::HTTP,
+ # "HTTPS"=>URI::HTTPS,
+ # "LDAP"=>URI::LDAP,
+ # "FILE"=>URI::File,
+ # "FTP"=>URI::FTP}
+ #
+ # Related: URI.register_scheme.
def self.scheme_list
- @@schemes
+ Schemes.list
+ end
+
+ # :stopdoc:
+ INITIAL_SCHEMES = scheme_list
+ private_constant :INITIAL_SCHEMES
+ Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor)
+ # :startdoc:
+
+ # Returns a new object constructed from the given +scheme+, +arguments+,
+ # and +default+:
+ #
+ # - The new object is an instance of <tt>URI.scheme_list[scheme.upcase]</tt>.
+ # - The object is initialized by calling the class initializer
+ # using +scheme+ and +arguments+.
+ # See URI::Generic.new.
+ #
+ # Examples:
+ #
+ # values = ['john.doe', 'www.example.com', '123', nil, '/forum/questions/', nil, 'tag=networking&order=newest', 'top']
+ # URI.for('https', *values)
+ # # => #<URI::HTTPS https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
+ # URI.for('foo', *values, default: URI::HTTP)
+ # # => #<URI::HTTP foo://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
+ #
+ def self.for(scheme, *arguments, default: Generic)
+ const_name = Schemes.escape(scheme)
+
+ uri_class = INITIAL_SCHEMES[const_name]
+ uri_class ||= Schemes.find(const_name)
+ uri_class ||= default
+
+ return uri_class.new(scheme, *arguments)
end
#
@@ -677,112 +211,67 @@ module URI
#
class BadURIError < Error; end
- #
- # == Synopsis
- #
- # URI::split(uri)
- #
- # == Args
- #
- # +uri+::
- # String with URI.
- #
- # == Description
- #
- # Splits the string on following parts and returns array with result:
- #
- # * Scheme
- # * Userinfo
- # * Host
- # * Port
- # * Registry
- # * Path
- # * Opaque
- # * Query
- # * Fragment
- #
- # == Usage
- #
- # require 'uri'
- #
- # p URI.split("http://www.ruby-lang.org/")
- # # => ["http", nil, "www.ruby-lang.org", nil, nil, "/", nil, nil, nil]
+ # Returns a 9-element array representing the parts of the \URI
+ # formed from the string +uri+;
+ # each array element is a string or +nil+:
+ #
+ # names = %w[scheme userinfo host port registry path opaque query fragment]
+ # values = URI.split('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
+ # names.zip(values)
+ # # =>
+ # [["scheme", "https"],
+ # ["userinfo", "john.doe"],
+ # ["host", "www.example.com"],
+ # ["port", "123"],
+ # ["registry", nil],
+ # ["path", "/forum/questions/"],
+ # ["opaque", nil],
+ # ["query", "tag=networking&order=newest"],
+ # ["fragment", "top"]]
#
def self.split(uri)
- DEFAULT_PARSER.split(uri)
+ PARSER.split(uri)
end
+ # Returns a new \URI object constructed from the given string +uri+:
#
- # == Synopsis
- #
- # URI::parse(uri_str)
+ # URI.parse('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
+ # # => #<URI::HTTPS https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
+ # URI.parse('http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
+ # # => #<URI::HTTP http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
#
- # == Args
- #
- # +uri_str+::
- # String with URI.
- #
- # == Description
- #
- # Creates one of the URI's subclasses instance from the string.
- #
- # == Raises
- #
- # URI::InvalidURIError
- # Raised if URI given is not a correct one.
- #
- # == Usage
- #
- # require 'uri'
- #
- # uri = URI.parse("http://www.ruby-lang.org/")
- # p uri
- # # => #<URI::HTTP:0x202281be URL:http://www.ruby-lang.org/>
- # p uri.scheme
- # # => "http"
- # p uri.host
- # # => "www.ruby-lang.org"
+ # It's recommended to first URI::RFC2396_PARSER.escape string +uri+
+ # if it may contain invalid URI characters.
#
def self.parse(uri)
- DEFAULT_PARSER.parse(uri)
+ PARSER.parse(uri)
end
+ # Merges the given URI strings +str+
+ # per {RFC 2396}[https://www.rfc-editor.org/rfc/rfc2396.html].
#
- # == Synopsis
- #
- # URI::join(str[, str, ...])
- #
- # == Args
- #
- # +str+::
- # String(s) to work with
- #
- # == Description
- #
- # Joins URIs.
- #
- # == Usage
- #
- # require 'uri'
+ # Each string in +str+ is converted to an
+ # {RFC3986 URI}[https://www.rfc-editor.org/rfc/rfc3986.html] before being merged.
#
- # p URI.join("http://example.com/","main.rbx")
- # # => #<URI::HTTP:0x2022ac02 URL:http://localhost/main.rbx>
+ # Examples:
#
- # p URI.join('http://example.com', 'foo')
- # # => #<URI::HTTP:0x01ab80a0 URL:http://example.com/foo>
+ # URI.join("http://example.com/","main.rbx")
+ # # => #<URI::HTTP http://example.com/main.rbx>
#
- # p URI.join('http://example.com', '/foo', '/bar')
- # # => #<URI::HTTP:0x01aaf0b0 URL:http://example.com/bar>
+ # URI.join('http://example.com', 'foo')
+ # # => #<URI::HTTP http://example.com/foo>
#
- # p URI.join('http://example.com', '/foo', 'bar')
- # # => #<URI::HTTP:0x801a92af0 URL:http://example.com/bar>
+ # URI.join('http://example.com', '/foo', '/bar')
+ # # => #<URI::HTTP http://example.com/bar>
#
- # p URI.join('http://example.com', '/foo/', 'bar')
- # # => #<URI::HTTP:0x80135a3a0 URL:http://example.com/foo/bar>
+ # URI.join('http://example.com', '/foo', 'bar')
+ # # => #<URI::HTTP http://example.com/bar>
#
+ # URI.join('http://example.com', '/foo/', 'bar')
+ # # => #<URI::HTTP http://example.com/foo/bar>
#
def self.join(*str)
- DEFAULT_PARSER.join(*str)
+ PARSER.join(*str)
end
#
@@ -795,7 +284,7 @@ module URI
# +str+::
# String to extract URIs from.
# +schemes+::
- # Limit URI matching to a specific schemes.
+ # Limit URI matching to specific schemes.
#
# == Description
#
@@ -809,8 +298,9 @@ module URI
# URI.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.")
# # => ["http://foo.example.com/bla", "mailto:test@example.com"]
#
- def self.extract(str, schemes = nil, &block)
- DEFAULT_PARSER.extract(str, schemes, &block)
+ def self.extract(str, schemes = nil, &block) # :nodoc:
+ warn "URI.extract is obsolete", uplevel: 1 if $VERBOSE
+ PARSER.extract(str, schemes, &block)
end
#
@@ -825,9 +315,10 @@ module URI
# whose scheme is one of the match_schemes.
#
# == Description
+ #
# Returns a Regexp object which matches to URI-like strings.
# The Regexp object returned by this method includes arbitrary
- # number of capture group (parentheses). Never rely on it's number.
+ # number of capture group (parentheses). Never rely on its number.
#
# == Usage
#
@@ -837,98 +328,242 @@ module URI
# html_string.slice(URI.regexp)
#
# # remove ftp URIs
- # html_string.sub(URI.regexp(['ftp'])
+ # html_string.sub(URI.regexp(['ftp']), '')
#
# # You should not rely on the number of parentheses
# html_string.scan(URI.regexp) do |*matches|
# p $&
# end
#
- def self.regexp(schemes = nil)
- DEFAULT_PARSER.make_regexp(schemes)
+ def self.regexp(schemes = nil)# :nodoc:
+ warn "URI.regexp is obsolete", uplevel: 1 if $VERBOSE
+ PARSER.make_regexp(schemes)
end
TBLENCWWWCOMP_ = {} # :nodoc:
256.times do |i|
- TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
+ TBLENCWWWCOMP_[-i.chr] = -('%%%02X' % i)
end
+ TBLENCURICOMP_ = TBLENCWWWCOMP_.dup.freeze # :nodoc:
TBLENCWWWCOMP_[' '] = '+'
TBLENCWWWCOMP_.freeze
TBLDECWWWCOMP_ = {} # :nodoc:
256.times do |i|
h, l = i>>4, i&15
- TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
- TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
- TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
- TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
+ TBLDECWWWCOMP_[-('%%%X%X' % [h, l])] = -i.chr
+ TBLDECWWWCOMP_[-('%%%x%X' % [h, l])] = -i.chr
+ TBLDECWWWCOMP_[-('%%%X%x' % [h, l])] = -i.chr
+ TBLDECWWWCOMP_[-('%%%x%x' % [h, l])] = -i.chr
end
TBLDECWWWCOMP_['+'] = ' '
TBLDECWWWCOMP_.freeze
- HTML5ASCIIINCOMPAT = [Encoding::UTF_7, Encoding::UTF_16BE, Encoding::UTF_16LE,
- Encoding::UTF_32BE, Encoding::UTF_32LE] # :nodoc:
-
- # Encode given +str+ to URL-encoded form data.
+ # Returns a URL-encoded string derived from the given string +str+.
+ #
+ # The returned string:
+ #
+ # - Preserves:
+ #
+ # - Characters <tt>'*'</tt>, <tt>'.'</tt>, <tt>'-'</tt>, and <tt>'_'</tt>.
+ # - Character in ranges <tt>'a'..'z'</tt>, <tt>'A'..'Z'</tt>,
+ # and <tt>'0'..'9'</tt>.
+ #
+ # Example:
#
- # This method doesn't convert *, -, ., 0-9, A-Z, _, a-z, but does convert SP
- # (ASCII space) to + and converts others to %XX.
+ # URI.encode_www_form_component('*.-_azAZ09')
+ # # => "*.-_azAZ09"
#
- # If +enc+ is given, convert +str+ to the encoding before percent encoding.
+ # - Converts:
#
- # This is an implementation of
- # http://www.w3.org/TR/html5/forms.html#url-encoded-form-data
+ # - Character <tt>' '</tt> to character <tt>'+'</tt>.
+ # - Any other character to "percent notation";
+ # the percent notation for character <i>c</i> is <tt>'%%%X' % c.ord</tt>.
#
- # See URI.decode_www_form_component, URI.encode_www_form
+ # Example:
+ #
+ # URI.encode_www_form_component('Here are some punctuation characters: ,;?:')
+ # # => "Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A"
+ #
+ # Encoding:
+ #
+ # - If +str+ has encoding Encoding::ASCII_8BIT, argument +enc+ is ignored.
+ # - Otherwise +str+ is converted first to Encoding::UTF_8
+ # (with suitable character replacements),
+ # and then to encoding +enc+.
+ #
+ # In either case, the returned string has forced encoding Encoding::US_ASCII.
+ #
+ # Related: URI.encode_uri_component (encodes <tt>' '</tt> as <tt>'%20'</tt>).
def self.encode_www_form_component(str, enc=nil)
+ _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP_, str, enc)
+ end
+
+ # Returns a string decoded from the given \URL-encoded string +str+.
+ #
+ # The given string is first encoded as Encoding::ASCII-8BIT (using String#b),
+ # then decoded (as below), and finally force-encoded to the given encoding +enc+.
+ #
+ # The returned string:
+ #
+ # - Preserves:
+ #
+ # - Characters <tt>'*'</tt>, <tt>'.'</tt>, <tt>'-'</tt>, and <tt>'_'</tt>.
+ # - Character in ranges <tt>'a'..'z'</tt>, <tt>'A'..'Z'</tt>,
+ # and <tt>'0'..'9'</tt>.
+ #
+ # Example:
+ #
+ # URI.decode_www_form_component('*.-_azAZ09')
+ # # => "*.-_azAZ09"
+ #
+ # - Converts:
+ #
+ # - Character <tt>'+'</tt> to character <tt>' '</tt>.
+ # - Each "percent notation" to an ASCII character.
+ #
+ # Example:
+ #
+ # URI.decode_www_form_component('Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A')
+ # # => "Here are some punctuation characters: ,;?:"
+ #
+ # Related: URI.decode_uri_component (preserves <tt>'+'</tt>).
+ def self.decode_www_form_component(str, enc=Encoding::UTF_8)
+ _decode_uri_component(/\+|%\h\h/, str, enc)
+ end
+
+ # Like URI.encode_www_form_component, except that <tt>' '</tt> (space)
+ # is encoded as <tt>'%20'</tt> (instead of <tt>'+'</tt>).
+ def self.encode_uri_component(str, enc=nil)
+ _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCURICOMP_, str, enc)
+ end
+
+ # Like URI.decode_www_form_component, except that <tt>'+'</tt> is preserved.
+ def self.decode_uri_component(str, enc=Encoding::UTF_8)
+ _decode_uri_component(/%\h\h/, str, enc)
+ end
+
+ # Returns a string derived from the given string +str+ with
+ # URI-encoded characters matching +regexp+ according to +table+.
+ def self._encode_uri_component(regexp, table, str, enc)
str = str.to_s.dup
if str.encoding != Encoding::ASCII_8BIT
if enc && enc != Encoding::ASCII_8BIT
str.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace)
- str.encode!(enc, fallback: ->(x){"&#{x.ord};"})
+ str.encode!(enc, fallback: ->(x){"&##{x.ord};"})
end
str.force_encoding(Encoding::ASCII_8BIT)
end
- str.gsub!(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP_)
+ str.gsub!(regexp, table)
str.force_encoding(Encoding::US_ASCII)
end
+ private_class_method :_encode_uri_component
- # Decode given +str+ of URL-encoded form data.
- #
- # This decodes + to SP.
- #
- # See URI.encode_www_form_component, URI.decode_www_form
- def self.decode_www_form_component(str, enc=Encoding::UTF_8)
- raise ArgumentError, "invalid %-encoding (#{str})" unless /\A[^%]*(?:%\h\h[^%]*)*\z/ =~ str
- str.b.gsub(/\+|%\h\h/, TBLDECWWWCOMP_).force_encoding(enc)
+ # Returns a string decoding characters matching +regexp+ from the
+ # given \URL-encoded string +str+.
+ def self._decode_uri_component(regexp, str, enc)
+ raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/.match?(str)
+ str.b.gsub(regexp, TBLDECWWWCOMP_).force_encoding(enc)
end
+ private_class_method :_decode_uri_component
- # Generate URL-encoded form data from given +enum+.
+ # Returns a URL-encoded string derived from the given
+ # {Enumerable}[rdoc-ref:Enumerable@Enumerable+in+Ruby+Classes]
+ # +enum+.
+ #
+ # The result is suitable for use as form data
+ # for an \HTTP request whose <tt>Content-Type</tt> is
+ # <tt>'application/x-www-form-urlencoded'</tt>.
+ #
+ # The returned string consists of the elements of +enum+,
+ # each converted to one or more URL-encoded strings,
+ # and all joined with character <tt>'&'</tt>.
+ #
+ # Simple examples:
+ #
+ # URI.encode_www_form([['foo', 0], ['bar', 1], ['baz', 2]])
+ # # => "foo=0&bar=1&baz=2"
+ # URI.encode_www_form({foo: 0, bar: 1, baz: 2})
+ # # => "foo=0&bar=1&baz=2"
+ #
+ # The returned string is formed using method URI.encode_www_form_component,
+ # which converts certain characters:
+ #
+ # URI.encode_www_form('f#o': '/', 'b-r': '$', 'b z': '@')
+ # # => "f%23o=%2F&b-r=%24&b+z=%40"
+ #
+ # When +enum+ is Array-like, each element +ele+ is converted to a field:
+ #
+ # - If +ele+ is an array of two or more elements,
+ # the field is formed from its first two elements
+ # (and any additional elements are ignored):
+ #
+ # name = URI.encode_www_form_component(ele[0], enc)
+ # value = URI.encode_www_form_component(ele[1], enc)
+ # "#{name}=#{value}"
#
- # This generates application/x-www-form-urlencoded data defined in HTML5
- # from given an Enumerable object.
+ # Examples:
#
- # This internally uses URI.encode_www_form_component(str).
+ # URI.encode_www_form([%w[foo bar], %w[baz bat bah]])
+ # # => "foo=bar&baz=bat"
+ # URI.encode_www_form([['foo', 0], ['bar', :baz, 'bat']])
+ # # => "foo=0&bar=baz"
#
- # This method doesn't convert the encoding of given items, so convert them
- # before call this method if you want to send data as other than original
- # encoding or mixed encoding data. (Strings which are encoded in an HTML5
- # ASCII incompatible encoding are converted to UTF-8.)
+ # - If +ele+ is an array of one element,
+ # the field is formed from <tt>ele[0]</tt>:
#
- # This method doesn't handle files. When you send a file, use
- # multipart/form-data.
+ # URI.encode_www_form_component(ele[0])
#
- # This refers http://url.spec.whatwg.org/#concept-urlencoded-serializer
+ # Example:
#
- # URI.encode_www_form([["q", "ruby"], ["lang", "en"]])
- # #=> "q=ruby&lang=en"
- # URI.encode_www_form("q" => "ruby", "lang" => "en")
- # #=> "q=ruby&lang=en"
- # URI.encode_www_form("q" => ["ruby", "perl"], "lang" => "en")
- # #=> "q=ruby&q=perl&lang=en"
- # URI.encode_www_form([["q", "ruby"], ["q", "perl"], ["lang", "en"]])
- # #=> "q=ruby&q=perl&lang=en"
+ # URI.encode_www_form([['foo'], [:bar], [0]])
+ # # => "foo&bar&0"
+ #
+ # - Otherwise the field is formed from +ele+:
+ #
+ # URI.encode_www_form_component(ele)
+ #
+ # Example:
+ #
+ # URI.encode_www_form(['foo', :bar, 0])
+ # # => "foo&bar&0"
+ #
+ # The elements of an Array-like +enum+ may be mixture:
+ #
+ # URI.encode_www_form([['foo', 0], ['bar', 1, 2], ['baz'], :bat])
+ # # => "foo=0&bar=1&baz&bat"
+ #
+ # When +enum+ is Hash-like,
+ # each +key+/+value+ pair is converted to one or more fields:
+ #
+ # - If +value+ is
+ # {Array-convertible}[rdoc-ref:implicit_conversion.rdoc@Array-Convertible+Objects],
+ # each element +ele+ in +value+ is paired with +key+ to form a field:
+ #
+ # name = URI.encode_www_form_component(key, enc)
+ # value = URI.encode_www_form_component(ele, enc)
+ # "#{name}=#{value}"
+ #
+ # Example:
+ #
+ # URI.encode_www_form({foo: [:bar, 1], baz: [:bat, :bam, 2]})
+ # # => "foo=bar&foo=1&baz=bat&baz=bam&baz=2"
+ #
+ # - Otherwise, +key+ and +value+ are paired to form a field:
+ #
+ # name = URI.encode_www_form_component(key, enc)
+ # value = URI.encode_www_form_component(value, enc)
+ # "#{name}=#{value}"
+ #
+ # Example:
+ #
+ # URI.encode_www_form({foo: 0, bar: 1, baz: 2})
+ # # => "foo=0&bar=1&baz=2"
+ #
+ # The elements of a Hash-like +enum+ may be mixture:
+ #
+ # URI.encode_www_form({foo: [0, 1], bar: 2})
+ # # => "foo=0&foo=1&bar=2"
#
- # See URI.encode_www_form_component, URI.decode_www_form
def self.encode_www_form(enum, enc=nil)
enum.map do |k,v|
if v.nil?
@@ -949,22 +584,39 @@ module URI
end.join('&')
end
- # Decode URL-encoded form data from given +str+.
+ # Returns name/value pairs derived from the given string +str+,
+ # which must be an ASCII string.
+ #
+ # The method may be used to decode the body of Net::HTTPResponse object +res+
+ # for which <tt>res['Content-Type']</tt> is <tt>'application/x-www-form-urlencoded'</tt>.
+ #
+ # The returned data is an array of 2-element subarrays;
+ # each subarray is a name/value pair (both are strings).
+ # Each returned string has encoding +enc+,
+ # and has had invalid characters removed via
+ # {String#scrub}[rdoc-ref:String#scrub].
+ #
+ # A simple example:
+ #
+ # URI.decode_www_form('foo=0&bar=1&baz')
+ # # => [["foo", "0"], ["bar", "1"], ["baz", ""]]
+ #
+ # The returned strings have certain conversions,
+ # similar to those performed in URI.decode_www_form_component:
+ #
+ # URI.decode_www_form('f%23o=%2F&b-r=%24&b+z=%40')
+ # # => [["f#o", "/"], ["b-r", "$"], ["b z", "@"]]
#
- # This decodes application/x-www-form-urlencoded data
- # and returns array of key-value array.
+ # The given string may contain consecutive separators:
#
- # This refers http://url.spec.whatwg.org/#concept-urlencoded-parser ,
- # so this supports only &-separator, don't support ;-separator.
+ # URI.decode_www_form('foo=0&&bar=1&&baz=2')
+ # # => [["foo", "0"], ["", ""], ["bar", "1"], ["", ""], ["baz", "2"]]
#
- # ary = URI.decode_www_form("a=1&a=2&b=3")
- # p ary #=> [['a', '1'], ['a', '2'], ['b', '3']]
- # p ary.assoc('a').last #=> '1'
- # p ary.assoc('b').last #=> '3'
- # p ary.rassoc('a').last #=> '2'
- # p Hash[ary] # => {"a"=>"2", "b"=>"3"}
+ # A different separator may be specified:
+ #
+ # URI.decode_www_form('foo=0--bar=1--baz', separator: '--')
+ # # => [["foo", "0"], ["bar", "1"], ["baz", ""]]
#
- # See URI.decode_www_form_component, URI.encode_www_form
def self.decode_www_form(str, enc=Encoding::UTF_8, separator: '&', use__charset_: false, isindex: false)
raise ArgumentError, "the input of #{self.name}.#{__method__} must be ASCII only string" unless str.ascii_only?
ary = []
@@ -976,7 +628,7 @@ module URI
if isindex
if sep.empty?
val = key
- key = ''
+ key = +''
end
isindex = false
end
@@ -990,7 +642,7 @@ module URI
if val
val.gsub!(/\+|%\h\h/, TBLDECWWWCOMP_)
else
- val = ''
+ val = +''
end
ary << [key, val]
@@ -1005,7 +657,24 @@ module URI
end
private
- # curl http://encoding.spec.whatwg.org/encodings.json|rb -rpp -rjson -e'H={};h={"shift_jis"=>"Windows-31J","euc-jp"=>"cp51932","iso-2022-jp"=>"cp50221","x-mac-cyrillic"=>"macCyrillic"};JSON($<.read).map{|x|x["encodings"]}.flatten.each{|x|Encoding.find(n=h.fetch(n=x["name"],n))rescue next;x["labels"].each{|y|H[y]=n}};pp H'
+=begin command for WEB_ENCODINGS_
+ curl https://encoding.spec.whatwg.org/encodings.json|
+ ruby -rjson -e 'H={}
+ h={
+ "shift_jis"=>"Windows-31J",
+ "euc-jp"=>"cp51932",
+ "iso-2022-jp"=>"cp50221",
+ "x-mac-cyrillic"=>"macCyrillic",
+ }
+ JSON($<.read).map{|x|x["encodings"]}.flatten.each{|x|
+ Encoding.find(n=h.fetch(n=x["name"].downcase,n))rescue next
+ x["labels"].each{|y|H[y]=n}
+ }
+ puts "{"
+ H.each{|k,v|puts %[ #{k.dump}=>#{v.dump},]}
+ puts "}"
+'
+=end
WEB_ENCODINGS_ = {
"unicode-1-1-utf-8"=>"utf-8",
"utf-8"=>"utf-8",
@@ -1111,6 +780,7 @@ module URI
"koi8"=>"koi8-r",
"koi8-r"=>"koi8-r",
"koi8_r"=>"koi8-r",
+ "koi8-ru"=>"koi8-u",
"koi8-u"=>"koi8-u",
"dos-874"=>"windows-874",
"iso-8859-11"=>"windows-874",
@@ -1191,6 +861,7 @@ module URI
"csiso2022jp"=>"cp50221",
"iso-2022-jp"=>"cp50221",
"csshiftjis"=>"Windows-31J",
+ "ms932"=>"Windows-31J",
"ms_kanji"=>"Windows-31J",
"shift-jis"=>"Windows-31J",
"shift_jis"=>"Windows-31J",
@@ -1209,8 +880,9 @@ module URI
"windows-949"=>"euc-kr",
"utf-16be"=>"utf-16be",
"utf-16"=>"utf-16le",
- "utf-16le"=>"utf-16le"
+ "utf-16le"=>"utf-16le",
} # :nodoc:
+ Ractor.make_shareable(WEB_ENCODINGS_) if defined?(Ractor)
# :nodoc:
# return encoding or nil
@@ -1223,7 +895,18 @@ end # module URI
module Kernel
#
- # Returns +uri+ converted to a URI object.
+ # Returns a \URI object derived from the given +uri+,
+ # which may be a \URI string or an existing \URI object:
+ #
+ # require 'uri'
+ # # Returns a new URI.
+ # uri = URI('http://github.com/ruby/ruby')
+ # # => #<URI::HTTP http://github.com/ruby/ruby>
+ # # Returns the given URI.
+ # URI(uri)
+ # # => #<URI::HTTP http://github.com/ruby/ruby>
+ #
+ # You must require 'uri' to use this method.
#
def URI(uri)
if uri.is_a?(URI::Generic)