addressable 2.7.0 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 73a7a5a0dfea976017780e3b434e97aa58216019
4
- data.tar.gz: a708925a0882de04f840e9e54d279eb954775921
2
+ SHA256:
3
+ metadata.gz: 03a21b1eab156a16e90bd7963af85980edfbddc8f3dbe052766303dba76cc000
4
+ data.tar.gz: 03eca5d86f4c70f9320000f36e3cff4fd8023342a4e0ac855d0ef1ec89ee6183
5
5
  SHA512:
6
- metadata.gz: c311a5594d7f1051df67287badef72551c52c6bc47598017099d5e8b5c2a9638144bcc6d635b418dfc1970e185cf31016ad54f681c7156d440223ef62f060bc5
7
- data.tar.gz: 78527879654347fcf16be86684e1b37ca240c5a80d3baf2217cfdfb501cd6255de47a7cc8058c10e98e558b073981a27be06672f4dec8cf7d18e88a34609e6a1
6
+ metadata.gz: d504f9475ad823f5bb077b9c039a2c91c83e52c20896247a7289b61725c61b1ddefe8ae06155fb018fc67087cf04276081b42105a18394b45e2374ad0b2fadb0
7
+ data.tar.gz: b81766fbcb9335d5ca94403b62d3b2a6fae31b66cd3c05f48e1885eaf07883bfa1321b6930271fe1415135aec687af51312a26ce27bd4b83b2ac6424dec597c9
data/CHANGELOG.md CHANGED
@@ -1,7 +1,18 @@
1
+ # Addressable 2.8.0
2
+ - fixes ReDoS vulnerability in Addressable::Template#match
3
+ - no longer replaces `+` with spaces in queries for non-http(s) schemes
4
+ - fixed encoding ipv6 literals
5
+ - the `:compacted` flag for `normalized_query` now dedupes parameters
6
+ - fix broken `escape_component` alias
7
+ - dropping support for Ruby 2.0 and 2.1
8
+ - adding Ruby 3.0 compatibility for development tasks
9
+ - drop support for `rack-mount` and remove Addressable::Template#generate
10
+ - performance improvements
11
+ - switch CI/CD to GitHub Actions
12
+
1
13
  # Addressable 2.7.0
2
14
  - added `:compacted` flag to `normalized_query`
3
15
  - `heuristic_parse` handles `mailto:` more intuitively
4
- - refactored validation to use a prepended module
5
16
  - dropped explicit support for JRuby 9.0.5.0
6
17
  - compatibility w/ public_suffix 4.x
7
18
  - performance improvements
data/Gemfile CHANGED
@@ -1,12 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
- gemspec
5
+ gemspec(path: __FILE__ == "(eval)" ? ".." : ".")
4
6
 
5
7
  group :test do
6
8
  gem 'rspec', '~> 3.8'
7
9
  gem 'rspec-its', '~> 1.3'
8
10
  end
9
11
 
12
+ group :coverage do
13
+ gem "coveralls", "> 0.7", require: false, platforms: :mri
14
+ gem "simplecov", require: false
15
+ end
16
+
10
17
  group :development do
11
18
  gem 'launchy', '~> 2.4', '>= 2.4.3'
12
19
  gem 'redcarpet', :platform => :mri_19
@@ -14,19 +21,8 @@ group :development do
14
21
  end
15
22
 
16
23
  group :test, :development do
17
- gem 'rake', '> 10.0', '< 12'
18
- gem 'simplecov', :require => false
19
- gem 'coveralls', :require => false, :platforms => [
20
- :ruby_20, :ruby_21, :ruby_22, :ruby_23
21
- ]
22
- # Used to test compatibility.
23
- gem 'rack-mount', git: 'https://github.com/sporkmonger/rack-mount.git', require: 'rack/mount'
24
-
25
- if RUBY_VERSION.start_with?('2.0', '2.1')
26
- gem 'rack', '< 2', :require => false
27
- else
28
- gem 'rack', :require => false
29
- end
24
+ gem 'memory_profiler'
25
+ gem "rake", ">= 12.3.3"
30
26
  end
31
27
 
32
- gem 'idn-ruby', :platform => [:mri_20, :mri_21, :mri_22, :mri_23, :mri_24]
28
+ gem "idn-ruby", platform: :mri
data/README.md CHANGED
@@ -7,15 +7,15 @@
7
7
  <dt>License</dt><dd>Apache 2.0</dd>
8
8
  </dl>
9
9
 
10
- [![Gem Version](http://img.shields.io/gem/dt/addressable.svg)][gem]
11
- [![Build Status](https://secure.travis-ci.org/sporkmonger/addressable.svg?branch=master)][travis]
10
+ [![Gem Version](https://img.shields.io/gem/dt/addressable.svg)][gem]
11
+ [![Build Status](https://github.com/sporkmonger/addressable/workflows/CI/badge.svg)][actions]
12
12
  [![Test Coverage Status](https://img.shields.io/coveralls/sporkmonger/addressable.svg)][coveralls]
13
- [![Documentation Coverage Status](http://inch-ci.org/github/sporkmonger/addressable.svg?branch=master)][inch]
13
+ [![Documentation Coverage Status](https://inch-ci.org/github/sporkmonger/addressable.svg?branch=master)][inch]
14
14
 
15
15
  [gem]: https://rubygems.org/gems/addressable
16
- [travis]: http://travis-ci.org/sporkmonger/addressable
16
+ [actions]: https://github.com/sporkmonger/addressable/actions
17
17
  [coveralls]: https://coveralls.io/r/sporkmonger/addressable
18
- [inch]: http://inch-ci.org/github/sporkmonger/addressable
18
+ [inch]: https://inch-ci.org/github/sporkmonger/addressable
19
19
 
20
20
  # Description
21
21
 
@@ -98,7 +98,7 @@ You may optionally turn on native IDN support by installing libidn and the
98
98
  idn gem:
99
99
 
100
100
  ```console
101
- $ sudo apt-get install idn # Debian/Ubuntu
101
+ $ sudo apt-get install libidn11-dev # Debian/Ubuntu
102
102
  $ brew install libidn # OS X
103
103
  $ gem install idn-ruby
104
104
  ```
@@ -110,7 +110,7 @@ dependency using a pessimistic version constraint covering the major and minor
110
110
  values:
111
111
 
112
112
  ```ruby
113
- spec.add_dependency 'addressable', '~> 2.5'
113
+ spec.add_dependency 'addressable', '~> 2.7'
114
114
  ```
115
115
 
116
116
  If you need a specific bug fix, you can also specify minimum tiny versions
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # stub: addressable 2.8.0 ruby lib
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "addressable".freeze
6
+ s.version = "2.8.0"
7
+
8
+ s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
+ s.require_paths = ["lib".freeze]
10
+ s.authors = ["Bob Aman".freeze]
11
+ s.date = "2021-07-03"
12
+ 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
13
+ s.email = "bob@sporkmonger.com".freeze
14
+ s.extra_rdoc_files = ["README.md".freeze]
15
+ 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]
16
+ s.homepage = "https://github.com/sporkmonger/addressable".freeze
17
+ s.licenses = ["Apache-2.0".freeze]
18
+ s.rdoc_options = ["--main".freeze, "README.md".freeze]
19
+ s.required_ruby_version = Gem::Requirement.new(">= 2.0".freeze)
20
+ s.rubygems_version = "3.0.3".freeze
21
+ s.summary = "URI Implementation".freeze
22
+
23
+ if s.respond_to? :specification_version then
24
+ s.specification_version = 4
25
+
26
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
27
+ s.add_runtime_dependency(%q<public_suffix>.freeze, [">= 2.0.2", "< 5.0"])
28
+ s.add_development_dependency(%q<bundler>.freeze, [">= 1.0", "< 3.0"])
29
+ else
30
+ s.add_dependency(%q<public_suffix>.freeze, [">= 2.0.2", "< 5.0"])
31
+ s.add_dependency(%q<bundler>.freeze, [">= 1.0", "< 3.0"])
32
+ end
33
+ else
34
+ s.add_dependency(%q<public_suffix>.freeze, [">= 2.0.2", "< 5.0"])
35
+ s.add_dependency(%q<bundler>.freeze, [">= 1.0", "< 3.0"])
36
+ end
37
+ end
@@ -178,44 +178,46 @@ module Addressable
178
178
  end
179
179
 
180
180
  p = []
181
- ucs4_to_utf8 = lambda do |ch|
182
- if ch < 128
183
- p << ch
184
- elsif ch < 2048
185
- p << (ch >> 6 | 192)
186
- p << (ch & 63 | 128)
187
- elsif ch < 0x10000
188
- p << (ch >> 12 | 224)
189
- p << (ch >> 6 & 63 | 128)
190
- p << (ch & 63 | 128)
191
- elsif ch < 0x200000
192
- p << (ch >> 18 | 240)
193
- p << (ch >> 12 & 63 | 128)
194
- p << (ch >> 6 & 63 | 128)
195
- p << (ch & 63 | 128)
196
- elsif ch < 0x4000000
197
- p << (ch >> 24 | 248)
198
- p << (ch >> 18 & 63 | 128)
199
- p << (ch >> 12 & 63 | 128)
200
- p << (ch >> 6 & 63 | 128)
201
- p << (ch & 63 | 128)
202
- elsif ch < 0x80000000
203
- p << (ch >> 30 | 252)
204
- p << (ch >> 24 & 63 | 128)
205
- p << (ch >> 18 & 63 | 128)
206
- p << (ch >> 12 & 63 | 128)
207
- p << (ch >> 6 & 63 | 128)
208
- p << (ch & 63 | 128)
209
- end
210
- end
211
181
 
212
- ucs4_to_utf8.call(ch_one)
213
- ucs4_to_utf8.call(ch_two)
182
+ ucs4_to_utf8(ch_one, p)
183
+ ucs4_to_utf8(ch_two, p)
214
184
 
215
185
  return lookup_unicode_composition(p)
216
186
  end
217
187
  private_class_method :unicode_compose_pair
218
188
 
189
+ def self.ucs4_to_utf8(char, buffer)
190
+ if char < 128
191
+ buffer << char
192
+ elsif char < 2048
193
+ buffer << (char >> 6 | 192)
194
+ buffer << (char & 63 | 128)
195
+ elsif char < 0x10000
196
+ buffer << (char >> 12 | 224)
197
+ buffer << (char >> 6 & 63 | 128)
198
+ buffer << (char & 63 | 128)
199
+ elsif char < 0x200000
200
+ buffer << (char >> 18 | 240)
201
+ buffer << (char >> 12 & 63 | 128)
202
+ buffer << (char >> 6 & 63 | 128)
203
+ buffer << (char & 63 | 128)
204
+ elsif char < 0x4000000
205
+ buffer << (char >> 24 | 248)
206
+ buffer << (char >> 18 & 63 | 128)
207
+ buffer << (char >> 12 & 63 | 128)
208
+ buffer << (char >> 6 & 63 | 128)
209
+ buffer << (char & 63 | 128)
210
+ elsif char < 0x80000000
211
+ buffer << (char >> 30 | 252)
212
+ buffer << (char >> 24 & 63 | 128)
213
+ buffer << (char >> 18 & 63 | 128)
214
+ buffer << (char >> 12 & 63 | 128)
215
+ buffer << (char >> 6 & 63 | 128)
216
+ buffer << (char & 63 | 128)
217
+ end
218
+ end
219
+ private_class_method :ucs4_to_utf8
220
+
219
221
  def self.unicode_sort_canonical(unpacked)
220
222
  unpacked = unpacked.dup
221
223
  i = 1
@@ -37,7 +37,7 @@ module Addressable
37
37
  Addressable::URI::CharacterClasses::DIGIT + '_'
38
38
 
39
39
  var_char =
40
- "(?:(?:[#{variable_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)"
40
+ "(?>(?:[#{variable_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)"
41
41
  RESERVED =
42
42
  "(?:[#{anything}]|%[a-fA-F0-9][a-fA-F0-9])"
43
43
  UNRESERVED =
@@ -412,7 +412,7 @@ module Addressable
412
412
  # match.captures
413
413
  # #=> ["a", ["b", "c"]]
414
414
  def match(uri, processor=nil)
415
- uri = Addressable::URI.parse(uri)
415
+ uri = Addressable::URI.parse(uri) unless uri.is_a?(Addressable::URI)
416
416
  mapping = {}
417
417
 
418
418
  # First, we need to process the pattern, and extract the values.
@@ -653,40 +653,6 @@ module Addressable
653
653
  self.to_regexp.named_captures
654
654
  end
655
655
 
656
- ##
657
- # Generates a route result for a given set of parameters.
658
- # Should only be used by rack-mount.
659
- #
660
- # @param params [Hash] The set of parameters used to expand the template.
661
- # @param recall [Hash] Default parameters used to expand the template.
662
- # @param options [Hash] Either a `:processor` or a `:parameterize` block.
663
- #
664
- # @api private
665
- def generate(params={}, recall={}, options={})
666
- merged = recall.merge(params)
667
- if options[:processor]
668
- processor = options[:processor]
669
- elsif options[:parameterize]
670
- # TODO: This is sending me into fits trying to shoe-horn this into
671
- # the existing API. I think I've got this backwards and processors
672
- # should be a set of 4 optional blocks named :validate, :transform,
673
- # :match, and :restore. Having to use a singleton here is a huge
674
- # code smell.
675
- processor = Object.new
676
- class <<processor
677
- attr_accessor :block
678
- def transform(name, value)
679
- block.call(name, value)
680
- end
681
- end
682
- processor.block = options[:parameterize]
683
- else
684
- processor = nil
685
- end
686
- result = self.expand(merged, processor)
687
- result.to_s if result
688
- end
689
-
690
656
  private
691
657
  def ordered_variable_defaults
692
658
  @ordered_variable_defaults ||= begin
@@ -973,15 +939,35 @@ module Addressable
973
939
  end
974
940
  end
975
941
 
942
+ ##
943
+ # Generates the <tt>Regexp</tt> that parses a template pattern. Memoizes the
944
+ # value if template processor not set (processors may not be deterministic)
945
+ #
946
+ # @param [String] pattern The URI template pattern.
947
+ # @param [#match] processor The template processor to use.
948
+ #
949
+ # @return [Array, Regexp]
950
+ # An array of expansion variables nad a regular expression which may be
951
+ # used to parse a template pattern
952
+ def parse_template_pattern(pattern, processor = nil)
953
+ if processor.nil? && pattern == @pattern
954
+ @cached_template_parse ||=
955
+ parse_new_template_pattern(pattern, processor)
956
+ else
957
+ parse_new_template_pattern(pattern, processor)
958
+ end
959
+ end
960
+
976
961
  ##
977
962
  # Generates the <tt>Regexp</tt> that parses a template pattern.
978
963
  #
979
964
  # @param [String] pattern The URI template pattern.
980
965
  # @param [#match] processor The template processor to use.
981
966
  #
982
- # @return [Regexp]
983
- # A regular expression which may be used to parse a template pattern.
984
- def parse_template_pattern(pattern, processor=nil)
967
+ # @return [Array, Regexp]
968
+ # An array of expansion variables nad a regular expression which may be
969
+ # used to parse a template pattern
970
+ def parse_new_template_pattern(pattern, processor = nil)
985
971
  # Escape the pattern. The two gsubs restore the escaped curly braces
986
972
  # back to their original form. Basically, escape everything that isn't
987
973
  # within an expansion.
@@ -48,12 +48,21 @@ module Addressable
48
48
  PCHAR = UNRESERVED + SUB_DELIMS + "\\:\\@"
49
49
  SCHEME = ALPHA + DIGIT + "\\-\\+\\."
50
50
  HOST = UNRESERVED + SUB_DELIMS + "\\[\\:\\]"
51
- AUTHORITY = PCHAR
51
+ AUTHORITY = PCHAR + "\\[\\:\\]"
52
52
  PATH = PCHAR + "\\/"
53
53
  QUERY = PCHAR + "\\/\\?"
54
54
  FRAGMENT = PCHAR + "\\/\\?"
55
55
  end
56
56
 
57
+ module NormalizeCharacterClasses
58
+ HOST = /[^#{CharacterClasses::HOST}]/
59
+ UNRESERVED = /[^#{CharacterClasses::UNRESERVED}]/
60
+ PCHAR = /[^#{CharacterClasses::PCHAR}]/
61
+ SCHEME = /[^#{CharacterClasses::SCHEME}]/
62
+ FRAGMENT = /[^#{CharacterClasses::FRAGMENT}]/
63
+ QUERY = %r{[^a-zA-Z0-9\-\.\_\~\!\$\'\(\)\*\+\,\=\:\@\/\?%]|%(?!2B|2b)}
64
+ end
65
+
57
66
  SLASH = '/'
58
67
  EMPTY_STR = ''
59
68
 
@@ -73,7 +82,7 @@ module Addressable
73
82
  "wais" => 210,
74
83
  "ldap" => 389,
75
84
  "prospero" => 1525
76
- }
85
+ }.freeze
77
86
 
78
87
  ##
79
88
  # Returns a URI object based on the parsed string.
@@ -421,7 +430,7 @@ module Addressable
421
430
  end
422
431
 
423
432
  class << self
424
- alias_method :encode_component, :encode_component
433
+ alias_method :escape_component, :encode_component
425
434
  end
426
435
 
427
436
  ##
@@ -463,7 +472,11 @@ module Addressable
463
472
  uri = uri.dup
464
473
  # Seriously, only use UTF-8. I'm really not kidding!
465
474
  uri.force_encoding("utf-8")
466
- leave_encoded = leave_encoded.dup.force_encoding("utf-8")
475
+
476
+ unless leave_encoded.empty?
477
+ leave_encoded = leave_encoded.dup.force_encoding("utf-8")
478
+ end
479
+
467
480
  result = uri.gsub(/%[0-9a-f]{2}/iu) do |sequence|
468
481
  c = sequence[1..3].to_i(16).chr
469
482
  c.force_encoding("utf-8")
@@ -554,7 +567,11 @@ module Addressable
554
567
  end.flatten.join('|')})"
555
568
  end
556
569
 
557
- character_class = /[^#{character_class}]#{leave_re}/
570
+ character_class = if leave_re
571
+ /[^#{character_class}]#{leave_re}/
572
+ else
573
+ /[^#{character_class}]/
574
+ end
558
575
  end
559
576
  # We can't perform regexps on invalid UTF sequences, but
560
577
  # here we need to, so switch to ASCII.
@@ -878,7 +895,7 @@ module Addressable
878
895
  else
879
896
  Addressable::URI.normalize_component(
880
897
  self.scheme.strip.downcase,
881
- Addressable::URI::CharacterClasses::SCHEME
898
+ Addressable::URI::NormalizeCharacterClasses::SCHEME
882
899
  )
883
900
  end
884
901
  end
@@ -898,7 +915,7 @@ module Addressable
898
915
  new_scheme = new_scheme.to_str
899
916
  end
900
917
  if new_scheme && new_scheme !~ /\A[a-z][a-z0-9\.\+\-]*\z/i
901
- raise InvalidURIError, "Invalid scheme format: #{new_scheme}"
918
+ raise InvalidURIError, "Invalid scheme format: '#{new_scheme}'"
902
919
  end
903
920
  @scheme = new_scheme
904
921
  @scheme = nil if @scheme.to_s.strip.empty?
@@ -933,7 +950,7 @@ module Addressable
933
950
  else
934
951
  Addressable::URI.normalize_component(
935
952
  self.user.strip,
936
- Addressable::URI::CharacterClasses::UNRESERVED
953
+ Addressable::URI::NormalizeCharacterClasses::UNRESERVED
937
954
  )
938
955
  end
939
956
  end
@@ -990,7 +1007,7 @@ module Addressable
990
1007
  else
991
1008
  Addressable::URI.normalize_component(
992
1009
  self.password.strip,
993
- Addressable::URI::CharacterClasses::UNRESERVED
1010
+ Addressable::URI::NormalizeCharacterClasses::UNRESERVED
994
1011
  )
995
1012
  end
996
1013
  end
@@ -1114,6 +1131,7 @@ module Addressable
1114
1131
  # @return [String] The host component, normalized.
1115
1132
  def normalized_host
1116
1133
  return nil unless self.host
1134
+
1117
1135
  @normalized_host ||= begin
1118
1136
  if !self.host.strip.empty?
1119
1137
  result = ::Addressable::IDNA.to_ascii(
@@ -1125,14 +1143,17 @@ module Addressable
1125
1143
  end
1126
1144
  result = Addressable::URI.normalize_component(
1127
1145
  result,
1128
- CharacterClasses::HOST)
1146
+ NormalizeCharacterClasses::HOST
1147
+ )
1129
1148
  result
1130
1149
  else
1131
1150
  EMPTY_STR.dup
1132
1151
  end
1133
1152
  end
1134
1153
  # All normalized values should be UTF-8
1135
- @normalized_host.force_encoding(Encoding::UTF_8) if @normalized_host
1154
+ if @normalized_host && !@normalized_host.empty?
1155
+ @normalized_host.force_encoding(Encoding::UTF_8)
1156
+ end
1136
1157
  @normalized_host
1137
1158
  end
1138
1159
 
@@ -1537,7 +1558,7 @@ module Addressable
1537
1558
  result = path.strip.split(SLASH, -1).map do |segment|
1538
1559
  Addressable::URI.normalize_component(
1539
1560
  segment,
1540
- Addressable::URI::CharacterClasses::PCHAR
1561
+ Addressable::URI::NormalizeCharacterClasses::PCHAR
1541
1562
  )
1542
1563
  end.join(SLASH)
1543
1564
 
@@ -1612,11 +1633,15 @@ module Addressable
1612
1633
  modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup
1613
1634
  # Make sure possible key-value pair delimiters are escaped.
1614
1635
  modified_query_class.sub!("\\&", "").sub!("\\;", "")
1615
- pairs = (self.query || "").split("&", -1)
1616
- pairs.delete_if(&:empty?) if flags.include?(:compacted)
1636
+ pairs = (query || "").split("&", -1)
1637
+ pairs.delete_if(&:empty?).uniq! if flags.include?(:compacted)
1617
1638
  pairs.sort! if flags.include?(:sorted)
1618
1639
  component = pairs.map do |pair|
1619
- Addressable::URI.normalize_component(pair, modified_query_class, "+")
1640
+ Addressable::URI.normalize_component(
1641
+ pair,
1642
+ Addressable::URI::NormalizeCharacterClasses::QUERY,
1643
+ "+"
1644
+ )
1620
1645
  end.join("&")
1621
1646
  component == "" ? nil : component
1622
1647
  end
@@ -1675,11 +1700,13 @@ module Addressable
1675
1700
  # so it's best to make all changes in-place.
1676
1701
  pair[0] = URI.unencode_component(pair[0])
1677
1702
  if pair[1].respond_to?(:to_str)
1703
+ value = pair[1].to_str
1678
1704
  # I loathe the fact that I have to do this. Stupid HTML 4.01.
1679
1705
  # Treating '+' as a space was just an unbelievably bad idea.
1680
1706
  # There was nothing wrong with '%20'!
1681
1707
  # If it ain't broke, don't fix it!
1682
- pair[1] = URI.unencode_component(pair[1].to_str.tr("+", " "))
1708
+ value = value.tr("+", " ") if ["http", "https", nil].include?(scheme)
1709
+ pair[1] = URI.unencode_component(value)
1683
1710
  end
1684
1711
  if return_type == Hash
1685
1712
  accu[pair[0]] = pair[1]
@@ -1810,7 +1837,7 @@ module Addressable
1810
1837
  @normalized_fragment ||= begin
1811
1838
  component = Addressable::URI.normalize_component(
1812
1839
  self.fragment,
1813
- Addressable::URI::CharacterClasses::FRAGMENT
1840
+ Addressable::URI::NormalizeCharacterClasses::FRAGMENT
1814
1841
  )
1815
1842
  component == "" ? nil : component
1816
1843
  end