Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package rubygem-addressable for 
openSUSE:Factory checked in at 2023-06-14 16:28:55
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/rubygem-addressable (Old)
 and      /work/SRC/openSUSE:Factory/.rubygem-addressable.new.15902 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "rubygem-addressable"

Wed Jun 14 16:28:55 2023 rev:23 rq:1092614 version:2.8.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/rubygem-addressable/rubygem-addressable.changes  
2022-08-28 13:12:10.864523052 +0200
+++ 
/work/SRC/openSUSE:Factory/.rubygem-addressable.new.15902/rubygem-addressable.changes
       2023-06-14 16:29:17.038447786 +0200
@@ -1,0 +2,16 @@
+Sat Jun 10 14:08:24 UTC 2023 - Andrea Manzini <[email protected]>
+
+- updated to version 2.8.4
+  * Restore Addressable::IDNA.unicode_normalize_kc as a deprecated method
+
+- updated to version 2.8.3
+  * Fix template expand level 2 hash support for non-string objects
+
+- updated to version 2.8.2
+  * Improve cache hits and JIT friendliness 
+  * Improve code style and test coverage 
+  * Ensure reset of deferred validation
+  * Resolve normalization differences between IDNA::Native and IDNA::Pure 
+  * Remove redundant colon in Addressable::URI::CharacterClasses::AUTHORITY
+
+-------------------------------------------------------------------

Old:
----
  addressable-2.8.1.gem

New:
----
  addressable-2.8.4.gem

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ rubygem-addressable.spec ++++++
--- /var/tmp/diff_new_pack.0mSdrJ/_old  2023-06-14 16:29:17.838452704 +0200
+++ /var/tmp/diff_new_pack.0mSdrJ/_new  2023-06-14 16:29:17.846452753 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package rubygem-addressable
 #
-# Copyright (c) 2022 SUSE LLC
+# Copyright (c) 2023 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -24,7 +24,7 @@
 #
 
 Name:           rubygem-addressable
-Version:        2.8.1
+Version:        2.8.4
 Release:        0
 %define mod_name addressable
 %define mod_full_name %{mod_name}-%{version}

++++++ addressable-2.8.1.gem -> addressable-2.8.4.gem ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/CHANGELOG.md new/CHANGELOG.md
--- old/CHANGELOG.md    2022-08-19 12:48:27.000000000 +0200
+++ new/CHANGELOG.md    2023-04-09 17:02:36.000000000 +0200
@@ -1,13 +1,33 @@
+# Addressable 2.8.4
+- Restore `Addressable::IDNA.unicode_normalize_kc` as a deprecated method 
([#504])
+
+[#504]: https://github.com/sporkmonger/addressable/pull/504
+
+# Addressable 2.8.3
+- Fix template expand level 2 hash support for non-string objects ([#499], 
[#498])
+
+[#499]: https://github.com/sporkmonger/addressable/pull/499
+[#498]: https://github.com/sporkmonger/addressable/pull/498
+
+# Addressable 2.8.2
+- Improve cache hits and JIT friendliness 
([#486](https://github.com/sporkmonger/addressable/pull/486))
+- Improve code style and test coverage 
([#482](https://github.com/sporkmonger/addressable/pull/482))
+- Ensure reset of deferred validation 
([#481](https://github.com/sporkmonger/addressable/pull/481))
+- Resolve normalization differences between `IDNA::Native` and `IDNA::Pure` 
([#408](https://github.com/sporkmonger/addressable/issues/408), [#492])
+- Remove redundant colon in `Addressable::URI::CharacterClasses::AUTHORITY` 
regex ([#438](https://github.com/sporkmonger/addressable/pull/438)) 
(accidentally reverted by [#449] merge but [added 
back](https://github.com/sporkmonger/addressable/pull/492#discussion_r1105125280)
 in [#492])
+
+[#492]: https://github.com/sporkmonger/addressable/pull/492
+
 # Addressable 2.8.1
 - refactor `Addressable::URI.normalize_path` to address linter offenses 
([#430](https://github.com/sporkmonger/addressable/pull/430))
-- remove redundant colon in `Addressable::URI::CharacterClasses::AUTHORITY` 
regex ([#438](https://github.com/sporkmonger/addressable/pull/438))
 - update gemspec to reflect supported Ruby versions ([#466], [#464], [#463])
 - compatibility w/ public_suffix 5.x ([#466], [#465], [#460])
 - fixes "invalid byte sequence in UTF-8" exception when unencoding URLs 
containing non UTF-8 characters 
([#459](https://github.com/sporkmonger/addressable/pull/459))
-- `Ractor` compatibility 
([#449](https://github.com/sporkmonger/addressable/pull/449))
+- `Ractor` compatibility ([#449])
 - use the whole string instead of a single line for template match 
([#431](https://github.com/sporkmonger/addressable/pull/431))
 - force UTF-8 encoding only if needed 
([#341](https://github.com/sporkmonger/addressable/pull/341))
 
+[#449]: https://github.com/sporkmonger/addressable/pull/449
 [#460]: https://github.com/sporkmonger/addressable/pull/460
 [#463]: https://github.com/sporkmonger/addressable/pull/463
 [#464]: https://github.com/sporkmonger/addressable/pull/464
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Rakefile new/Rakefile
--- old/Rakefile        2022-08-19 12:48:27.000000000 +0200
+++ new/Rakefile        2023-04-09 17:02:36.000000000 +0200
@@ -24,7 +24,8 @@
     "tasks/**/*",
     "[A-Z]*", "Rakefile"
 ].exclude(/pkg/).exclude(/database\.yml/).
-  exclude(/Gemfile\.lock/).exclude(/[_\.]git$/)
+  exclude(/Gemfile\.lock/).exclude(/[_\.]git$/).
+  exclude(/coverage/)
 
 task :default => "spec"
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/addressable.gemspec new/addressable.gemspec
--- old/addressable.gemspec     1970-01-01 01:00:00.000000000 +0100
+++ new/addressable.gemspec     2023-04-09 17:02:36.000000000 +0200
@@ -0,0 +1,28 @@
+# -*- encoding: utf-8 -*-
+# stub: addressable 2.8.4 ruby lib
+
+Gem::Specification.new do |s|
+  s.name = "addressable".freeze
+  s.version = "2.8.4"
+
+  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if 
s.respond_to? :required_rubygems_version=
+  s.metadata = { "changelog_uri" => 
"https://github.com/sporkmonger/addressable/blob/main/CHANGELOG.md"; } if 
s.respond_to? :metadata=
+  s.require_paths = ["lib".freeze]
+  s.authors = ["Bob Aman".freeze]
+  s.date = "2023-04-09"
+  s.description = "Addressable is an alternative implementation to the URI 
implementation that is\npart of Ruby's standard library. It is flexible, offers 
heuristic parsing, and\nadditionally provides extensive support for IRIs and 
URI templates.\n".freeze
+  s.email = "[email protected]".freeze
+  s.extra_rdoc_files = ["README.md".freeze]
+  s.files = ["CHANGELOG.md".freeze, "Gemfile".freeze, "LICENSE.txt".freeze, 
"README.md".freeze, "Rakefile".freeze, "addressable.gemspec".freeze, 
"data/unicode.data".freeze, "lib/addressable.rb".freeze, 
"lib/addressable/idna.rb".freeze, "lib/addressable/idna/native.rb".freeze, 
"lib/addressable/idna/pure.rb".freeze, "lib/addressable/template.rb".freeze, 
"lib/addressable/uri.rb".freeze, "lib/addressable/version.rb".freeze, 
"spec/addressable/idna_spec.rb".freeze, 
"spec/addressable/net_http_compat_spec.rb".freeze, 
"spec/addressable/security_spec.rb".freeze, 
"spec/addressable/template_spec.rb".freeze, 
"spec/addressable/uri_spec.rb".freeze, "spec/spec_helper.rb".freeze, 
"tasks/clobber.rake".freeze, "tasks/gem.rake".freeze, "tasks/git.rake".freeze, 
"tasks/metrics.rake".freeze, "tasks/profile.rake".freeze, 
"tasks/rspec.rake".freeze, "tasks/yard.rake".freeze]
+  s.homepage = "https://github.com/sporkmonger/addressable".freeze
+  s.licenses = ["Apache-2.0".freeze]
+  s.rdoc_options = ["--main".freeze, "README.md".freeze]
+  s.required_ruby_version = Gem::Requirement.new(">= 2.2".freeze)
+  s.rubygems_version = "3.4.10".freeze
+  s.summary = "URI Implementation".freeze
+
+  s.specification_version = 4
+
+  s.add_runtime_dependency(%q<public_suffix>.freeze, [">= 2.0.2", "< 6.0"])
+  s.add_development_dependency(%q<bundler>.freeze, [">= 1.0", "< 3.0"])
+end
Binary files old/checksums.yaml.gz and new/checksums.yaml.gz differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/addressable/idna/native.rb 
new/lib/addressable/idna/native.rb
--- old/lib/addressable/idna/native.rb  2022-08-19 12:48:27.000000000 +0200
+++ new/lib/addressable/idna/native.rb  2023-04-09 17:02:36.000000000 +0200
@@ -29,8 +29,14 @@
        IDN::Punycode.decode(value.to_s)
      end
 
-    def self.unicode_normalize_kc(value)
-      IDN::Stringprep.nfkc_normalize(value.to_s)
+    class << self
+      # @deprecated Use {String#unicode_normalize(:nfkc)} instead
+      def unicode_normalize_kc(value)
+        value.to_s.unicode_normalize(:nfkc)
+      end
+
+      extend Gem::Deprecate
+      deprecate :unicode_normalize_kc, "String#unicode_normalize(:nfkc)", 
2023, 4
     end
 
     def self.to_ascii(value)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/addressable/idna/pure.rb 
new/lib/addressable/idna/pure.rb
--- old/lib/addressable/idna/pure.rb    2022-08-19 12:48:27.000000000 +0200
+++ new/lib/addressable/idna/pure.rb    2023-04-09 17:02:36.000000000 +0200
@@ -66,7 +66,7 @@
     # domain name as described in RFC 3490.
     def self.to_ascii(input)
       input = input.to_s unless input.is_a?(String)
-      input = input.dup
+      input = 
input.dup.force_encoding(Encoding::UTF_8).unicode_normalize(:nfkc)
       if input.respond_to?(:force_encoding)
         input.force_encoding(Encoding::ASCII_8BIT)
       end
@@ -77,7 +77,7 @@
             part.force_encoding(Encoding::ASCII_8BIT)
           end
           if part =~ UTF8_REGEX && part =~ UTF8_REGEX_MULTIBYTE
-            ACE_PREFIX + punycode_encode(unicode_normalize_kc(part))
+            ACE_PREFIX + punycode_encode(part)
           else
             part
           end
@@ -112,13 +112,14 @@
       output
     end
 
-    # Unicode normalization form KC.
-    def self.unicode_normalize_kc(input)
-      input = input.to_s unless input.is_a?(String)
-      unpacked = input.unpack("U*")
-      unpacked =
-        unicode_compose(unicode_sort_canonical(unicode_decompose(unpacked)))
-      return unpacked.pack("U*")
+    class << self
+      # @deprecated Use {String#unicode_normalize(:nfkc)} instead
+      def unicode_normalize_kc(value)
+        value.to_s.unicode_normalize(:nfkc)
+      end
+
+      extend Gem::Deprecate
+      deprecate :unicode_normalize_kc, "String#unicode_normalize(:nfkc)", 
2023, 4
     end
 
     ##
@@ -136,164 +137,6 @@
     end
     private_class_method :unicode_downcase
 
-    def self.unicode_compose(unpacked)
-      unpacked_result = []
-      length = unpacked.length
-
-      return unpacked if length == 0
-
-      starter = unpacked[0]
-      starter_cc = lookup_unicode_combining_class(starter)
-      starter_cc = 256 if starter_cc != 0
-      for i in 1...length
-        ch = unpacked[i]
-
-        if (starter_cc == 0 &&
-            (composite = unicode_compose_pair(starter, ch)) != nil)
-          starter = composite
-        else
-          unpacked_result << starter
-          starter = ch
-        end
-      end
-      unpacked_result << starter
-      return unpacked_result
-    end
-    private_class_method :unicode_compose
-
-    def self.unicode_compose_pair(ch_one, ch_two)
-      if ch_one >= HANGUL_LBASE && ch_one < HANGUL_LBASE + HANGUL_LCOUNT &&
-          ch_two >= HANGUL_VBASE && ch_two < HANGUL_VBASE + HANGUL_VCOUNT
-        # Hangul L + V
-        return HANGUL_SBASE + (
-          (ch_one - HANGUL_LBASE) * HANGUL_VCOUNT + (ch_two - HANGUL_VBASE)
-        ) * HANGUL_TCOUNT
-      elsif ch_one >= HANGUL_SBASE &&
-          ch_one < HANGUL_SBASE + HANGUL_SCOUNT &&
-          (ch_one - HANGUL_SBASE) % HANGUL_TCOUNT == 0 &&
-          ch_two >= HANGUL_TBASE && ch_two < HANGUL_TBASE + HANGUL_TCOUNT
-           # Hangul LV + T
-        return ch_one + (ch_two - HANGUL_TBASE)
-      end
-
-      p = []
-
-      ucs4_to_utf8(ch_one, p)
-      ucs4_to_utf8(ch_two, p)
-
-      return lookup_unicode_composition(p)
-    end
-    private_class_method :unicode_compose_pair
-
-    def self.ucs4_to_utf8(char, buffer)
-      if char < 128
-        buffer << char
-      elsif char < 2048
-        buffer << (char >> 6 | 192)
-        buffer << (char & 63 | 128)
-      elsif char < 0x10000
-        buffer << (char >> 12 | 224)
-        buffer << (char >> 6 & 63 | 128)
-        buffer << (char & 63 | 128)
-      elsif char < 0x200000
-        buffer << (char >> 18 | 240)
-        buffer << (char >> 12 & 63 | 128)
-        buffer << (char >> 6 & 63 | 128)
-        buffer << (char & 63 | 128)
-      elsif char < 0x4000000
-        buffer << (char >> 24 | 248)
-        buffer << (char >> 18 & 63 | 128)
-        buffer << (char >> 12 & 63 | 128)
-        buffer << (char >> 6 & 63 | 128)
-        buffer << (char & 63 | 128)
-      elsif char < 0x80000000
-        buffer << (char >> 30 | 252)
-        buffer << (char >> 24 & 63 | 128)
-        buffer << (char >> 18 & 63 | 128)
-        buffer << (char >> 12 & 63 | 128)
-        buffer << (char >> 6 & 63 | 128)
-        buffer << (char & 63 | 128)
-      end
-    end
-    private_class_method :ucs4_to_utf8
-
-    def self.unicode_sort_canonical(unpacked)
-      unpacked = unpacked.dup
-      i = 1
-      length = unpacked.length
-
-      return unpacked if length < 2
-
-      while i < length
-        last = unpacked[i-1]
-        ch = unpacked[i]
-        last_cc = lookup_unicode_combining_class(last)
-        cc = lookup_unicode_combining_class(ch)
-        if cc != 0 && last_cc != 0 && last_cc > cc
-          unpacked[i] = last
-          unpacked[i-1] = ch
-          i -= 1 if i > 1
-        else
-          i += 1
-        end
-      end
-      return unpacked
-    end
-    private_class_method :unicode_sort_canonical
-
-    def self.unicode_decompose(unpacked)
-      unpacked_result = []
-      for cp in unpacked
-        if cp >= HANGUL_SBASE && cp < HANGUL_SBASE + HANGUL_SCOUNT
-          l, v, t = unicode_decompose_hangul(cp)
-          unpacked_result << l
-          unpacked_result << v if v
-          unpacked_result << t if t
-        else
-          dc = lookup_unicode_compatibility(cp)
-          unless dc
-            unpacked_result << cp
-          else
-            unpacked_result.concat(unicode_decompose(dc.unpack("U*")))
-          end
-        end
-      end
-      return unpacked_result
-    end
-    private_class_method :unicode_decompose
-
-    def self.unicode_decompose_hangul(codepoint)
-      sindex = codepoint - HANGUL_SBASE;
-      if sindex < 0 || sindex >= HANGUL_SCOUNT
-        l = codepoint
-        v = t = nil
-        return l, v, t
-      end
-      l = HANGUL_LBASE + sindex / HANGUL_NCOUNT
-      v = HANGUL_VBASE + (sindex % HANGUL_NCOUNT) / HANGUL_TCOUNT
-      t = HANGUL_TBASE + sindex % HANGUL_TCOUNT
-      if t == HANGUL_TBASE
-        t = nil
-      end
-      return l, v, t
-    end
-    private_class_method :unicode_decompose_hangul
-
-    def self.lookup_unicode_combining_class(codepoint)
-      codepoint_data = UNICODE_DATA[codepoint]
-      (codepoint_data ?
-        (codepoint_data[UNICODE_DATA_COMBINING_CLASS] || 0) :
-        0)
-    end
-    private_class_method :lookup_unicode_combining_class
-
-    def self.lookup_unicode_compatibility(codepoint)
-      codepoint_data = UNICODE_DATA[codepoint]
-      (codepoint_data ?
-        codepoint_data[UNICODE_DATA_COMPATIBILITY] : nil)
-    end
-    private_class_method :lookup_unicode_compatibility
-
     def self.lookup_unicode_lowercase(codepoint)
       codepoint_data = UNICODE_DATA[codepoint]
       (codepoint_data ?
@@ -302,21 +145,6 @@
     end
     private_class_method :lookup_unicode_lowercase
 
-    def self.lookup_unicode_composition(unpacked)
-      return COMPOSITION_TABLE[unpacked]
-    end
-    private_class_method :lookup_unicode_composition
-
-    HANGUL_SBASE =  0xac00
-    HANGUL_LBASE =  0x1100
-    HANGUL_LCOUNT = 19
-    HANGUL_VBASE =  0x1161
-    HANGUL_VCOUNT = 21
-    HANGUL_TBASE =  0x11a7
-    HANGUL_TCOUNT = 28
-    HANGUL_NCOUNT = HANGUL_VCOUNT * HANGUL_TCOUNT # 588
-    HANGUL_SCOUNT = HANGUL_LCOUNT * HANGUL_NCOUNT # 11172
-
     UNICODE_DATA_COMBINING_CLASS = 0
     UNICODE_DATA_EXCLUSION = 1
     UNICODE_DATA_CANONICAL = 2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/addressable/template.rb 
new/lib/addressable/template.rb
--- old/lib/addressable/template.rb     2022-08-19 12:48:27.000000000 +0200
+++ new/lib/addressable/template.rb     2023-04-09 17:02:36.000000000 +0200
@@ -892,25 +892,24 @@
     # operator.
     #
     # @param [Hash, Array, String] value
-    #   Normalizes keys and values with IDNA#unicode_normalize_kc
+    #   Normalizes unicode keys and values with String#unicode_normalize (NFC)
     #
     # @return [Hash, Array, String] The normalized values
     def normalize_value(value)
-      unless value.is_a?(Hash)
-        value = value.respond_to?(:to_ary) ? value.to_ary : value.to_str
-      end
-
       # Handle unicode normalization
-      if value.kind_of?(Array)
-        value.map! { |val| Addressable::IDNA.unicode_normalize_kc(val) }
+      if value.respond_to?(:to_ary)
+        value.to_ary.map! { |val| normalize_value(val) }
       elsif value.kind_of?(Hash)
         value = value.inject({}) { |acc, (k, v)|
-          acc[Addressable::IDNA.unicode_normalize_kc(k)] =
-            Addressable::IDNA.unicode_normalize_kc(v)
+          acc[normalize_value(k)] = normalize_value(v)
           acc
         }
       else
-        value = Addressable::IDNA.unicode_normalize_kc(value)
+        value = value.to_s if !value.kind_of?(String)
+        if value.encoding != Encoding::UTF_8
+          value = value.dup.force_encoding(Encoding::UTF_8)
+        end
+        value = value.unicode_normalize(:nfc)
       end
       value
     end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/addressable/uri.rb new/lib/addressable/uri.rb
--- old/lib/addressable/uri.rb  2022-08-19 12:48:27.000000000 +0200
+++ new/lib/addressable/uri.rb  2023-04-09 17:02:36.000000000 +0200
@@ -53,7 +53,7 @@
       PCHAR = (UNRESERVED + SUB_DELIMS + "\\:\\@").freeze
       SCHEME = (ALPHA + DIGIT + "\\-\\+\\.").freeze
       HOST = (UNRESERVED + SUB_DELIMS + "\\[\\:\\]").freeze
-      AUTHORITY = (PCHAR + "\\[\\:\\]").freeze
+      AUTHORITY = (PCHAR + "\\[\\]").freeze
       PATH = (PCHAR + "\\/").freeze
       QUERY = (PCHAR + "\\/\\?").freeze
       FRAGMENT = (PCHAR + "\\/\\?").freeze
@@ -117,7 +117,7 @@
         uri = uri.to_str
       rescue TypeError, NoMethodError
         raise TypeError, "Can't convert #{uri.class} into String."
-      end if not uri.is_a? String
+      end unless uri.is_a?(String)
 
       # This Regexp supplied as an example in RFC 3986, and it works great.
       scan = uri.scan(URIREGEX)
@@ -138,15 +138,15 @@
           user = userinfo.strip[/^([^:]*):?/, 1]
           password = userinfo.strip[/:(.*)$/, 1]
         end
+
         host = authority.sub(
           /^([^\[\]]*)@/, EMPTY_STR
         ).sub(
           /:([^:@\[\]]*?)$/, EMPTY_STR
         )
+
         port = authority[/:([^:@\[\]]*?)$/, 1]
-      end
-      if port == EMPTY_STR
-        port = nil
+        port = nil if port == EMPTY_STR
       end
 
       return new(
@@ -189,7 +189,7 @@
         uri = uri.to_s
       end
 
-      if !uri.respond_to?(:to_str)
+      unless uri.respond_to?(:to_str)
         raise TypeError, "Can't convert #{uri.class} into String."
       end
       # Otherwise, convert to a String
@@ -281,7 +281,7 @@
       return nil unless path
       # If a URI object is passed, just return itself.
       return path if path.kind_of?(self)
-      if !path.respond_to?(:to_str)
+      unless path.respond_to?(:to_str)
         raise TypeError, "Can't convert #{path.class} into String."
       end
       # Otherwise, convert to a String
@@ -329,13 +329,13 @@
     #   #=> #<Addressable::URI:0xcab390 URI:http://example.com/relative/path>
     def self.join(*uris)
       uri_objects = uris.collect do |uri|
-        if !uri.respond_to?(:to_str)
+        unless uri.respond_to?(:to_str)
           raise TypeError, "Can't convert #{uri.class} into String."
         end
         uri.kind_of?(self) ? uri : self.parse(uri.to_str)
       end
       result = uri_objects.shift.dup
-      for uri in uri_objects
+      uri_objects.each do |uri|
         result.join!(uri)
       end
       return result
@@ -481,7 +481,7 @@
         leave_encoded.include?(c) ? sequence : c
       end
 
-      result.force_encoding("utf-8")
+      result.force_encoding(Encoding::UTF_8)
       if return_type == String
         return result
       elsif return_type == ::Addressable::URI
@@ -579,7 +579,7 @@
       unencoded = self.unencode_component(component, String, leave_encoded)
       begin
         encoded = self.encode_component(
-          Addressable::IDNA.unicode_normalize_kc(unencoded),
+          unencoded.unicode_normalize(:nfc),
           character_class,
           leave_encoded
         )
@@ -687,8 +687,7 @@
       components.each do |key, value|
         if value != nil
           begin
-            components[key] =
-              Addressable::IDNA.unicode_normalize_kc(value.to_str)
+            components[key] = value.to_str.unicode_normalize(:nfc)
           rescue ArgumentError
             # Likely a malformed UTF-8 character, skip unicode normalization
             components[key] = value.to_str
@@ -836,7 +835,9 @@
         end
       end
 
-      self.defer_validation do
+      reset_ivs
+
+      defer_validation do
         # Bunch of crazy logic required because of the composite components
         # like userinfo and authority.
         self.scheme = options[:scheme] if options[:scheme]
@@ -851,7 +852,8 @@
         self.query_values = options[:query_values] if options[:query_values]
         self.fragment = options[:fragment] if options[:fragment]
       end
-      self.to_s
+
+      to_s # force path validation
     end
 
     ##
@@ -878,9 +880,7 @@
     # The scheme component for this URI.
     #
     # @return [String] The scheme component.
-    def scheme
-      return defined?(@scheme) ? @scheme : nil
-    end
+    attr_reader :scheme
 
     ##
     # The scheme component for this URI, normalized.
@@ -888,8 +888,8 @@
     # @return [String] The scheme component, normalized.
     def normalized_scheme
       return nil unless self.scheme
-      @normalized_scheme ||= begin
-        if self.scheme =~ /^\s*ssh\+svn\s*$/i
+      if @normalized_scheme == NONE
+        @normalized_scheme = if self.scheme =~ /^\s*ssh\+svn\s*$/i
           "svn+ssh".dup
         else
           Addressable::URI.normalize_component(
@@ -920,7 +920,7 @@
       @scheme = nil if @scheme.to_s.strip.empty?
 
       # Reset dependent values
-      remove_instance_variable(:@normalized_scheme) if 
defined?(@normalized_scheme)
+      @normalized_scheme = NONE
       remove_composite_values
 
       # Ensure we haven't created an invalid URI
@@ -931,9 +931,7 @@
     # The user component for this URI.
     #
     # @return [String] The user component.
-    def user
-      return defined?(@user) ? @user : nil
-    end
+    attr_reader :user
 
     ##
     # The user component for this URI, normalized.
@@ -941,8 +939,8 @@
     # @return [String] The user component, normalized.
     def normalized_user
       return nil unless self.user
-      return @normalized_user if defined?(@normalized_user)
-      @normalized_user ||= begin
+      return @normalized_user unless @normalized_user == NONE
+      @normalized_user = begin
         if normalized_scheme =~ /https?/ && self.user.strip.empty? &&
             (!self.password || self.password.strip.empty?)
           nil
@@ -970,14 +968,14 @@
 
       # You can't have a nil user with a non-nil password
       if password != nil
-        @user = EMPTY_STR if @user.nil?
+        @user = EMPTY_STR unless user
       end
 
       # Reset dependent values
-      remove_instance_variable(:@userinfo) if defined?(@userinfo)
-      remove_instance_variable(:@normalized_userinfo) if 
defined?(@normalized_userinfo)
-      remove_instance_variable(:@authority) if defined?(@authority)
-      remove_instance_variable(:@normalized_user) if defined?(@normalized_user)
+      @userinfo = nil
+      @normalized_userinfo = NONE
+      @authority = nil
+      @normalized_user = NONE
       remove_composite_values
 
       # Ensure we haven't created an invalid URI
@@ -988,9 +986,7 @@
     # The password component for this URI.
     #
     # @return [String] The password component.
-    def password
-      return defined?(@password) ? @password : nil
-    end
+    attr_reader :password
 
     ##
     # The password component for this URI, normalized.
@@ -998,8 +994,8 @@
     # @return [String] The password component, normalized.
     def normalized_password
       return nil unless self.password
-      return @normalized_password if defined?(@normalized_password)
-      @normalized_password ||= begin
+      return @normalized_password unless @normalized_password == NONE
+      @normalized_password = begin
         if self.normalized_scheme =~ /https?/ && self.password.strip.empty? &&
             (!self.user || self.user.strip.empty?)
           nil
@@ -1026,17 +1022,15 @@
       @password = new_password ? new_password.to_str : nil
 
       # You can't have a nil user with a non-nil password
-      @password ||= nil
-      @user ||= nil
       if @password != nil
-        @user = EMPTY_STR if @user.nil?
+        self.user = EMPTY_STR if user.nil?
       end
 
       # Reset dependent values
-      remove_instance_variable(:@userinfo) if defined?(@userinfo)
-      remove_instance_variable(:@normalized_userinfo) if 
defined?(@normalized_userinfo)
-      remove_instance_variable(:@authority) if defined?(@authority)
-      remove_instance_variable(:@normalized_password) if 
defined?(@normalized_password)
+      @userinfo = nil
+      @normalized_userinfo = NONE
+      @authority = nil
+      @normalized_password = NONE
       remove_composite_values
 
       # Ensure we haven't created an invalid URI
@@ -1066,8 +1060,8 @@
     # @return [String] The userinfo component, normalized.
     def normalized_userinfo
       return nil unless self.userinfo
-      return @normalized_userinfo if defined?(@normalized_userinfo)
-      @normalized_userinfo ||= begin
+      return @normalized_userinfo unless @normalized_userinfo == NONE
+      @normalized_userinfo = begin
         current_user = self.normalized_user
         current_password = self.normalized_password
         if !current_user && !current_password
@@ -1105,7 +1099,7 @@
       self.user = new_user
 
       # Reset dependent values
-      remove_instance_variable(:@authority) if defined?(@authority)
+      @authority = nil
       remove_composite_values
 
       # Ensure we haven't created an invalid URI
@@ -1116,9 +1110,7 @@
     # The host component for this URI.
     #
     # @return [String] The host component.
-    def host
-      return defined?(@host) ? @host : nil
-    end
+    attr_reader :host
 
     ##
     # The host component for this URI, normalized.
@@ -1161,8 +1153,8 @@
       @host = new_host ? new_host.to_str : nil
 
       # Reset dependent values
-      remove_instance_variable(:@authority) if defined?(@authority)
-      remove_instance_variable(:@normalized_host) if defined?(@normalized_host)
+      @authority = nil
+      @normalized_host = nil
       remove_composite_values
 
       # Ensure we haven't created an invalid URI
@@ -1293,14 +1285,14 @@
       end
 
       # Password assigned first to ensure validity in case of nil
-      self.password = defined?(new_password) ? new_password : nil
-      self.user = defined?(new_user) ? new_user : nil
-      self.host = defined?(new_host) ? new_host : nil
-      self.port = defined?(new_port) ? new_port : nil
+      self.password = new_password
+      self.user = new_user
+      self.host = new_host
+      self.port = new_port
 
       # Reset dependent values
-      remove_instance_variable(:@userinfo) if defined?(@userinfo)
-      remove_instance_variable(:@normalized_userinfo) if 
defined?(@normalized_userinfo)
+      @userinfo = nil
+      @normalized_userinfo = NONE
       remove_composite_values
 
       # Ensure we haven't created an invalid URI
@@ -1348,16 +1340,16 @@
         new_port = new_origin[/:([^:@\[\]\/]*?)$/, 1]
       end
 
-      self.scheme = defined?(new_scheme) ? new_scheme : nil
-      self.host = defined?(new_host) ? new_host : nil
-      self.port = defined?(new_port) ? new_port : nil
+      self.scheme = new_scheme
+      self.host = new_host
+      self.port = new_port
       self.userinfo = nil
 
       # Reset dependent values
-      remove_instance_variable(:@userinfo) if defined?(@userinfo)
-      remove_instance_variable(:@normalized_userinfo) if 
defined?(@normalized_userinfo)
-      remove_instance_variable(:@authority) if defined?(@authority)
-      remove_instance_variable(:@normalized_authority) if 
defined?(@normalized_authority)
+      @userinfo = nil
+      @normalized_userinfo = NONE
+      @authority = nil
+      @normalized_authority = nil
       remove_composite_values
 
       # Ensure we haven't created an invalid URI
@@ -1384,9 +1376,7 @@
     # infer port numbers from default values.
     #
     # @return [Integer] The port component.
-    def port
-      return defined?(@port) ? @port : nil
-    end
+    attr_reader :port
 
     ##
     # The port component for this URI, normalized.
@@ -1394,8 +1384,8 @@
     # @return [Integer] The port component, normalized.
     def normalized_port
       return nil unless self.port
-      return @normalized_port if defined?(@normalized_port)
-      @normalized_port ||= begin
+      return @normalized_port unless @normalized_port == NONE
+      @normalized_port = begin
         if URI.port_mapping[self.normalized_scheme] == self.port
           nil
         else
@@ -1426,8 +1416,8 @@
       @port = nil if @port == 0
 
       # Reset dependent values
-      remove_instance_variable(:@authority) if defined?(@authority)
-      remove_instance_variable(:@normalized_port) if defined?(@normalized_port)
+      @authority = nil
+      @normalized_port = NONE
       remove_composite_values
 
       # Ensure we haven't created an invalid URI
@@ -1528,9 +1518,7 @@
     # The path component for this URI.
     #
     # @return [String] The path component.
-    def path
-      return defined?(@path) ? @path : EMPTY_STR
-    end
+    attr_reader :path
 
     NORMPATH = /^(?!\/)[^\/:]*:.*$/
     ##
@@ -1579,7 +1567,7 @@
       end
 
       # Reset dependent values
-      remove_instance_variable(:@normalized_path) if defined?(@normalized_path)
+      @normalized_path = nil
       remove_composite_values
 
       # Ensure we haven't created an invalid URI
@@ -1609,9 +1597,7 @@
     # The query component for this URI.
     #
     # @return [String] The query component.
-    def query
-      return defined?(@query) ? @query : nil
-    end
+    attr_reader :query
 
     ##
     # The query component for this URI, normalized.
@@ -1619,8 +1605,8 @@
     # @return [String] The query component, normalized.
     def normalized_query(*flags)
       return nil unless self.query
-      return @normalized_query if defined?(@normalized_query)
-      @normalized_query ||= begin
+      return @normalized_query unless @normalized_query == NONE
+      @normalized_query = begin
         modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup
         # Make sure possible key-value pair delimiters are escaped.
         modified_query_class.sub!("\\&", "").sub!("\\;", "")
@@ -1652,7 +1638,7 @@
       @query = new_query ? new_query.to_str : nil
 
       # Reset dependent values
-      remove_instance_variable(:@normalized_query) if 
defined?(@normalized_query)
+      @normalized_query = NONE
       remove_composite_values
     end
 
@@ -1814,9 +1800,7 @@
     # The fragment component for this URI.
     #
     # @return [String] The fragment component.
-    def fragment
-      return defined?(@fragment) ? @fragment : nil
-    end
+    attr_reader :fragment
 
     ##
     # The fragment component for this URI, normalized.
@@ -1824,8 +1808,8 @@
     # @return [String] The fragment component, normalized.
     def normalized_fragment
       return nil unless self.fragment
-      return @normalized_fragment if defined?(@normalized_fragment)
-      @normalized_fragment ||= begin
+      return @normalized_fragment unless @normalized_fragment == NONE
+      @normalized_fragment = begin
         component = Addressable::URI.normalize_component(
           self.fragment,
           Addressable::URI::NormalizeCharacterClasses::FRAGMENT
@@ -1848,7 +1832,7 @@
       @fragment = new_fragment ? new_fragment.to_str : nil
 
       # Reset dependent values
-      remove_instance_variable(:@normalized_fragment) if 
defined?(@normalized_fragment)
+      @normalized_fragment = NONE
       remove_composite_values
 
       # Ensure we haven't created an invalid URI
@@ -2014,7 +1998,7 @@
     #
     # @see Hash#merge
     def merge(hash)
-      if !hash.respond_to?(:to_hash)
+      unless hash.respond_to?(:to_hash)
         raise TypeError, "Can't convert #{hash.class} into Hash."
       end
       hash = hash.to_hash
@@ -2408,7 +2392,8 @@
       yield
       @validation_deferred = false
       validate
-      return nil
+    ensure
+      @validation_deferred = false
     end
 
   protected
@@ -2507,11 +2492,7 @@
     # @return [Addressable::URI] <code>self</code>.
     def replace_self(uri)
       # Reset dependent values
-      instance_variables.each do |var|
-        if instance_variable_defined?(var) && var != :@validation_deferred
-          remove_instance_variable(var)
-        end
-      end
+      reset_ivs
 
       @scheme = uri.scheme
       @user = uri.user
@@ -2543,8 +2524,8 @@
     #
     # @api private
     def remove_composite_values
-      remove_instance_variable(:@uri_string) if defined?(@uri_string)
-      remove_instance_variable(:@hash) if defined?(@hash)
+      @uri_string = nil
+      @hash = nil
     end
 
     ##
@@ -2556,5 +2537,40 @@
         str.force_encoding(Encoding::UTF_8)
       end
     end
+
+    private
+
+    ##
+    # Resets instance variables
+    #
+    # @api private
+    def reset_ivs
+      @scheme = nil
+      @user = nil
+      @normalized_scheme = NONE
+      @normalized_user = NONE
+      @uri_string = nil
+      @hash = nil
+      @userinfo = nil
+      @normalized_userinfo = NONE
+      @authority = nil
+      @password = nil
+      @normalized_authority = nil
+      @port = nil
+      @normalized_password = NONE
+      @host = nil
+      @normalized_host = nil
+      @normalized_port = NONE
+      @path = EMPTY_STR
+      @normalized_path = nil
+      @normalized_query = NONE
+      @fragment = nil
+      @normalized_fragment = NONE
+      @query = nil
+    end
+
+    NONE = Object.new.freeze
+
+    private_constant :NONE
   end
 end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/addressable/version.rb 
new/lib/addressable/version.rb
--- old/lib/addressable/version.rb      2022-08-19 12:48:27.000000000 +0200
+++ new/lib/addressable/version.rb      2023-04-09 17:02:36.000000000 +0200
@@ -23,7 +23,7 @@
     module VERSION
       MAJOR = 2
       MINOR = 8
-      TINY  = 1
+      TINY  = 4
 
       STRING = [MAJOR, MINOR, TINY].join('.')
     end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/metadata new/metadata
--- old/metadata        2022-08-19 12:48:27.000000000 +0200
+++ new/metadata        2023-04-09 17:02:36.000000000 +0200
@@ -1,14 +1,14 @@
 --- !ruby/object:Gem::Specification
 name: addressable
 version: !ruby/object:Gem::Version
-  version: 2.8.1
+  version: 2.8.4
 platform: ruby
 authors:
 - Bob Aman
 autorequire:
 bindir: bin
 cert_chain: []
-date: 2022-08-19 00:00:00.000000000 Z
+date: 2023-04-09 00:00:00.000000000 Z
 dependencies:
 - !ruby/object:Gem::Dependency
   name: public_suffix
@@ -65,6 +65,7 @@
 - LICENSE.txt
 - README.md
 - Rakefile
+- addressable.gemspec
 - data/unicode.data
 - lib/addressable.rb
 - lib/addressable/idna.rb
@@ -108,7 +109,7 @@
     - !ruby/object:Gem::Version
       version: '0'
 requirements: []
-rubygems_version: 3.3.7
+rubygems_version: 3.4.10
 signing_key:
 specification_version: 4
 summary: URI Implementation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/spec/addressable/idna_spec.rb 
new/spec/addressable/idna_spec.rb
--- old/spec/addressable/idna_spec.rb   2022-08-19 12:48:27.000000000 +0200
+++ new/spec/addressable/idna_spec.rb   2023-04-09 17:02:36.000000000 +0200
@@ -38,6 +38,12 @@
     )).to eq("www.xn--8ws00zhy3a.com")
   end
 
+  it "also accepts unicode strings encoded as ascii-8bit" do
+    expect(Addressable::IDNA.to_ascii(
+      "www.詹姆斯.com".b
+    )).to eq("www.xn--8ws00zhy3a.com")
+  end
+
   it "should convert 'www.Iñtërnâtiônàlizætiøn.com' correctly" do
     "www.Iñtërnâtiônàlizætiøn.com"
     expect(Addressable::IDNA.to_ascii(
@@ -249,11 +255,6 @@
       "example..host"
     )).to eq("example..host")
   end
-
-  it "should normalize 'string' correctly" do
-    expect(Addressable::IDNA.unicode_normalize_kc(:'string')).to eq("string")
-    expect(Addressable::IDNA.unicode_normalize_kc("string")).to eq("string")
-  end
 end
 
 describe Addressable::IDNA, "when using the pure-Ruby implementation" do
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/spec/addressable/template_spec.rb 
new/spec/addressable/template_spec.rb
--- old/spec/addressable/template_spec.rb       2022-08-19 12:48:27.000000000 
+0200
+++ new/spec/addressable/template_spec.rb       2023-04-09 17:02:36.000000000 
+0200
@@ -26,11 +26,7 @@
     exp = expansion.is_a?(Array) ? expansion.first : expansion
     it "#{template} to #{exp}" do
       tmpl = Addressable::Template.new(template).expand(subject)
-      if expansion.is_a?(Array)
-        expect(expansion.any?{|i| i == tmpl.to_str}).to be true
-      else
-        expect(tmpl.to_str).to eq(expansion)
-      end
+      expect(tmpl.to_str).to eq(expansion)
     end
   end
 end
@@ -209,7 +205,7 @@
       :path => "/foo/bar",
       :semi => ";",
       :list => %w(red green blue),
-      :keys => {"semi" => ';', "dot" => '.', "comma" => ','}
+      :keys => {"semi" => ';', "dot" => '.', :comma => ','}
     }
   }
   context "Expansion with value modifiers" do
@@ -218,22 +214,8 @@
       '{var:30}' => 'value',
       '{list}' => 'red,green,blue',
       '{list*}' => 'red,green,blue',
-      '{keys}' => [
-        'semi,%3B,dot,.,comma,%2C',
-        'dot,.,semi,%3B,comma,%2C',
-        'comma,%2C,semi,%3B,dot,.',
-        'semi,%3B,comma,%2C,dot,.',
-        'dot,.,comma,%2C,semi,%3B',
-        'comma,%2C,dot,.,semi,%3B'
-      ],
-      '{keys*}' => [
-        'semi=%3B,dot=.,comma=%2C',
-        'dot=.,semi=%3B,comma=%2C',
-        'comma=%2C,semi=%3B,dot=.',
-        'semi=%3B,comma=%2C,dot=.',
-        'dot=.,comma=%2C,semi=%3B',
-        'comma=%2C,dot=.,semi=%3B'
-      ]
+      '{keys}' => 'semi,%3B,dot,.,comma,%2C',
+      '{keys*}' => 'semi=%3B,dot=.,comma=%2C',
     }
   end
   context "Operator + with value modifiers" do
@@ -241,22 +223,8 @@
       '{+path:6}/here' => '/foo/b/here',
       '{+list}' => 'red,green,blue',
       '{+list*}' => 'red,green,blue',
-      '{+keys}' => [
-        'semi,;,dot,.,comma,,',
-        'dot,.,semi,;,comma,,',
-        'comma,,,semi,;,dot,.',
-        'semi,;,comma,,,dot,.',
-        'dot,.,comma,,,semi,;',
-        'comma,,,dot,.,semi,;'
-      ],
-      '{+keys*}' => [
-        'semi=;,dot=.,comma=,',
-        'dot=.,semi=;,comma=,',
-        'comma=,,semi=;,dot=.',
-        'semi=;,comma=,,dot=.',
-        'dot=.,comma=,,semi=;',
-        'comma=,,dot=.,semi=;'
-      ]
+      '{+keys}' => 'semi,;,dot,.,comma,,',
+      '{+keys*}' => 'semi=;,dot=.,comma=,',
     }
   end
   context "Operator # with value modifiers" do
@@ -264,22 +232,8 @@
       '{#path:6}/here' => '#/foo/b/here',
       '{#list}' => '#red,green,blue',
       '{#list*}' => '#red,green,blue',
-      '{#keys}' => [
-        '#semi,;,dot,.,comma,,',
-        '#dot,.,semi,;,comma,,',
-        '#comma,,,semi,;,dot,.',
-        '#semi,;,comma,,,dot,.',
-        '#dot,.,comma,,,semi,;',
-        '#comma,,,dot,.,semi,;'
-      ],
-      '{#keys*}' => [
-        '#semi=;,dot=.,comma=,',
-        '#dot=.,semi=;,comma=,',
-        '#comma=,,semi=;,dot=.',
-        '#semi=;,comma=,,dot=.',
-        '#dot=.,comma=,,semi=;',
-        '#comma=,,dot=.,semi=;'
-      ]
+      '{#keys}' => '#semi,;,dot,.,comma,,',
+      '{#keys*}' => '#semi=;,dot=.,comma=,',
     }
   end
   context "Operator . with value modifiers" do
@@ -287,22 +241,8 @@
       'X{.var:3}' => 'X.val',
       'X{.list}' => 'X.red,green,blue',
       'X{.list*}' => 'X.red.green.blue',
-      'X{.keys}' => [
-        'X.semi,%3B,dot,.,comma,%2C',
-        'X.dot,.,semi,%3B,comma,%2C',
-        'X.comma,%2C,semi,%3B,dot,.',
-        'X.semi,%3B,comma,%2C,dot,.',
-        'X.dot,.,comma,%2C,semi,%3B',
-        'X.comma,%2C,dot,.,semi,%3B'
-      ],
-      'X{.keys*}' => [
-        'X.semi=%3B.dot=..comma=%2C',
-        'X.dot=..semi=%3B.comma=%2C',
-        'X.comma=%2C.semi=%3B.dot=.',
-        'X.semi=%3B.comma=%2C.dot=.',
-        'X.dot=..comma=%2C.semi=%3B',
-        'X.comma=%2C.dot=..semi=%3B'
-      ]
+      'X{.keys}' => 'X.semi,%3B,dot,.,comma,%2C',
+      'X{.keys*}' => 'X.semi=%3B.dot=..comma=%2C',
     }
   end
   context "Operator / with value modifiers" do
@@ -311,22 +251,8 @@
       '{/list}' => '/red,green,blue',
       '{/list*}' => '/red/green/blue',
       '{/list*,path:4}' => '/red/green/blue/%2Ffoo',
-      '{/keys}' => [
-        '/semi,%3B,dot,.,comma,%2C',
-        '/dot,.,semi,%3B,comma,%2C',
-        '/comma,%2C,semi,%3B,dot,.',
-        '/semi,%3B,comma,%2C,dot,.',
-        '/dot,.,comma,%2C,semi,%3B',
-        '/comma,%2C,dot,.,semi,%3B'
-      ],
-      '{/keys*}' => [
-        '/semi=%3B/dot=./comma=%2C',
-        '/dot=./semi=%3B/comma=%2C',
-        '/comma=%2C/semi=%3B/dot=.',
-        '/semi=%3B/comma=%2C/dot=.',
-        '/dot=./comma=%2C/semi=%3B',
-        '/comma=%2C/dot=./semi=%3B'
-      ]
+      '{/keys}' => '/semi,%3B,dot,.,comma,%2C',
+      '{/keys*}' => '/semi=%3B/dot=./comma=%2C',
     }
   end
   context "Operator ; with value modifiers" do
@@ -334,22 +260,8 @@
       '{;hello:5}' => ';hello=Hello',
       '{;list}' => ';list=red,green,blue',
       '{;list*}' => ';list=red;list=green;list=blue',
-      '{;keys}' => [
-        ';keys=semi,%3B,dot,.,comma,%2C',
-        ';keys=dot,.,semi,%3B,comma,%2C',
-        ';keys=comma,%2C,semi,%3B,dot,.',
-        ';keys=semi,%3B,comma,%2C,dot,.',
-        ';keys=dot,.,comma,%2C,semi,%3B',
-        ';keys=comma,%2C,dot,.,semi,%3B'
-      ],
-      '{;keys*}' => [
-        ';semi=%3B;dot=.;comma=%2C',
-        ';dot=.;semi=%3B;comma=%2C',
-        ';comma=%2C;semi=%3B;dot=.',
-        ';semi=%3B;comma=%2C;dot=.',
-        ';dot=.;comma=%2C;semi=%3B',
-        ';comma=%2C;dot=.;semi=%3B'
-      ]
+      '{;keys}' => ';keys=semi,%3B,dot,.,comma,%2C',
+      '{;keys*}' => ';semi=%3B;dot=.;comma=%2C',
     }
   end
   context "Operator ? with value modifiers" do
@@ -357,22 +269,8 @@
       '{?var:3}' => '?var=val',
       '{?list}' => '?list=red,green,blue',
       '{?list*}' => '?list=red&list=green&list=blue',
-      '{?keys}' => [
-        '?keys=semi,%3B,dot,.,comma,%2C',
-        '?keys=dot,.,semi,%3B,comma,%2C',
-        '?keys=comma,%2C,semi,%3B,dot,.',
-        '?keys=semi,%3B,comma,%2C,dot,.',
-        '?keys=dot,.,comma,%2C,semi,%3B',
-        '?keys=comma,%2C,dot,.,semi,%3B'
-      ],
-      '{?keys*}' => [
-        '?semi=%3B&dot=.&comma=%2C',
-        '?dot=.&semi=%3B&comma=%2C',
-        '?comma=%2C&semi=%3B&dot=.',
-        '?semi=%3B&comma=%2C&dot=.',
-        '?dot=.&comma=%2C&semi=%3B',
-        '?comma=%2C&dot=.&semi=%3B'
-      ]
+      '{?keys}' => '?keys=semi,%3B,dot,.,comma,%2C',
+      '{?keys*}' => '?semi=%3B&dot=.&comma=%2C',
     }
   end
   context "Operator & with value modifiers" do
@@ -380,22 +278,8 @@
       '{&var:3}' => '&var=val',
       '{&list}' => '&list=red,green,blue',
       '{&list*}' => '&list=red&list=green&list=blue',
-      '{&keys}' => [
-        '&keys=semi,%3B,dot,.,comma,%2C',
-        '&keys=dot,.,semi,%3B,comma,%2C',
-        '&keys=comma,%2C,semi,%3B,dot,.',
-        '&keys=semi,%3B,comma,%2C,dot,.',
-        '&keys=dot,.,comma,%2C,semi,%3B',
-        '&keys=comma,%2C,dot,.,semi,%3B'
-      ],
-      '{&keys*}' => [
-        '&semi=%3B&dot=.&comma=%2C',
-        '&dot=.&semi=%3B&comma=%2C',
-        '&comma=%2C&semi=%3B&dot=.',
-        '&semi=%3B&comma=%2C&dot=.',
-        '&dot=.&comma=%2C&semi=%3B',
-        '&comma=%2C&dot=.&semi=%3B'
-      ]
+      '{&keys}' => '&keys=semi,%3B,dot,.,comma,%2C',
+      '{&keys*}' => '&semi=%3B&dot=.&comma=%2C',
     }
   end
 end
@@ -404,7 +288,7 @@
     {
       :var => "value",
       :semi => ";",
-      :year => %w(1965 2000 2012),
+      :year => [1965, 2000, 2012],
       :dom => %w(example com)
     }
   }
@@ -437,7 +321,7 @@
       :base  => "http://example.com/home/";,
       :path  => "/foo/bar",
       :list  => ["red", "green", "blue"],
-      :keys  => {"semi" => ";","dot" => ".","comma" => ","},
+      :keys  => {"semi" => ";","dot" => ".",:comma => ","},
       :v     => "6",
       :x     => "1024",
       :y     => "768",
@@ -475,22 +359,8 @@
       '{var:30}' => 'value',
       '{list}' => 'red,green,blue',
       '{list*}' => 'red,green,blue',
-      '{keys}' => [
-        'semi,%3B,dot,.,comma,%2C',
-        'dot,.,semi,%3B,comma,%2C',
-        'comma,%2C,semi,%3B,dot,.',
-        'semi,%3B,comma,%2C,dot,.',
-        'dot,.,comma,%2C,semi,%3B',
-        'comma,%2C,dot,.,semi,%3B'
-      ],
-      '{keys*}' => [
-        'semi=%3B,dot=.,comma=%2C',
-        'dot=.,semi=%3B,comma=%2C',
-        'comma=%2C,semi=%3B,dot=.',
-        'semi=%3B,comma=%2C,dot=.',
-        'dot=.,comma=%2C,semi=%3B',
-        'comma=%2C,dot=.,semi=%3B'
-      ]
+      '{keys}' => 'semi,%3B,dot,.,comma,%2C',
+      '{keys*}' => 'semi=%3B,dot=.,comma=%2C',
     }
   end
   context "reserved expansion (+)" do
@@ -510,22 +380,8 @@
       '{+path:6}/here' => '/foo/b/here',
       '{+list}' => 'red,green,blue',
       '{+list*}' => 'red,green,blue',
-      '{+keys}' => [
-        'semi,;,dot,.,comma,,',
-        'dot,.,semi,;,comma,,',
-        'comma,,,semi,;,dot,.',
-        'semi,;,comma,,,dot,.',
-        'dot,.,comma,,,semi,;',
-        'comma,,,dot,.,semi,;'
-      ],
-      '{+keys*}' => [
-        'semi=;,dot=.,comma=,',
-        'dot=.,semi=;,comma=,',
-        'comma=,,semi=;,dot=.',
-        'semi=;,comma=,,dot=.',
-        'dot=.,comma=,,semi=;',
-        'comma=,,dot=.,semi=;'
-      ]
+      '{+keys}' => 'semi,;,dot,.,comma,,',
+      '{+keys*}' => 'semi=;,dot=.,comma=,',
     }
   end
   context "fragment expansion (#)" do
@@ -540,22 +396,8 @@
       '{#path:6}/here' => '#/foo/b/here',
       '{#list}' => '#red,green,blue',
       '{#list*}' => '#red,green,blue',
-      '{#keys}' => [
-        '#semi,;,dot,.,comma,,',
-        '#dot,.,semi,;,comma,,',
-        '#comma,,,semi,;,dot,.',
-        '#semi,;,comma,,,dot,.',
-        '#dot,.,comma,,,semi,;',
-        '#comma,,,dot,.,semi,;'
-      ],
-      '{#keys*}' => [
-        '#semi=;,dot=.,comma=,',
-        '#dot=.,semi=;,comma=,',
-        '#comma=,,semi=;,dot=.',
-        '#semi=;,comma=,,dot=.',
-        '#dot=.,comma=,,semi=;',
-        '#comma=,,dot=.,semi=;'
-      ]
+      '{#keys}' => '#semi,;,dot,.,comma,,',
+      '{#keys*}' => '#semi=;,dot=.,comma=,',
     }
   end
   context "label expansion (.)" do
@@ -570,22 +412,8 @@
       'X{.var:3}' => 'X.val',
       'X{.list}' => 'X.red,green,blue',
       'X{.list*}' => 'X.red.green.blue',
-      'X{.keys}' => [
-        'X.semi,%3B,dot,.,comma,%2C',
-        'X.dot,.,semi,%3B,comma,%2C',
-        'X.comma,%2C,semi,%3B,dot,.',
-        'X.semi,%3B,comma,%2C,dot,.',
-        'X.dot,.,comma,%2C,semi,%3B',
-        'X.comma,%2C,dot,.,semi,%3B'
-      ],
-      'X{.keys*}' => [
-        'X.semi=%3B.dot=..comma=%2C',
-        'X.dot=..semi=%3B.comma=%2C',
-        'X.comma=%2C.semi=%3B.dot=.',
-        'X.semi=%3B.comma=%2C.dot=.',
-        'X.dot=..comma=%2C.semi=%3B',
-        'X.comma=%2C.dot=..semi=%3B'
-      ],
+      'X{.keys}' => 'X.semi,%3B,dot,.,comma,%2C',
+      'X{.keys*}' => 'X.semi=%3B.dot=..comma=%2C',
       'X{.empty_keys}' => 'X',
       'X{.empty_keys*}' => 'X'
     }
@@ -604,22 +432,8 @@
       '{/list}' => '/red,green,blue',
       '{/list*}' => '/red/green/blue',
       '{/list*,path:4}' => '/red/green/blue/%2Ffoo',
-      '{/keys}' => [
-        '/semi,%3B,dot,.,comma,%2C',
-        '/dot,.,semi,%3B,comma,%2C',
-        '/comma,%2C,semi,%3B,dot,.',
-        '/semi,%3B,comma,%2C,dot,.',
-        '/dot,.,comma,%2C,semi,%3B',
-        '/comma,%2C,dot,.,semi,%3B'
-      ],
-      '{/keys*}' => [
-        '/semi=%3B/dot=./comma=%2C',
-        '/dot=./semi=%3B/comma=%2C',
-        '/comma=%2C/semi=%3B/dot=.',
-        '/semi=%3B/comma=%2C/dot=.',
-        '/dot=./comma=%2C/semi=%3B',
-        '/comma=%2C/dot=./semi=%3B'
-      ]
+      '{/keys}' => '/semi,%3B,dot,.,comma,%2C',
+      '{/keys*}' => '/semi=%3B/dot=./comma=%2C',
     }
   end
   context "path-style expansion (;)" do
@@ -635,22 +449,8 @@
       '{;hello:5}' => ';hello=Hello',
       '{;list}' => ';list=red,green,blue',
       '{;list*}' => ';list=red;list=green;list=blue',
-      '{;keys}' => [
-        ';keys=semi,%3B,dot,.,comma,%2C',
-        ';keys=dot,.,semi,%3B,comma,%2C',
-        ';keys=comma,%2C,semi,%3B,dot,.',
-        ';keys=semi,%3B,comma,%2C,dot,.',
-        ';keys=dot,.,comma,%2C,semi,%3B',
-        ';keys=comma,%2C,dot,.,semi,%3B'
-      ],
-      '{;keys*}' => [
-        ';semi=%3B;dot=.;comma=%2C',
-        ';dot=.;semi=%3B;comma=%2C',
-        ';comma=%2C;semi=%3B;dot=.',
-        ';semi=%3B;comma=%2C;dot=.',
-        ';dot=.;comma=%2C;semi=%3B',
-        ';comma=%2C;dot=.;semi=%3B'
-      ]
+      '{;keys}' => ';keys=semi,%3B,dot,.,comma,%2C',
+      '{;keys*}' => ';semi=%3B;dot=.;comma=%2C',
     }
   end
   context "form query expansion (?)" do
@@ -663,22 +463,8 @@
       '{?var:3}' => '?var=val',
       '{?list}' => '?list=red,green,blue',
       '{?list*}' => '?list=red&list=green&list=blue',
-      '{?keys}' => [
-        '?keys=semi,%3B,dot,.,comma,%2C',
-        '?keys=dot,.,semi,%3B,comma,%2C',
-        '?keys=comma,%2C,semi,%3B,dot,.',
-        '?keys=semi,%3B,comma,%2C,dot,.',
-        '?keys=dot,.,comma,%2C,semi,%3B',
-        '?keys=comma,%2C,dot,.,semi,%3B'
-      ],
-      '{?keys*}' => [
-        '?semi=%3B&dot=.&comma=%2C',
-        '?dot=.&semi=%3B&comma=%2C',
-        '?comma=%2C&semi=%3B&dot=.',
-        '?semi=%3B&comma=%2C&dot=.',
-        '?dot=.&comma=%2C&semi=%3B',
-        '?comma=%2C&dot=.&semi=%3B'
-      ]
+      '{?keys}' => '?keys=semi,%3B,dot,.,comma,%2C',
+      '{?keys*}' => '?semi=%3B&dot=.&comma=%2C',
     }
   end
   context "form query expansion (&)" do
@@ -691,22 +477,8 @@
       '{&var:3}' => '&var=val',
       '{&list}' => '&list=red,green,blue',
       '{&list*}' => '&list=red&list=green&list=blue',
-      '{&keys}' => [
-        '&keys=semi,%3B,dot,.,comma,%2C',
-        '&keys=dot,.,semi,%3B,comma,%2C',
-        '&keys=comma,%2C,semi,%3B,dot,.',
-        '&keys=semi,%3B,comma,%2C,dot,.',
-        '&keys=dot,.,comma,%2C,semi,%3B',
-        '&keys=comma,%2C,dot,.,semi,%3B'
-      ],
-      '{&keys*}' => [
-        '&semi=%3B&dot=.&comma=%2C',
-        '&dot=.&semi=%3B&comma=%2C',
-        '&comma=%2C&semi=%3B&dot=.',
-        '&semi=%3B&comma=%2C&dot=.',
-        '&dot=.&comma=%2C&semi=%3B',
-        '&comma=%2C&dot=.&semi=%3B'
-      ]
+      '{&keys}' => '&keys=semi,%3B,dot,.,comma,%2C',
+      '{&keys*}' => '&semi=%3B&dot=.&comma=%2C',
     }
   end
   context "non-string key in match data" do
@@ -1021,6 +793,19 @@
         )
       end
 
+      it "normalizes as unicode even with wrong encoding specified" do
+        template = subject.partial_expand("query" => "Cafe\u0301".b)
+        expect(template.pattern).to eq(
+          "http://example.com/{resource}/Caf%C3%A9/";
+        )
+      end
+
+      it "raises on invalid unicode input" do
+        expect {
+          subject.partial_expand("query" => "M\xE9thode".b)
+        }.to raise_error(ArgumentError, "invalid byte sequence in UTF-8")
+      end
+
       it "does not normalize unicode when byte semantics requested" do
         template = subject.partial_expand({"query" => "Cafe\u0301"}, nil, 
false)
         expect(template.pattern).to eq(
@@ -1081,6 +866,17 @@
         expect(uri).to eq("http://example.com/search/Caf%C3%A9/";)
       end
 
+      it "normalizes as unicode even with wrong encoding specified" do
+        uri = subject.expand("query" => "Cafe\u0301".b).to_str
+        expect(uri).to eq("http://example.com/search/Caf%C3%A9/";)
+      end
+
+      it "raises on invalid unicode input" do
+        expect {
+          subject.expand("query" => "M\xE9thode".b).to_str
+        }.to raise_error(ArgumentError, "invalid byte sequence in UTF-8")
+      end
+
       it "does not normalize unicode when byte semantics requested" do
         uri = subject.expand({ "query" => "Cafe\u0301" }, nil, false).to_str
         expect(uri).to eq("http://example.com/search/Cafe%CC%81/";)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/spec/addressable/uri_spec.rb 
new/spec/addressable/uri_spec.rb
--- old/spec/addressable/uri_spec.rb    2022-08-19 12:48:27.000000000 +0200
+++ new/spec/addressable/uri_spec.rb    2023-04-09 17:02:36.000000000 +0200
@@ -3021,6 +3021,20 @@
   end
 end
 
+describe Addressable::URI, "when parsed with empty port" do
+  subject(:uri) do
+    Addressable::URI.parse("//example.com:")
+  end
+
+  it "should not infer a port" do
+    expect(uri.port).to be(nil)
+  end
+
+  it "should have a site value of '//example.com'" do
+    expect(uri.site).to eq("//example.com")
+  end
+end
+
 describe Addressable::URI, "when parsed from " +
     "'http://example.com/%2E/'" do
   before do
@@ -5939,6 +5953,26 @@
   end
 end
 
+describe Addressable::URI, "when normalizing a path with special unicode" do
+  it "does not stop at or ignore null bytes" do
+    expect(Addressable::URI.parse("/path%00segment/").normalize.path).to eq(
+      "/path%00segment/"
+    )
+  end
+
+  it "does apply NFC unicode normalization" do
+    expect(Addressable::URI.parse("/%E2%84%A6").normalize.path).to eq(
+      "/%CE%A9"
+    )
+  end
+
+  it "does not apply NFKC unicode normalization" do
+    expect(Addressable::URI.parse("/%C2%AF%C2%A0").normalize.path).to eq(
+      "/%C2%AF%C2%A0"
+    )
+  end
+end
+
 describe Addressable::URI, "when normalizing a partially encoded string" do
   it "should result in correct percent encoded sequence" do
     expect(Addressable::URI.normalize_component(
@@ -6743,3 +6777,25 @@
     ).to eq(main)
   end
 end
+
+describe Addressable::URI, "when deferring validation" do
+  subject(:deferred) { uri.instance_variable_get(:@validation_deferred) }
+
+  let(:uri) { Addressable::URI.parse("http://example.com";) }
+
+  it "defers validation within the block" do
+    uri.defer_validation do
+      expect(deferred).to be true
+    end
+  end
+
+  it "always resets deferral afterward" do
+    expect { uri.defer_validation { raise "boom" } }.to raise_error("boom")
+    expect(deferred).to be false
+  end
+
+  it "returns nil" do
+    res = uri.defer_validation {}
+    expect(res).to be nil
+  end
+end

Reply via email to