addressable 1.0.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +15 -0
- data/LICENSE +1 -1
- data/README +18 -18
- data/Rakefile +51 -0
- data/lib/addressable/idna.rb +4867 -0
- data/lib/addressable/uri.rb +1865 -643
- data/lib/addressable/version.rb +9 -6
- data/spec/addressable/idna_spec.rb +196 -0
- data/spec/addressable/uri_spec.rb +1454 -294
- data/tasks/clobber.rake +2 -0
- data/tasks/gem.rake +68 -0
- data/tasks/git.rake +40 -0
- data/tasks/metrics.rake +22 -0
- data/tasks/rdoc.rake +29 -0
- data/tasks/rubyforge.rake +89 -0
- data/tasks/spec.rake +64 -0
- data/website/index.html +107 -0
- metadata +27 -12
- data/coverage/-Library-Ruby-Gems-1_8-gems-rcov-0_8_1_2_0-lib-rcov_rb.html +0 -1598
- data/coverage/-Library-Ruby-Gems-1_8-gems-rspec-1_1_3-bin-spec.html +0 -614
- data/coverage/-Library-Ruby-Gems-1_8-gems-rspec-1_1_3-lib-spec_rb.html +0 -640
- data/coverage/-Library-Ruby-Gems-1_8-gems-rspec-1_1_3-plugins-mock_frameworks-rspec_rb.html +0 -628
- data/coverage/index.html +0 -360
- data/coverage/lib-addressable-uri_rb.html +0 -1857
- data/coverage/lib-addressable-version_rb.html +0 -642
- data/rakefile +0 -242
    
        data/lib/addressable/uri.rb
    CHANGED
    
    | @@ -1,5 +1,6 @@ | |
| 1 | 
            +
            # coding:utf-8
         | 
| 1 2 | 
             
            #--
         | 
| 2 | 
            -
            # Addressable, Copyright (c) 2006- | 
| 3 | 
            +
            # Addressable, Copyright (c) 2006-2008 Bob Aman
         | 
| 3 4 | 
             
            #
         | 
| 4 5 | 
             
            # Permission is hereby granted, free of charge, to any person obtaining
         | 
| 5 6 | 
             
            # a copy of this software and associated documentation files (the
         | 
| @@ -24,23 +25,43 @@ | |
| 24 25 | 
             
            $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '/..')))
         | 
| 25 26 | 
             
            $:.uniq!
         | 
| 26 27 |  | 
| 27 | 
            -
            require  | 
| 28 | 
            +
            require "addressable/version"
         | 
| 29 | 
            +
            require "addressable/idna"
         | 
| 28 30 |  | 
| 29 31 | 
             
            module Addressable
         | 
| 30 | 
            -
               | 
| 32 | 
            +
              ##
         | 
| 33 | 
            +
              # This is an implementation of a URI parser based on
         | 
| 34 | 
            +
              # <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>,
         | 
| 35 | 
            +
              # <a href="http://www.ietf.org/rfc/rfc3987.txt">RFC 3987</a>.
         | 
| 31 36 | 
             
              class URI
         | 
| 37 | 
            +
                ##
         | 
| 32 38 | 
             
                # Raised if something other than a uri is supplied.
         | 
| 33 39 | 
             
                class InvalidURIError < StandardError
         | 
| 34 40 | 
             
                end
         | 
| 35 | 
            -
             | 
| 41 | 
            +
             | 
| 42 | 
            +
                ##
         | 
| 36 43 | 
             
                # Raised if an invalid method option is supplied.
         | 
| 37 44 | 
             
                class InvalidOptionError < StandardError
         | 
| 38 45 | 
             
                end
         | 
| 39 | 
            -
             | 
| 40 | 
            -
                 | 
| 41 | 
            -
                 | 
| 46 | 
            +
             | 
| 47 | 
            +
                ##
         | 
| 48 | 
            +
                # Raised if an invalid template value is supplied.
         | 
| 49 | 
            +
                class InvalidTemplateValueError < StandardError
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                ##
         | 
| 53 | 
            +
                # Raised if an invalid template operator is used in a pattern.
         | 
| 54 | 
            +
                class InvalidTemplateOperatorError < StandardError
         | 
| 42 55 | 
             
                end
         | 
| 43 56 |  | 
| 57 | 
            +
                ##
         | 
| 58 | 
            +
                # Raised if an invalid template operator is used in a pattern.
         | 
| 59 | 
            +
                class TemplateOperatorAbortedError < StandardError
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                ##
         | 
| 63 | 
            +
                # Container for the character classes specified in
         | 
| 64 | 
            +
                # <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
         | 
| 44 65 | 
             
                module CharacterClasses
         | 
| 45 66 | 
             
                  ALPHA = "a-zA-Z"
         | 
| 46 67 | 
             
                  DIGIT = "0-9"
         | 
| @@ -54,24 +75,39 @@ module Addressable | |
| 54 75 | 
             
                  PATH = PCHAR + "\\/"
         | 
| 55 76 | 
             
                  QUERY = PCHAR + "\\/\\?"
         | 
| 56 77 | 
             
                  FRAGMENT = PCHAR + "\\/\\?"
         | 
| 57 | 
            -
                end | 
| 58 | 
            -
             | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                ##
         | 
| 59 81 | 
             
                # Returns a URI object based on the parsed string.
         | 
| 60 | 
            -
                 | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 82 | 
            +
                #
         | 
| 83 | 
            +
                # @param [String, Addressable::URI, #to_str] uri
         | 
| 84 | 
            +
                #   The URI string to parse.  No parsing is performed if the object is
         | 
| 85 | 
            +
                #   already an <tt>Addressable::URI</tt>.
         | 
| 86 | 
            +
                #
         | 
| 87 | 
            +
                # @return [Addressable::URI] The parsed URI.
         | 
| 88 | 
            +
                def self.parse(uri)
         | 
| 89 | 
            +
                  # If we were given nil, return nil.
         | 
| 90 | 
            +
                  return nil unless uri
         | 
| 63 91 | 
             
                  # If a URI object is passed, just return itself.
         | 
| 64 | 
            -
                  return  | 
| 65 | 
            -
                  
         | 
| 92 | 
            +
                  return uri if uri.kind_of?(self)
         | 
| 93 | 
            +
                  if !uri.respond_to?(:to_str)
         | 
| 94 | 
            +
                    raise TypeError, "Can't convert #{uri.class} into String."
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
                  # Otherwise, convert to a String
         | 
| 97 | 
            +
                  uri = uri.to_str
         | 
| 98 | 
            +
             | 
| 66 99 | 
             
                  # If a URI object of the Ruby standard library variety is passed,
         | 
| 67 100 | 
             
                  # convert it to a string, then parse the string.
         | 
| 68 | 
            -
                   | 
| 69 | 
            -
             | 
| 101 | 
            +
                  # We do the check this way because we don't want to accidentally
         | 
| 102 | 
            +
                  # cause a missing constant exception to be thrown.
         | 
| 103 | 
            +
                  if uri.class.name =~ /^URI\b/
         | 
| 104 | 
            +
                    uri = uri.to_s
         | 
| 70 105 | 
             
                  end
         | 
| 71 | 
            -
             | 
| 106 | 
            +
             | 
| 107 | 
            +
                  # This Regexp supplied as an example in RFC 3986, and it works great.
         | 
| 72 108 | 
             
                  uri_regex =
         | 
| 73 109 | 
             
                    /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
         | 
| 74 | 
            -
                  scan =  | 
| 110 | 
            +
                  scan = uri.scan(uri_regex)
         | 
| 75 111 | 
             
                  fragments = scan[0]
         | 
| 76 112 | 
             
                  return nil if fragments.nil?
         | 
| 77 113 | 
             
                  scheme = fragments[1]
         | 
| @@ -85,47 +121,74 @@ module Addressable | |
| 85 121 | 
             
                  host = nil
         | 
| 86 122 | 
             
                  port = nil
         | 
| 87 123 | 
             
                  if authority != nil
         | 
| 88 | 
            -
                     | 
| 124 | 
            +
                    # The Regexp above doesn't split apart the authority.
         | 
| 125 | 
            +
                    userinfo = authority[/^([^\[\]]*)@/, 1]
         | 
| 89 126 | 
             
                    if userinfo != nil
         | 
| 90 | 
            -
                      user = userinfo.strip | 
| 91 | 
            -
                      password = userinfo.strip | 
| 127 | 
            +
                      user = userinfo.strip[/^([^:]*):?/, 1]
         | 
| 128 | 
            +
                      password = userinfo.strip[/:(.*)$/, 1]
         | 
| 92 129 | 
             
                    end
         | 
| 93 130 | 
             
                    host = authority.gsub(/^([^\[\]]*)@/, "").gsub(/:([^:@\[\]]*?)$/, "")
         | 
| 94 | 
            -
                    port = authority | 
| 131 | 
            +
                    port = authority[/:([^:@\[\]]*?)$/, 1]
         | 
| 95 132 | 
             
                  end
         | 
| 96 133 | 
             
                  if port == ""
         | 
| 97 134 | 
             
                    port = nil
         | 
| 98 135 | 
             
                  end
         | 
| 99 | 
            -
             | 
| 136 | 
            +
             | 
| 100 137 | 
             
                  return Addressable::URI.new(
         | 
| 101 | 
            -
                    scheme | 
| 138 | 
            +
                    :scheme => scheme,
         | 
| 139 | 
            +
                    :user => user,
         | 
| 140 | 
            +
                    :password => password,
         | 
| 141 | 
            +
                    :host => host,
         | 
| 142 | 
            +
                    :port => port,
         | 
| 143 | 
            +
                    :path => path,
         | 
| 144 | 
            +
                    :query => query,
         | 
| 145 | 
            +
                    :fragment => fragment
         | 
| 146 | 
            +
                  )
         | 
| 102 147 | 
             
                end
         | 
| 103 | 
            -
             | 
| 148 | 
            +
             | 
| 149 | 
            +
                ##
         | 
| 104 150 | 
             
                # Converts an input to a URI.  The input does not have to be a valid
         | 
| 105 | 
            -
                # URI  | 
| 106 | 
            -
                # This is not standards | 
| 107 | 
            -
                 | 
| 108 | 
            -
             | 
| 151 | 
            +
                # URI — the method will use heuristics to guess what URI was intended.
         | 
| 152 | 
            +
                # This is not standards-compliant, merely user-friendly.
         | 
| 153 | 
            +
                #
         | 
| 154 | 
            +
                # @param [String, Addressable::URI, #to_str] uri
         | 
| 155 | 
            +
                #   The URI string to parse.  No parsing is performed if the object is
         | 
| 156 | 
            +
                #   already an <tt>Addressable::URI</tt>.
         | 
| 157 | 
            +
                # @param [Hash] hints
         | 
| 158 | 
            +
                #   A <tt>Hash</tt> of hints to the heuristic parser.  Defaults to
         | 
| 159 | 
            +
                #   <tt>{:scheme => "http"}</tt>.
         | 
| 160 | 
            +
                #
         | 
| 161 | 
            +
                # @return [Addressable::URI] The parsed URI.
         | 
| 162 | 
            +
                def self.heuristic_parse(uri, hints={})
         | 
| 163 | 
            +
                  # If we were given nil, return nil.
         | 
| 164 | 
            +
                  return nil unless uri
         | 
| 165 | 
            +
                  # If a URI object is passed, just return itself.
         | 
| 166 | 
            +
                  return uri if uri.kind_of?(self)
         | 
| 167 | 
            +
                  if !uri.respond_to?(:to_str)
         | 
| 168 | 
            +
                    raise TypeError, "Can't convert #{uri.class} into String."
         | 
| 169 | 
            +
                  end
         | 
| 170 | 
            +
                  # Otherwise, convert to a String
         | 
| 171 | 
            +
                  uri = uri.to_str.dup
         | 
| 109 172 | 
             
                  hints = {
         | 
| 110 173 | 
             
                    :scheme => "http"
         | 
| 111 174 | 
             
                  }.merge(hints)
         | 
| 112 | 
            -
                  case  | 
| 175 | 
            +
                  case uri
         | 
| 113 176 | 
             
                  when /^http:\/+/
         | 
| 114 | 
            -
                     | 
| 177 | 
            +
                    uri.gsub!(/^http:\/+/, "http://")
         | 
| 115 178 | 
             
                  when /^feed:\/+http:\/+/
         | 
| 116 | 
            -
                     | 
| 179 | 
            +
                    uri.gsub!(/^feed:\/+http:\/+/, "feed:http://")
         | 
| 117 180 | 
             
                  when /^feed:\/+/
         | 
| 118 | 
            -
                     | 
| 181 | 
            +
                    uri.gsub!(/^feed:\/+/, "feed://")
         | 
| 119 182 | 
             
                  when /^file:\/+/
         | 
| 120 | 
            -
                     | 
| 183 | 
            +
                    uri.gsub!(/^file:\/+/, "file:///")
         | 
| 121 184 | 
             
                  end
         | 
| 122 | 
            -
                  parsed = self.parse( | 
| 185 | 
            +
                  parsed = self.parse(uri)
         | 
| 123 186 | 
             
                  if parsed.scheme =~ /^[^\/?#\.]+\.[^\/?#]+$/
         | 
| 124 | 
            -
                    parsed = self.parse(hints[:scheme] + "://" +  | 
| 187 | 
            +
                    parsed = self.parse(hints[:scheme] + "://" + uri)
         | 
| 125 188 | 
             
                  end
         | 
| 126 189 | 
             
                  if parsed.authority == nil
         | 
| 127 190 | 
             
                    if parsed.path =~ /^[^\/]+\./
         | 
| 128 | 
            -
                      new_host = parsed.path | 
| 191 | 
            +
                      new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1]
         | 
| 129 192 | 
             
                      if new_host
         | 
| 130 193 | 
             
                        new_path = parsed.path.gsub(
         | 
| 131 194 | 
             
                          Regexp.new("^" + Regexp.escape(new_host)), "")
         | 
| @@ -137,77 +200,167 @@ module Addressable | |
| 137 200 | 
             
                  end
         | 
| 138 201 | 
             
                  return parsed
         | 
| 139 202 | 
             
                end
         | 
| 140 | 
            -
             | 
| 141 | 
            -
                 | 
| 203 | 
            +
             | 
| 204 | 
            +
                ##
         | 
| 205 | 
            +
                # Converts a path to a file scheme URI.  If the path supplied is
         | 
| 142 206 | 
             
                # relative, it will be returned as a relative URI.  If the path supplied
         | 
| 143 | 
            -
                # is actually a URI, it will  | 
| 207 | 
            +
                # is actually a non-file URI, it will parse the URI as if it had been
         | 
| 208 | 
            +
                # parsed with <tt>Addressable::URI.parse</tt>.  Handles all of the
         | 
| 209 | 
            +
                # various Microsoft-specific formats for specifying paths.
         | 
| 210 | 
            +
                #
         | 
| 211 | 
            +
                # @param [String, Addressable::URI, #to_str] path
         | 
| 212 | 
            +
                #   Typically a <tt>String</tt> path to a file or directory, but
         | 
| 213 | 
            +
                #   will return a sensible return value if an absolute URI is supplied
         | 
| 214 | 
            +
                #   instead.
         | 
| 215 | 
            +
                #
         | 
| 216 | 
            +
                # @return [Addressable::URI]
         | 
| 217 | 
            +
                #   The parsed file scheme URI or the original URI if some other URI
         | 
| 218 | 
            +
                #   scheme was provided.
         | 
| 219 | 
            +
                #
         | 
| 220 | 
            +
                # @example
         | 
| 221 | 
            +
                #   base = Addressable::URI.convert_path("/absolute/path/")
         | 
| 222 | 
            +
                #   uri = Addressable::URI.convert_path("relative/path")
         | 
| 223 | 
            +
                #   (base + uri).to_s
         | 
| 224 | 
            +
                #   #=> "file:///absolute/path/relative/path"
         | 
| 225 | 
            +
                #
         | 
| 226 | 
            +
                #   Addressable::URI.convert_path(
         | 
| 227 | 
            +
                #     "c:\\windows\\My Documents 100%20\\foo.txt"
         | 
| 228 | 
            +
                #   ).to_s
         | 
| 229 | 
            +
                #   #=> "file:///c:/windows/My%20Documents%20100%20/foo.txt"
         | 
| 230 | 
            +
                #
         | 
| 231 | 
            +
                #   Addressable::URI.convert_path("http://example.com/").to_s
         | 
| 232 | 
            +
                #   #=> "http://example.com/"
         | 
| 144 233 | 
             
                def self.convert_path(path)
         | 
| 145 | 
            -
                   | 
| 146 | 
            -
                  
         | 
| 147 | 
            -
                   | 
| 148 | 
            -
                  if  | 
| 149 | 
            -
             | 
| 150 | 
            -
             | 
| 151 | 
            -
                  if converted_uri.length > 0 &&
         | 
| 152 | 
            -
                      converted_uri.scan(/^[a-zA-Z]:[\\\/]/).size > 0
         | 
| 153 | 
            -
                    converted_uri = "file:///" + converted_uri
         | 
| 234 | 
            +
                  # If we were given nil, return nil.
         | 
| 235 | 
            +
                  return nil unless path
         | 
| 236 | 
            +
                  # If a URI object is passed, just return itself.
         | 
| 237 | 
            +
                  return path if path.kind_of?(self)
         | 
| 238 | 
            +
                  if !path.respond_to?(:to_str)
         | 
| 239 | 
            +
                    raise TypeError, "Can't convert #{path.class} into String."
         | 
| 154 240 | 
             
                  end
         | 
| 155 | 
            -
                   | 
| 156 | 
            -
                   | 
| 241 | 
            +
                  # Otherwise, convert to a String
         | 
| 242 | 
            +
                  path = path.to_str.strip
         | 
| 243 | 
            +
             | 
| 244 | 
            +
                  path.gsub!(/^file:\/?\/?/, "") if path =~ /^file:\/?\/?/
         | 
| 245 | 
            +
                  path = "/" + path if path =~ /^([a-zA-Z])(\||:)/
         | 
| 246 | 
            +
                  uri = self.parse(path)
         | 
| 247 | 
            +
             | 
| 248 | 
            +
                  if uri.scheme == nil
         | 
| 157 249 | 
             
                    # Adjust windows-style uris
         | 
| 158 | 
            -
                     | 
| 159 | 
            -
                     | 
| 160 | 
            -
                     | 
| 161 | 
            -
             | 
| 162 | 
            -
             | 
| 163 | 
            -
                       | 
| 164 | 
            -
                      converted_uri.path = converted_uri.path + '/'
         | 
| 250 | 
            +
                    uri.path.gsub!(/^\/?([a-zA-Z])\|(\\|\/)/, "/\\1:/")
         | 
| 251 | 
            +
                    uri.path.gsub!(/\\/, "/")
         | 
| 252 | 
            +
                    if File.exists?(uri.path) &&
         | 
| 253 | 
            +
                        File.stat(uri.path).directory?
         | 
| 254 | 
            +
                      uri.path.gsub!(/\/$/, "")
         | 
| 255 | 
            +
                      uri.path = uri.path + '/'
         | 
| 165 256 | 
             
                    end
         | 
| 166 | 
            -
             | 
| 167 | 
            -
                     | 
| 257 | 
            +
             | 
| 258 | 
            +
                    # If the path is absolute, set the scheme and host.
         | 
| 259 | 
            +
                    if uri.path =~ /^\//
         | 
| 260 | 
            +
                      uri.scheme = "file"
         | 
| 261 | 
            +
                      uri.host = ""
         | 
| 262 | 
            +
                    end
         | 
| 263 | 
            +
                    uri.normalize!
         | 
| 168 264 | 
             
                  end
         | 
| 169 | 
            -
             | 
| 170 | 
            -
                  return  | 
| 265 | 
            +
             | 
| 266 | 
            +
                  return uri
         | 
| 171 267 | 
             
                end
         | 
| 172 | 
            -
             | 
| 268 | 
            +
             | 
| 269 | 
            +
                ##
         | 
| 173 270 | 
             
                # Expands a URI template into a full URI.
         | 
| 174 271 | 
             
                #
         | 
| 175 | 
            -
                #  | 
| 176 | 
            -
                #  | 
| 177 | 
            -
                #  | 
| 178 | 
            -
                #  | 
| 179 | 
            -
                #  | 
| 180 | 
            -
                #  | 
| 181 | 
            -
                #  | 
| 182 | 
            -
                #
         | 
| 183 | 
            -
                #  | 
| 184 | 
            -
                # | 
| 185 | 
            -
                #   | 
| 186 | 
            -
                # | 
| 187 | 
            -
                # | 
| 188 | 
            -
                # | 
| 189 | 
            -
                # | 
| 190 | 
            -
                # | 
| 191 | 
            -
                # | 
| 192 | 
            -
                # | 
| 193 | 
            -
                # | 
| 194 | 
            -
                # | 
| 195 | 
            -
                # | 
| 196 | 
            -
                # | 
| 197 | 
            -
                # | 
| 198 | 
            -
                # | 
| 199 | 
            -
                # | 
| 200 | 
            -
                # | 
| 201 | 
            -
                # | 
| 272 | 
            +
                # @param [String, #to_str] pattern The URI template pattern.
         | 
| 273 | 
            +
                # @param [Hash] mapping The mapping that corresponds to the pattern.
         | 
| 274 | 
            +
                # @param [#validate, #transform] processor
         | 
| 275 | 
            +
                #   An optional processor object may be supplied.  The object should
         | 
| 276 | 
            +
                #   respond to either the <tt>validate</tt> or <tt>transform</tt> messages
         | 
| 277 | 
            +
                #   or both.  Both the <tt>validate</tt> and <tt>transform</tt> methods
         | 
| 278 | 
            +
                #   should take two parameters: <tt>name</tt> and <tt>value</tt>.  The
         | 
| 279 | 
            +
                #   <tt>validate</tt> method should return <tt>true</tt> or
         | 
| 280 | 
            +
                #   <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
         | 
| 281 | 
            +
                #   <tt>false</tt> otherwise.  An <tt>InvalidTemplateValueError</tt>
         | 
| 282 | 
            +
                #   exception will be raised if the value is invalid.  The
         | 
| 283 | 
            +
                #   <tt>transform</tt> method should return the transformed variable
         | 
| 284 | 
            +
                #   value as a <tt>String</tt>.
         | 
| 285 | 
            +
                #
         | 
| 286 | 
            +
                # @return [Addressable::URI] The expanded URI template.
         | 
| 287 | 
            +
                #
         | 
| 288 | 
            +
                # @example
         | 
| 289 | 
            +
                #   class ExampleProcessor
         | 
| 290 | 
            +
                #     def self.validate(name, value)
         | 
| 291 | 
            +
                #       return !!(value =~ /^[\w ]+$/) if name == "query"
         | 
| 292 | 
            +
                #       return true
         | 
| 293 | 
            +
                #     end
         | 
| 294 | 
            +
                #
         | 
| 295 | 
            +
                #     def self.transform(name, value)
         | 
| 296 | 
            +
                #       return value.gsub(/ /, "+") if name == "query"
         | 
| 297 | 
            +
                #       return value
         | 
| 298 | 
            +
                #     end
         | 
| 299 | 
            +
                #   end
         | 
| 300 | 
            +
                #
         | 
| 301 | 
            +
                #   Addressable::URI.expand_template(
         | 
| 302 | 
            +
                #     "http://example.com/search/{query}/",
         | 
| 303 | 
            +
                #     {"query" => "an example search query"},
         | 
| 304 | 
            +
                #     ExampleProcessor
         | 
| 305 | 
            +
                #   ).to_s
         | 
| 306 | 
            +
                #   #=> "http://example.com/search/an+example+search+query/"
         | 
| 307 | 
            +
                #
         | 
| 308 | 
            +
                #   Addressable::URI.expand_template(
         | 
| 309 | 
            +
                #     "http://example.com/search/{-list|+|query}/",
         | 
| 310 | 
            +
                #     {"query" => "an example search query".split(" ")}
         | 
| 311 | 
            +
                #   ).to_s
         | 
| 312 | 
            +
                #   #=> "http://example.com/search/an+example+search+query/"
         | 
| 313 | 
            +
                #
         | 
| 314 | 
            +
                #   Addressable::URI.expand_template(
         | 
| 315 | 
            +
                #     "http://example.com/search/{query}/",
         | 
| 316 | 
            +
                #     {"query" => "bogus!"},
         | 
| 317 | 
            +
                #     ExampleProcessor
         | 
| 318 | 
            +
                #   ).to_s
         | 
| 319 | 
            +
                #   #=> Addressable::URI::InvalidTemplateValueError
         | 
| 202 320 | 
             
                def self.expand_template(pattern, mapping, processor=nil)
         | 
| 321 | 
            +
             | 
| 322 | 
            +
                  # FIXME: MUST REFACTOR!!!
         | 
| 323 | 
            +
             | 
| 203 324 | 
             
                  result = pattern.dup
         | 
| 204 | 
            -
             | 
| 205 | 
            -
             | 
| 325 | 
            +
             | 
| 326 | 
            +
                  reserved = Addressable::URI::CharacterClasses::RESERVED
         | 
| 327 | 
            +
                  unreserved = Addressable::URI::CharacterClasses::UNRESERVED
         | 
| 328 | 
            +
                  anything = reserved + unreserved
         | 
| 329 | 
            +
                  operator_expansion =
         | 
| 330 | 
            +
                    /\{-([a-zA-Z]+)\|([#{anything}]+)\|([#{anything}]+)\}/
         | 
| 331 | 
            +
                  variable_expansion = /\{([#{anything}]+?)(=([#{anything}]+))?\}/
         | 
| 332 | 
            +
             | 
| 333 | 
            +
                  transformed_mapping = mapping.inject({}) do |accu, pair|
         | 
| 334 | 
            +
                    name, value = pair
         | 
| 335 | 
            +
                    unless value.respond_to?(:to_ary) || value.respond_to?(:to_str)
         | 
| 336 | 
            +
                      raise TypeError,
         | 
| 337 | 
            +
                        "Can't convert #{value.class} into String or Array."
         | 
| 338 | 
            +
                    end
         | 
| 339 | 
            +
                    transformed_value =
         | 
| 340 | 
            +
                      value.respond_to?(:to_ary) ? value.to_ary : value.to_str
         | 
| 341 | 
            +
             | 
| 342 | 
            +
                    # Handle percent escaping, and unicode normalization
         | 
| 343 | 
            +
                    if transformed_value.kind_of?(Array)
         | 
| 344 | 
            +
                      transformed_value.map! do |value|
         | 
| 345 | 
            +
                        self.encode_component(
         | 
| 346 | 
            +
                          Addressable::IDNA.unicode_normalize_kc(value),
         | 
| 347 | 
            +
                          Addressable::URI::CharacterClasses::UNRESERVED
         | 
| 348 | 
            +
                        )
         | 
| 349 | 
            +
                      end
         | 
| 350 | 
            +
                    else
         | 
| 351 | 
            +
                      transformed_value = self.encode_component(
         | 
| 352 | 
            +
                        Addressable::IDNA.unicode_normalize_kc(transformed_value),
         | 
| 353 | 
            +
                        Addressable::URI::CharacterClasses::UNRESERVED
         | 
| 354 | 
            +
                      )
         | 
| 355 | 
            +
                    end
         | 
| 356 | 
            +
             | 
| 357 | 
            +
                    # Process, if we've got a processor
         | 
| 206 358 | 
             
                    if processor != nil
         | 
| 207 359 | 
             
                      if processor.respond_to?(:validate)
         | 
| 208 360 | 
             
                        if !processor.validate(name, value)
         | 
| 209 | 
            -
                           | 
| 210 | 
            -
             | 
| 361 | 
            +
                          display_value = value.kind_of?(Array) ? value.inspect : value
         | 
| 362 | 
            +
                          raise InvalidTemplateValueError,
         | 
| 363 | 
            +
                            "#{name}=#{display_value} is an invalid template value."
         | 
| 211 364 | 
             
                        end
         | 
| 212 365 | 
             
                      end
         | 
| 213 366 | 
             
                      if processor.respond_to?(:transform)
         | 
| @@ -215,229 +368,773 @@ module Addressable | |
| 215 368 | 
             
                      end
         | 
| 216 369 | 
             
                    end
         | 
| 217 370 |  | 
| 218 | 
            -
                     | 
| 219 | 
            -
                     | 
| 220 | 
            -
                      Addressable::URI::CharacterClasses::RESERVED +
         | 
| 221 | 
            -
                      Addressable::URI::CharacterClasses::UNRESERVED)
         | 
| 222 | 
            -
                    
         | 
| 223 | 
            -
                    result.gsub!(/\{#{Regexp.escape(name)}\}/, transformed_value)
         | 
| 371 | 
            +
                    accu[name] = transformed_value
         | 
| 372 | 
            +
                    accu
         | 
| 224 373 | 
             
                  end
         | 
| 225 374 | 
             
                  result.gsub!(
         | 
| 226 | 
            -
                     | 
| 375 | 
            +
                    /#{operator_expansion}|#{variable_expansion}/
         | 
| 376 | 
            +
                  ) do |capture|
         | 
| 377 | 
            +
                    if capture =~ operator_expansion
         | 
| 378 | 
            +
                      operator, argument, variables, default_mapping =
         | 
| 379 | 
            +
                        parse_template_expansion(capture, transformed_mapping)
         | 
| 380 | 
            +
                      expand_method = "expand_#{operator}_operator"
         | 
| 381 | 
            +
                      if ([expand_method, expand_method.to_sym] & private_methods).empty?
         | 
| 382 | 
            +
                        raise InvalidTemplateOperatorError,
         | 
| 383 | 
            +
                          "Invalid template operator: #{operator}"
         | 
| 384 | 
            +
                      else
         | 
| 385 | 
            +
                        send(expand_method.to_sym, argument, variables, default_mapping)
         | 
| 386 | 
            +
                      end
         | 
| 387 | 
            +
                    else
         | 
| 388 | 
            +
                      varname, _, vardefault = capture.scan(/^\{(.+?)(=(.*))?\}$/)[0]
         | 
| 389 | 
            +
                      transformed_mapping[varname] || vardefault
         | 
| 390 | 
            +
                    end
         | 
| 391 | 
            +
                  end
         | 
| 227 392 | 
             
                  return Addressable::URI.parse(result)
         | 
| 228 393 | 
             
                end
         | 
| 229 | 
            -
             | 
| 394 | 
            +
             | 
| 395 | 
            +
                ##
         | 
| 396 | 
            +
                # Expands a URI Template opt operator.
         | 
| 397 | 
            +
                #
         | 
| 398 | 
            +
                # @param [String] argument The argument to the operator.
         | 
| 399 | 
            +
                # @param [Array] variables The variables the operator is working on.
         | 
| 400 | 
            +
                # @param [Hash] mapping The mapping of variables to values.
         | 
| 401 | 
            +
                #
         | 
| 402 | 
            +
                # @return [String] The expanded result.
         | 
| 403 | 
            +
                def self.expand_opt_operator(argument, variables, mapping)
         | 
| 404 | 
            +
                  if (variables.any? do |variable|
         | 
| 405 | 
            +
                    mapping[variable] != [] &&
         | 
| 406 | 
            +
                    mapping[variable]
         | 
| 407 | 
            +
                  end)
         | 
| 408 | 
            +
                    argument
         | 
| 409 | 
            +
                  else
         | 
| 410 | 
            +
                    ""
         | 
| 411 | 
            +
                  end
         | 
| 412 | 
            +
                end
         | 
| 413 | 
            +
                class <<self; private :expand_opt_operator; end
         | 
| 414 | 
            +
             | 
| 415 | 
            +
                ##
         | 
| 416 | 
            +
                # Expands a URI Template neg operator.
         | 
| 417 | 
            +
                #
         | 
| 418 | 
            +
                # @param [String] argument The argument to the operator.
         | 
| 419 | 
            +
                # @param [Array] variables The variables the operator is working on.
         | 
| 420 | 
            +
                # @param [Hash] mapping The mapping of variables to values.
         | 
| 421 | 
            +
                #
         | 
| 422 | 
            +
                # @return [String] The expanded result.
         | 
| 423 | 
            +
                def self.expand_neg_operator(argument, variables, mapping)
         | 
| 424 | 
            +
                  if (variables.any? do |variable|
         | 
| 425 | 
            +
                    mapping[variable] != [] &&
         | 
| 426 | 
            +
                    mapping[variable]
         | 
| 427 | 
            +
                  end)
         | 
| 428 | 
            +
                    ""
         | 
| 429 | 
            +
                  else
         | 
| 430 | 
            +
                    argument
         | 
| 431 | 
            +
                  end
         | 
| 432 | 
            +
                end
         | 
| 433 | 
            +
                class <<self; private :expand_neg_operator; end
         | 
| 434 | 
            +
             | 
| 435 | 
            +
                ##
         | 
| 436 | 
            +
                # Expands a URI Template prefix operator.
         | 
| 437 | 
            +
                #
         | 
| 438 | 
            +
                # @param [String] argument The argument to the operator.
         | 
| 439 | 
            +
                # @param [Array] variables The variables the operator is working on.
         | 
| 440 | 
            +
                # @param [Hash] mapping The mapping of variables to values.
         | 
| 441 | 
            +
                #
         | 
| 442 | 
            +
                # @return [String] The expanded result.
         | 
| 443 | 
            +
                def self.expand_prefix_operator(argument, variables, mapping)
         | 
| 444 | 
            +
                  if variables.size != 1
         | 
| 445 | 
            +
                    raise InvalidTemplateOperatorError,
         | 
| 446 | 
            +
                      "Template operator 'prefix' takes exactly one variable."
         | 
| 447 | 
            +
                  end
         | 
| 448 | 
            +
                  value = mapping[variables.first]
         | 
| 449 | 
            +
                  if value.kind_of?(Array)
         | 
| 450 | 
            +
                    (value.map { |list_value| argument + list_value }).join("")
         | 
| 451 | 
            +
                  else
         | 
| 452 | 
            +
                    argument + value.to_s
         | 
| 453 | 
            +
                  end
         | 
| 454 | 
            +
                end
         | 
| 455 | 
            +
                class <<self; private :expand_prefix_operator; end
         | 
| 456 | 
            +
             | 
| 457 | 
            +
                ##
         | 
| 458 | 
            +
                # Expands a URI Template suffix operator.
         | 
| 459 | 
            +
                #
         | 
| 460 | 
            +
                # @param [String] argument The argument to the operator.
         | 
| 461 | 
            +
                # @param [Array] variables The variables the operator is working on.
         | 
| 462 | 
            +
                # @param [Hash] mapping The mapping of variables to values.
         | 
| 463 | 
            +
                #
         | 
| 464 | 
            +
                # @return [String] The expanded result.
         | 
| 465 | 
            +
                def self.expand_suffix_operator(argument, variables, mapping)
         | 
| 466 | 
            +
                  if variables.size != 1
         | 
| 467 | 
            +
                    raise InvalidTemplateOperatorError,
         | 
| 468 | 
            +
                      "Template operator 'suffix' takes exactly one variable."
         | 
| 469 | 
            +
                  end
         | 
| 470 | 
            +
                  value = mapping[variables.first]
         | 
| 471 | 
            +
                  if value.kind_of?(Array)
         | 
| 472 | 
            +
                    (value.map { |list_value| list_value + argument }).join("")
         | 
| 473 | 
            +
                  else
         | 
| 474 | 
            +
                    value.to_s + argument
         | 
| 475 | 
            +
                  end
         | 
| 476 | 
            +
                end
         | 
| 477 | 
            +
                class <<self; private :expand_suffix_operator; end
         | 
| 478 | 
            +
             | 
| 479 | 
            +
                ##
         | 
| 480 | 
            +
                # Expands a URI Template join operator.
         | 
| 481 | 
            +
                #
         | 
| 482 | 
            +
                # @param [String] argument The argument to the operator.
         | 
| 483 | 
            +
                # @param [Array] variables The variables the operator is working on.
         | 
| 484 | 
            +
                # @param [Hash] mapping The mapping of variables to values.
         | 
| 485 | 
            +
                #
         | 
| 486 | 
            +
                # @return [String] The expanded result.
         | 
| 487 | 
            +
                def self.expand_join_operator(argument, variables, mapping)
         | 
| 488 | 
            +
                  variable_values = variables.inject([]) do |accu, variable|
         | 
| 489 | 
            +
                    if !mapping[variable].kind_of?(Array)
         | 
| 490 | 
            +
                      if mapping[variable]
         | 
| 491 | 
            +
                        accu << variable + "=" + (mapping[variable])
         | 
| 492 | 
            +
                      end
         | 
| 493 | 
            +
                    else
         | 
| 494 | 
            +
                      raise InvalidTemplateOperatorError,
         | 
| 495 | 
            +
                        "Template operator 'join' does not accept Array values."
         | 
| 496 | 
            +
                    end
         | 
| 497 | 
            +
                    accu
         | 
| 498 | 
            +
                  end
         | 
| 499 | 
            +
                  variable_values.join(argument)
         | 
| 500 | 
            +
                end
         | 
| 501 | 
            +
                class <<self; private :expand_join_operator; end
         | 
| 502 | 
            +
             | 
| 503 | 
            +
                ##
         | 
| 504 | 
            +
                # Expands a URI Template list operator.
         | 
| 505 | 
            +
                #
         | 
| 506 | 
            +
                # @param [String] argument The argument to the operator.
         | 
| 507 | 
            +
                # @param [Array] variables The variables the operator is working on.
         | 
| 508 | 
            +
                # @param [Hash] mapping The mapping of variables to values.
         | 
| 509 | 
            +
                #
         | 
| 510 | 
            +
                # @return [String] The expanded result.
         | 
| 511 | 
            +
                def self.expand_list_operator(argument, variables, mapping)
         | 
| 512 | 
            +
                  if variables.size != 1
         | 
| 513 | 
            +
                    raise InvalidTemplateOperatorError,
         | 
| 514 | 
            +
                      "Template operator 'list' takes exactly one variable."
         | 
| 515 | 
            +
                  end
         | 
| 516 | 
            +
                  mapping[variables.first].join(argument)
         | 
| 517 | 
            +
                end
         | 
| 518 | 
            +
                class <<self; private :expand_list_operator; end
         | 
| 519 | 
            +
             | 
| 520 | 
            +
                ##
         | 
| 521 | 
            +
                # Parses a URI template expansion <tt>String</tt>.
         | 
| 522 | 
            +
                #
         | 
| 523 | 
            +
                # @param [String] expansion The operator <tt>String</tt>.
         | 
| 524 | 
            +
                # @param [Hash] mapping The mapping to merge defaults into.
         | 
| 525 | 
            +
                #
         | 
| 526 | 
            +
                # @return [Array]
         | 
| 527 | 
            +
                #   A tuple of the operator, argument, variables, and mapping.
         | 
| 528 | 
            +
                def self.parse_template_expansion(capture, mapping)
         | 
| 529 | 
            +
                  operator, argument, variables = capture[1...-1].split("|")
         | 
| 530 | 
            +
                  operator.gsub!(/^\-/, "")
         | 
| 531 | 
            +
                  variables = variables.split(",")
         | 
| 532 | 
            +
                  mapping = (variables.inject({}) do |accu, var|
         | 
| 533 | 
            +
                    varname, _, vardefault = var.scan(/^(.+?)(=(.*))?$/)[0]
         | 
| 534 | 
            +
                    accu[varname] = vardefault
         | 
| 535 | 
            +
                    accu
         | 
| 536 | 
            +
                  end).merge(mapping)
         | 
| 537 | 
            +
                  variables = variables.map { |var| var.gsub(/=.*$/, "") }
         | 
| 538 | 
            +
                  return operator, argument, variables, mapping
         | 
| 539 | 
            +
                end
         | 
| 540 | 
            +
                class <<self; private :parse_template_expansion; end
         | 
| 541 | 
            +
             | 
| 542 | 
            +
                ##
         | 
| 230 543 | 
             
                # Extracts a mapping from the URI using a URI Template pattern.
         | 
| 231 | 
            -
                # | 
| 232 | 
            -
                #
         | 
| 233 | 
            -
                #  | 
| 234 | 
            -
                #  | 
| 235 | 
            -
                #  | 
| 236 | 
            -
                # | 
| 237 | 
            -
                #  | 
| 238 | 
            -
                #  | 
| 239 | 
            -
                #  | 
| 240 | 
            -
                #  | 
| 241 | 
            -
                #
         | 
| 242 | 
            -
                #  | 
| 243 | 
            -
                # | 
| 244 | 
            -
                #   | 
| 245 | 
            -
                # | 
| 246 | 
            -
                # | 
| 247 | 
            -
                # | 
| 248 | 
            -
                # | 
| 249 | 
            -
                #
         | 
| 250 | 
            -
                # | 
| 251 | 
            -
                # | 
| 252 | 
            -
                # | 
| 253 | 
            -
                # | 
| 254 | 
            -
                # | 
| 255 | 
            -
                # | 
| 256 | 
            -
                # | 
| 257 | 
            -
                # | 
| 258 | 
            -
                # | 
| 259 | 
            -
                # | 
| 260 | 
            -
                # | 
| 261 | 
            -
                # | 
| 262 | 
            -
                # | 
| 263 | 
            -
                # | 
| 264 | 
            -
                # | 
| 265 | 
            -
                # | 
| 266 | 
            -
                # | 
| 544 | 
            +
                #
         | 
| 545 | 
            +
                # @param [String] pattern
         | 
| 546 | 
            +
                #   A URI template pattern.
         | 
| 547 | 
            +
                # @param [#restore, #match] processor
         | 
| 548 | 
            +
                #   A template processor object may optionally be supplied.
         | 
| 549 | 
            +
                #   The object should respond to either the <tt>restore</tt> or
         | 
| 550 | 
            +
                #   <tt>match</tt> messages or both.  The <tt>restore</tt> method should
         | 
| 551 | 
            +
                #   take two parameters: [String] name and [String] value.  The
         | 
| 552 | 
            +
                #   <tt>restore</tt> method should reverse any transformations that have
         | 
| 553 | 
            +
                #   been performed on the value to ensure a valid URI.  The
         | 
| 554 | 
            +
                #   <tt>match</tt> method should take a single parameter: [String] name.
         | 
| 555 | 
            +
                #   The <tt>match</tt> method should return a <tt>String</tt> containing
         | 
| 556 | 
            +
                #   a regular expression capture group for matching on that particular
         | 
| 557 | 
            +
                #   variable.  The default value is ".*?".  The <tt>match</tt> method has
         | 
| 558 | 
            +
                #   no effect on multivariate operator expansions.
         | 
| 559 | 
            +
                # @return [Hash, NilClass]
         | 
| 560 | 
            +
                #   The <tt>Hash</tt> mapping that was extracted from the URI, or
         | 
| 561 | 
            +
                #   <tt>nil</tt> if the URI didn't match the template.
         | 
| 562 | 
            +
                #
         | 
| 563 | 
            +
                # @example
         | 
| 564 | 
            +
                #   class ExampleProcessor
         | 
| 565 | 
            +
                #     def self.restore(name, value)
         | 
| 566 | 
            +
                #       return value.gsub(/\+/, " ") if name == "query"
         | 
| 567 | 
            +
                #       return value
         | 
| 568 | 
            +
                #     end
         | 
| 569 | 
            +
                #
         | 
| 570 | 
            +
                #     def self.match(name)
         | 
| 571 | 
            +
                #       return ".*?" if name == "first"
         | 
| 572 | 
            +
                #       return ".*"
         | 
| 573 | 
            +
                #     end
         | 
| 574 | 
            +
                #   end
         | 
| 575 | 
            +
                #
         | 
| 576 | 
            +
                #   uri = Addressable::URI.parse(
         | 
| 577 | 
            +
                #     "http://example.com/search/an+example+search+query/"
         | 
| 578 | 
            +
                #   )
         | 
| 579 | 
            +
                #   uri.extract_mapping(
         | 
| 580 | 
            +
                #     "http://example.com/search/{query}/",
         | 
| 581 | 
            +
                #     ExampleProcessor
         | 
| 582 | 
            +
                #   )
         | 
| 583 | 
            +
                #   #=> {"query" => "an example search query"}
         | 
| 584 | 
            +
                #
         | 
| 585 | 
            +
                #   uri = Addressable::URI.parse("http://example.com/a/b/c/")
         | 
| 586 | 
            +
                #   uri.extract_mapping(
         | 
| 587 | 
            +
                #     "http://example.com/{first}/{second}/",
         | 
| 588 | 
            +
                #     ExampleProcessor
         | 
| 589 | 
            +
                #   )
         | 
| 590 | 
            +
                #   #=> {"first" => "a", "second" => "b/c"}
         | 
| 591 | 
            +
                #
         | 
| 592 | 
            +
                #   uri = Addressable::URI.parse("http://example.com/a/b/c/")
         | 
| 593 | 
            +
                #   uri.extract_mapping(
         | 
| 594 | 
            +
                #     "http://example.com/{first}/{-list|/|second}/"
         | 
| 595 | 
            +
                #   )
         | 
| 596 | 
            +
                #   #=> {"first" => "a", "second" => ["b", "c"]}
         | 
| 267 597 | 
             
                def extract_mapping(pattern, processor=nil)
         | 
| 598 | 
            +
                  reserved = Addressable::URI::CharacterClasses::RESERVED
         | 
| 599 | 
            +
                  unreserved = Addressable::URI::CharacterClasses::UNRESERVED
         | 
| 600 | 
            +
                  anything = reserved + unreserved
         | 
| 601 | 
            +
                  operator_expansion =
         | 
| 602 | 
            +
                    /\{-([a-zA-Z]+)\|([#{anything}]+)\|([#{anything}]+)\}/
         | 
| 603 | 
            +
                  variable_expansion = /\{([#{anything}]+?)(=([#{anything}]+))?\}/
         | 
| 604 | 
            +
             | 
| 605 | 
            +
                  # First, we need to process the pattern, and extract the values.
         | 
| 606 | 
            +
                  expansions, expansion_regexp =
         | 
| 607 | 
            +
                    parse_template_pattern(pattern, processor)
         | 
| 608 | 
            +
                  unparsed_values = self.to_s.scan(expansion_regexp).flatten
         | 
| 609 | 
            +
             | 
| 268 610 | 
             
                  mapping = {}
         | 
| 269 | 
            -
             | 
| 270 | 
            -
             | 
| 271 | 
            -
             | 
| 272 | 
            -
                   | 
| 273 | 
            -
             | 
| 274 | 
            -
             | 
| 275 | 
            -
             | 
| 276 | 
            -
             | 
| 277 | 
            -
             | 
| 278 | 
            -
             | 
| 279 | 
            -
             | 
| 280 | 
            -
             | 
| 281 | 
            -
             | 
| 611 | 
            +
             | 
| 612 | 
            +
                  if self.to_s == pattern
         | 
| 613 | 
            +
                    return mapping
         | 
| 614 | 
            +
                  elsif expansions.size > 0 && expansions.size == unparsed_values.size
         | 
| 615 | 
            +
                    expansions.each_with_index do |expansion, index|
         | 
| 616 | 
            +
                      unparsed_value = unparsed_values[index]
         | 
| 617 | 
            +
                      if expansion =~ operator_expansion
         | 
| 618 | 
            +
                        operator, argument, variables =
         | 
| 619 | 
            +
                          parse_template_expansion(expansion)
         | 
| 620 | 
            +
                        extract_method = "extract_#{operator}_operator"
         | 
| 621 | 
            +
                        if ([extract_method, extract_method.to_sym] &
         | 
| 622 | 
            +
                            private_methods).empty?
         | 
| 623 | 
            +
                          raise InvalidTemplateOperatorError,
         | 
| 624 | 
            +
                            "Invalid template operator: #{operator}"
         | 
| 625 | 
            +
                        else
         | 
| 626 | 
            +
                          begin
         | 
| 627 | 
            +
                            send(
         | 
| 628 | 
            +
                              extract_method.to_sym, unparsed_value, processor,
         | 
| 629 | 
            +
                              argument, variables, mapping
         | 
| 630 | 
            +
                            )
         | 
| 631 | 
            +
                          rescue TemplateOperatorAbortedError
         | 
| 632 | 
            +
                            return nil
         | 
| 633 | 
            +
                          end
         | 
| 634 | 
            +
                        end
         | 
| 635 | 
            +
                      else
         | 
| 636 | 
            +
                        name = expansion[variable_expansion, 1]
         | 
| 637 | 
            +
                        value = unparsed_value
         | 
| 638 | 
            +
                        if processor != nil && processor.respond_to?(:restore)
         | 
| 639 | 
            +
                          value = processor.restore(name, value)
         | 
| 640 | 
            +
                        end
         | 
| 641 | 
            +
                        mapping[name] = value
         | 
| 642 | 
            +
                      end
         | 
| 643 | 
            +
                    end
         | 
| 644 | 
            +
                    return mapping
         | 
| 645 | 
            +
                  else
         | 
| 646 | 
            +
                    return nil
         | 
| 647 | 
            +
                  end
         | 
| 648 | 
            +
                end
         | 
| 649 | 
            +
             | 
| 650 | 
            +
                ##
         | 
| 651 | 
            +
                # Generates the <tt>Regexp</tt> that parses a template pattern.
         | 
| 652 | 
            +
                #
         | 
| 653 | 
            +
                # @param [String] pattern The URI template pattern.
         | 
| 654 | 
            +
                # @param [#match] processor The template processor to use.
         | 
| 655 | 
            +
                #
         | 
| 656 | 
            +
                # @return [Regexp]
         | 
| 657 | 
            +
                #   A regular expression which may be used to parse a template pattern.
         | 
| 658 | 
            +
                def parse_template_pattern(pattern, processor)
         | 
| 659 | 
            +
                  reserved = Addressable::URI::CharacterClasses::RESERVED
         | 
| 660 | 
            +
                  unreserved = Addressable::URI::CharacterClasses::UNRESERVED
         | 
| 661 | 
            +
                  anything = reserved + unreserved
         | 
| 662 | 
            +
                  operator_expansion =
         | 
| 663 | 
            +
                    /\{-[a-zA-Z]+\|[#{anything}]+\|[#{anything}]+\}/
         | 
| 664 | 
            +
                  variable_expansion = /\{([#{anything}]+?)(=([#{anything}]+))?\}/
         | 
| 665 | 
            +
             | 
| 666 | 
            +
                  # Escape the pattern.  The two gsubs restore the escaped curly braces
         | 
| 667 | 
            +
                  # back to their original form.  Basically, escape everything that isn't
         | 
| 668 | 
            +
                  # within an expansion.
         | 
| 669 | 
            +
                  escaped_pattern = Regexp.escape(
         | 
| 670 | 
            +
                    pattern
         | 
| 671 | 
            +
                  ).gsub(/\\\{(.*?)\\\}/) do |escaped|
         | 
| 672 | 
            +
                    escaped.gsub(/\\(.)/, "\\1")
         | 
| 673 | 
            +
                  end
         | 
| 674 | 
            +
             | 
| 675 | 
            +
                  expansions = []
         | 
| 676 | 
            +
             | 
| 282 677 | 
             
                  # Create a regular expression that captures the values of the
         | 
| 283 678 | 
             
                  # variables in the URI.
         | 
| 284 | 
            -
                  regexp_string = escaped_pattern.gsub( | 
| 285 | 
            -
                     | 
| 286 | 
            -
             | 
| 287 | 
            -
                     | 
| 288 | 
            -
             | 
| 289 | 
            -
             | 
| 679 | 
            +
                  regexp_string = escaped_pattern.gsub(
         | 
| 680 | 
            +
                    /#{operator_expansion}|#{variable_expansion}/
         | 
| 681 | 
            +
                  ) do |expansion|
         | 
| 682 | 
            +
                    expansions << expansion
         | 
| 683 | 
            +
                    if expansion =~ operator_expansion
         | 
| 684 | 
            +
                      capture_group = "(.*)"
         | 
| 685 | 
            +
                      if processor != nil && processor.respond_to?(:match)
         | 
| 686 | 
            +
                        # We can only lookup the match values for single variable
         | 
| 687 | 
            +
                        # operator expansions.  Besides, ".*" is usually the only
         | 
| 688 | 
            +
                        # reasonable value for multivariate operators anyways.
         | 
| 689 | 
            +
                        operator, _, names, _ =
         | 
| 690 | 
            +
                          parse_template_expansion(expansion)
         | 
| 691 | 
            +
                        if ["prefix", "suffix", "list"].include?(operator)
         | 
| 692 | 
            +
                          capture_group = "(#{processor.match(names.first)})"
         | 
| 693 | 
            +
                        end
         | 
| 694 | 
            +
                      end
         | 
| 695 | 
            +
                      capture_group
         | 
| 696 | 
            +
                    else
         | 
| 697 | 
            +
                      capture_group = "(.*?)"
         | 
| 698 | 
            +
                      if processor != nil && processor.respond_to?(:match)
         | 
| 699 | 
            +
                        name = expansion[/\{([^\}=]+)(=[^\}]+)?\}/, 1]
         | 
| 290 700 | 
             
                        capture_group = "(#{processor.match(name)})"
         | 
| 291 701 | 
             
                      end
         | 
| 702 | 
            +
                      capture_group
         | 
| 292 703 | 
             
                    end
         | 
| 293 | 
            -
                    
         | 
| 294 | 
            -
                    capture_group
         | 
| 295 704 | 
             
                  end
         | 
| 296 | 
            -
             | 
| 705 | 
            +
             | 
| 297 706 | 
             
                  # Ensure that the regular expression matches the whole URI.
         | 
| 298 707 | 
             
                  regexp_string = "^#{regexp_string}$"
         | 
| 299 | 
            -
             | 
| 300 | 
            -
                   | 
| 301 | 
            -
             | 
| 302 | 
            -
             | 
| 303 | 
            -
             | 
| 304 | 
            -
             | 
| 305 | 
            -
             | 
| 306 | 
            -
             | 
| 307 | 
            -
             | 
| 308 | 
            -
             | 
| 309 | 
            -
             | 
| 310 | 
            -
             | 
| 311 | 
            -
             | 
| 312 | 
            -
             | 
| 313 | 
            -
             | 
| 314 | 
            -
             | 
| 315 | 
            -
             | 
| 708 | 
            +
             | 
| 709 | 
            +
                  return expansions, Regexp.new(regexp_string)
         | 
| 710 | 
            +
                end
         | 
| 711 | 
            +
                private :parse_template_pattern
         | 
| 712 | 
            +
             | 
| 713 | 
            +
                ##
         | 
| 714 | 
            +
                # Parses a URI template expansion <tt>String</tt>.
         | 
| 715 | 
            +
                #
         | 
| 716 | 
            +
                # @param [String] expansion The operator <tt>String</tt>.
         | 
| 717 | 
            +
                #
         | 
| 718 | 
            +
                # @return [Array]
         | 
| 719 | 
            +
                #   A tuple of the operator, argument, variables.
         | 
| 720 | 
            +
                def parse_template_expansion(capture)
         | 
| 721 | 
            +
                  operator, argument, variables = capture[1...-1].split("|")
         | 
| 722 | 
            +
                  operator.gsub!(/^\-/, "")
         | 
| 723 | 
            +
                  variables = variables.split(",").map { |var| var.gsub(/=.*$/, "") }
         | 
| 724 | 
            +
                  return operator, argument, variables
         | 
| 725 | 
            +
                end
         | 
| 726 | 
            +
                private :parse_template_expansion
         | 
| 727 | 
            +
             | 
| 728 | 
            +
             | 
| 729 | 
            +
                ##
         | 
| 730 | 
            +
                # Extracts a URI Template opt operator.
         | 
| 731 | 
            +
                #
         | 
| 732 | 
            +
                # @param [String] value The unparsed value to extract from.
         | 
| 733 | 
            +
                # @param [#restore] processor The processor object.
         | 
| 734 | 
            +
                # @param [String] argument The argument to the operator.
         | 
| 735 | 
            +
                # @param [Array] variables The variables the operator is working on.
         | 
| 736 | 
            +
                # @param [Hash] mapping The mapping of variables to values.
         | 
| 737 | 
            +
                #
         | 
| 738 | 
            +
                # @return [String] The extracted result.
         | 
| 739 | 
            +
                def extract_opt_operator(
         | 
| 740 | 
            +
                    value, processor, argument, variables, mapping)
         | 
| 741 | 
            +
                  if value != "" && value != argument
         | 
| 742 | 
            +
                    raise TemplateOperatorAbortedError,
         | 
| 743 | 
            +
                      "Value for template operator 'neg' was unexpected."
         | 
| 744 | 
            +
                  end
         | 
| 745 | 
            +
                end
         | 
| 746 | 
            +
                private :extract_opt_operator
         | 
| 747 | 
            +
             | 
| 748 | 
            +
                ##
         | 
| 749 | 
            +
                # Extracts a URI Template neg operator.
         | 
| 750 | 
            +
                #
         | 
| 751 | 
            +
                # @param [String] value The unparsed value to extract from.
         | 
| 752 | 
            +
                # @param [#restore] processor The processor object.
         | 
| 753 | 
            +
                # @param [String] argument The argument to the operator.
         | 
| 754 | 
            +
                # @param [Array] variables The variables the operator is working on.
         | 
| 755 | 
            +
                # @param [Hash] mapping The mapping of variables to values.
         | 
| 756 | 
            +
                #
         | 
| 757 | 
            +
                # @return [String] The extracted result.
         | 
| 758 | 
            +
                def extract_neg_operator(
         | 
| 759 | 
            +
                    value, processor, argument, variables, mapping)
         | 
| 760 | 
            +
                  if value != "" && value != argument
         | 
| 761 | 
            +
                    raise TemplateOperatorAbortedError,
         | 
| 762 | 
            +
                      "Value for template operator 'neg' was unexpected."
         | 
| 763 | 
            +
                  end
         | 
| 764 | 
            +
                end
         | 
| 765 | 
            +
                private :extract_neg_operator
         | 
| 766 | 
            +
             | 
| 767 | 
            +
                ##
         | 
| 768 | 
            +
                # Extracts a URI Template prefix operator.
         | 
| 769 | 
            +
                #
         | 
| 770 | 
            +
                # @param [String] value The unparsed value to extract from.
         | 
| 771 | 
            +
                # @param [#restore] processor The processor object.
         | 
| 772 | 
            +
                # @param [String] argument The argument to the operator.
         | 
| 773 | 
            +
                # @param [Array] variables The variables the operator is working on.
         | 
| 774 | 
            +
                # @param [Hash] mapping The mapping of variables to values.
         | 
| 775 | 
            +
                #
         | 
| 776 | 
            +
                # @return [String] The extracted result.
         | 
| 777 | 
            +
                def extract_prefix_operator(
         | 
| 778 | 
            +
                    value, processor, argument, variables, mapping)
         | 
| 779 | 
            +
                  if variables.size != 1
         | 
| 780 | 
            +
                    raise InvalidTemplateOperatorError,
         | 
| 781 | 
            +
                      "Template operator 'suffix' takes exactly one variable."
         | 
| 782 | 
            +
                  end
         | 
| 783 | 
            +
                  if value[0...argument.size] != argument
         | 
| 784 | 
            +
                    raise TemplateOperatorAbortedError,
         | 
| 785 | 
            +
                      "Value for template operator 'prefix' missing expected prefix."
         | 
| 786 | 
            +
                  end
         | 
| 787 | 
            +
                  values = value.split(argument)
         | 
| 788 | 
            +
                  # Compensate for the crappy result from split.
         | 
| 789 | 
            +
                  if value[-argument.size..-1] == argument
         | 
| 790 | 
            +
                    values << ""
         | 
| 791 | 
            +
                  end
         | 
| 792 | 
            +
                  if values[0] == ""
         | 
| 793 | 
            +
                    values.shift
         | 
| 794 | 
            +
                  end
         | 
| 795 | 
            +
                  if processor && processor.respond_to?(:restore)
         | 
| 796 | 
            +
                    values.map! { |value| processor.restore(variables.first, value) }
         | 
| 797 | 
            +
                  end
         | 
| 798 | 
            +
                  mapping[variables.first] = values
         | 
| 799 | 
            +
                end
         | 
| 800 | 
            +
                private :extract_prefix_operator
         | 
| 801 | 
            +
             | 
| 802 | 
            +
                ##
         | 
| 803 | 
            +
                # Extracts a URI Template suffix operator.
         | 
| 804 | 
            +
                #
         | 
| 805 | 
            +
                # @param [String] value The unparsed value to extract from.
         | 
| 806 | 
            +
                # @param [#restore] processor The processor object.
         | 
| 807 | 
            +
                # @param [String] argument The argument to the operator.
         | 
| 808 | 
            +
                # @param [Array] variables The variables the operator is working on.
         | 
| 809 | 
            +
                # @param [Hash] mapping The mapping of variables to values.
         | 
| 810 | 
            +
                #
         | 
| 811 | 
            +
                # @return [String] The extracted result.
         | 
| 812 | 
            +
                def extract_suffix_operator(
         | 
| 813 | 
            +
                    value, processor, argument, variables, mapping)
         | 
| 814 | 
            +
                  if variables.size != 1
         | 
| 815 | 
            +
                    raise InvalidTemplateOperatorError,
         | 
| 816 | 
            +
                      "Template operator 'suffix' takes exactly one variable."
         | 
| 817 | 
            +
                  end
         | 
| 818 | 
            +
                  if value[-argument.size..-1] != argument
         | 
| 819 | 
            +
                    raise TemplateOperatorAbortedError,
         | 
| 820 | 
            +
                      "Value for template operator 'suffix' missing expected suffix."
         | 
| 821 | 
            +
                  end
         | 
| 822 | 
            +
                  values = value.split(argument)
         | 
| 823 | 
            +
                  # Compensate for the crappy result from split.
         | 
| 824 | 
            +
                  if value[-argument.size..-1] == argument
         | 
| 825 | 
            +
                    values << ""
         | 
| 826 | 
            +
                  end
         | 
| 827 | 
            +
                  if values[-1] == ""
         | 
| 828 | 
            +
                    values.pop
         | 
| 829 | 
            +
                  end
         | 
| 830 | 
            +
                  if processor && processor.respond_to?(:restore)
         | 
| 831 | 
            +
                    values.map! { |value| processor.restore(variables.first, value) }
         | 
| 832 | 
            +
                  end
         | 
| 833 | 
            +
                  mapping[variables.first] = values
         | 
| 834 | 
            +
                end
         | 
| 835 | 
            +
                private :extract_suffix_operator
         | 
| 836 | 
            +
             | 
| 837 | 
            +
                ##
         | 
| 838 | 
            +
                # Extracts a URI Template join operator.
         | 
| 839 | 
            +
                #
         | 
| 840 | 
            +
                # @param [String] value The unparsed value to extract from.
         | 
| 841 | 
            +
                # @param [#restore] processor The processor object.
         | 
| 842 | 
            +
                # @param [String] argument The argument to the operator.
         | 
| 843 | 
            +
                # @param [Array] variables The variables the operator is working on.
         | 
| 844 | 
            +
                # @param [Hash] mapping The mapping of variables to values.
         | 
| 845 | 
            +
                #
         | 
| 846 | 
            +
                # @return [String] The extracted result.
         | 
| 847 | 
            +
                def extract_join_operator(value, processor, argument, variables, mapping)
         | 
| 848 | 
            +
                  unparsed_values = value.split(argument)
         | 
| 849 | 
            +
                  parsed_variables = []
         | 
| 850 | 
            +
                  for unparsed_value in unparsed_values
         | 
| 851 | 
            +
                    name = unparsed_value[/^(.+?)=(.+)$/, 1]
         | 
| 852 | 
            +
                    parsed_variables << name
         | 
| 853 | 
            +
                    parsed_value = unparsed_value[/^(.+?)=(.+)$/, 2]
         | 
| 854 | 
            +
                    if processor && processor.respond_to?(:restore)
         | 
| 855 | 
            +
                      parsed_value = processor.restore(name, parsed_value)
         | 
| 316 856 | 
             
                    end
         | 
| 317 | 
            -
                     | 
| 318 | 
            -
                   | 
| 319 | 
            -
             | 
| 320 | 
            -
                     | 
| 321 | 
            -
             | 
| 322 | 
            -
             | 
| 323 | 
            -
                    return nil
         | 
| 857 | 
            +
                    mapping[name] = parsed_value
         | 
| 858 | 
            +
                  end
         | 
| 859 | 
            +
                  if (parsed_variables & variables) != parsed_variables
         | 
| 860 | 
            +
                    raise TemplateOperatorAbortedError,
         | 
| 861 | 
            +
                      "Template operator 'join' variable mismatch: " +
         | 
| 862 | 
            +
                      "#{parsed_variables.inspect}, #{variables.inspect}"
         | 
| 324 863 | 
             
                  end
         | 
| 325 864 | 
             
                end
         | 
| 326 | 
            -
                
         | 
| 327 | 
            -
             | 
| 865 | 
            +
                private :extract_join_operator
         | 
| 866 | 
            +
             | 
| 867 | 
            +
                ##
         | 
| 868 | 
            +
                # Extracts a URI Template list operator.
         | 
| 869 | 
            +
                #
         | 
| 870 | 
            +
                # @param [String] value The unparsed value to extract from.
         | 
| 871 | 
            +
                # @param [#restore] processor The processor object.
         | 
| 872 | 
            +
                # @param [String] argument The argument to the operator.
         | 
| 873 | 
            +
                # @param [Array] variables The variables the operator is working on.
         | 
| 874 | 
            +
                # @param [Hash] mapping The mapping of variables to values.
         | 
| 875 | 
            +
                #
         | 
| 876 | 
            +
                # @return [String] The extracted result.
         | 
| 877 | 
            +
                def extract_list_operator(value, processor, argument, variables, mapping)
         | 
| 878 | 
            +
                  if variables.size != 1
         | 
| 879 | 
            +
                    raise InvalidTemplateOperatorError,
         | 
| 880 | 
            +
                      "Template operator 'list' takes exactly one variable."
         | 
| 881 | 
            +
                  end
         | 
| 882 | 
            +
                  values = value.split(argument)
         | 
| 883 | 
            +
                  if processor && processor.respond_to?(:restore)
         | 
| 884 | 
            +
                    values.map! { |value| processor.restore(variables.first, value) }
         | 
| 885 | 
            +
                  end
         | 
| 886 | 
            +
                  mapping[variables.first] = values
         | 
| 887 | 
            +
                end
         | 
| 888 | 
            +
                private :extract_list_operator
         | 
| 889 | 
            +
             | 
| 890 | 
            +
                ##
         | 
| 891 | 
            +
                # Joins several URIs together.
         | 
| 892 | 
            +
                #
         | 
| 893 | 
            +
                # @param [String, Addressable::URI, #to_str] *uris
         | 
| 894 | 
            +
                #   The URIs to join.
         | 
| 895 | 
            +
                #
         | 
| 896 | 
            +
                # @return [Addressable::URI] The joined URI.
         | 
| 897 | 
            +
                #
         | 
| 898 | 
            +
                # @example
         | 
| 899 | 
            +
                #   base = "http://example.com/"
         | 
| 900 | 
            +
                #   uri = Addressable::URI.parse("relative/path")
         | 
| 901 | 
            +
                #   Addressable::URI.join(base, uri)
         | 
| 902 | 
            +
                #   #=> #<Addressable::URI:0xcab390 URI:http://example.com/relative/path>
         | 
| 328 903 | 
             
                def self.join(*uris)
         | 
| 329 904 | 
             
                  uri_objects = uris.collect do |uri|
         | 
| 330 | 
            -
                    uri. | 
| 905 | 
            +
                    if !uri.respond_to?(:to_str)
         | 
| 906 | 
            +
                      raise TypeError, "Can't convert #{uri.class} into String."
         | 
| 907 | 
            +
                    end
         | 
| 908 | 
            +
                    uri.kind_of?(self) ? uri : self.parse(uri.to_str)
         | 
| 331 909 | 
             
                  end
         | 
| 332 910 | 
             
                  result = uri_objects.shift.dup
         | 
| 333 911 | 
             
                  for uri in uri_objects
         | 
| 334 | 
            -
                    result. | 
| 912 | 
            +
                    result.join!(uri)
         | 
| 335 913 | 
             
                  end
         | 
| 336 914 | 
             
                  return result
         | 
| 337 915 | 
             
                end
         | 
| 338 | 
            -
             | 
| 339 | 
            -
                 | 
| 340 | 
            -
                #  | 
| 341 | 
            -
                # | 
| 342 | 
            -
                #  | 
| 343 | 
            -
                # | 
| 344 | 
            -
                #  | 
| 345 | 
            -
                #  | 
| 346 | 
            -
                #
         | 
| 347 | 
            -
                #  | 
| 348 | 
            -
                #
         | 
| 349 | 
            -
                # | 
| 350 | 
            -
                #   | 
| 351 | 
            -
                 | 
| 352 | 
            -
             | 
| 353 | 
            -
             | 
| 354 | 
            -
             | 
| 355 | 
            -
             | 
| 356 | 
            -
             | 
| 357 | 
            -
             | 
| 358 | 
            -
             | 
| 359 | 
            -
             | 
| 360 | 
            -
                 | 
| 361 | 
            -
                
         | 
| 362 | 
            -
                # | 
| 363 | 
            -
                #  | 
| 364 | 
            -
                 | 
| 365 | 
            -
             | 
| 366 | 
            -
             | 
| 916 | 
            +
             | 
| 917 | 
            +
                ##
         | 
| 918 | 
            +
                # Percent encodes a URI component.
         | 
| 919 | 
            +
                #
         | 
| 920 | 
            +
                # @param [String, #to_str] component The URI component to encode.
         | 
| 921 | 
            +
                #
         | 
| 922 | 
            +
                # @param [String, Regexp] character_class
         | 
| 923 | 
            +
                #   The characters which are not percent encoded.  If a <tt>String</tt>
         | 
| 924 | 
            +
                #   is passed, the <tt>String</tt> must be formatted as a regular
         | 
| 925 | 
            +
                #   expression character class.  (Do not include the surrounding square
         | 
| 926 | 
            +
                #   brackets.)  For example, <tt>"b-zB-Z0-9"</tt> would cause everything
         | 
| 927 | 
            +
                #   but the letters 'b' through 'z' and the numbers '0' through '9' to be
         | 
| 928 | 
            +
                #   percent encoded.  If a <tt>Regexp</tt> is passed, the value
         | 
| 929 | 
            +
                #   <tt>/[^b-zB-Z0-9]/</tt> would have the same effect.
         | 
| 930 | 
            +
                #   A set of useful <tt>String</tt> values may be found in the
         | 
| 931 | 
            +
                #   <tt>Addressable::URI::CharacterClasses</tt> module.  The default value
         | 
| 932 | 
            +
                #   is the reserved plus unreserved character classes specified in
         | 
| 933 | 
            +
                #   <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
         | 
| 934 | 
            +
                #
         | 
| 935 | 
            +
                # @return [String] The encoded component.
         | 
| 936 | 
            +
                #
         | 
| 937 | 
            +
                # @example
         | 
| 938 | 
            +
                #   Addressable::URI.encode_component("simple/example", "b-zB-Z0-9")
         | 
| 939 | 
            +
                #   => "simple%2Fex%61mple"
         | 
| 940 | 
            +
                #   Addressable::URI.encode_component("simple/example", /[^b-zB-Z0-9]/)
         | 
| 941 | 
            +
                #   => "simple%2Fex%61mple"
         | 
| 942 | 
            +
                #   Addressable::URI.encode_component(
         | 
| 943 | 
            +
                #     "simple/example", Addressable::URI::CharacterClasses::UNRESERVED
         | 
| 944 | 
            +
                #   )
         | 
| 945 | 
            +
                #   => "simple%2Fexample"
         | 
| 946 | 
            +
                def self.encode_component(component, character_class=
         | 
| 947 | 
            +
                    CharacterClasses::RESERVED + CharacterClasses::UNRESERVED)
         | 
| 948 | 
            +
                  return nil if component.nil?
         | 
| 949 | 
            +
                  if !component.respond_to?(:to_str)
         | 
| 950 | 
            +
                    raise TypeError, "Can't convert #{component.class} into String."
         | 
| 951 | 
            +
                  end
         | 
| 952 | 
            +
                  component = component.to_str
         | 
| 953 | 
            +
                  if ![String, Regexp].include?(character_class.class)
         | 
| 954 | 
            +
                    raise TypeError,
         | 
| 955 | 
            +
                      "Expected String or Regexp, got #{character_class.inspect}"
         | 
| 956 | 
            +
                  end
         | 
| 957 | 
            +
                  if character_class.kind_of?(String)
         | 
| 958 | 
            +
                    character_class = /[^#{character_class}]/
         | 
| 959 | 
            +
                  end
         | 
| 960 | 
            +
                  return component.gsub(character_class) do |sequence|
         | 
| 961 | 
            +
                    (sequence.unpack('C*').map { |c| "%#{c.to_s(16).upcase}" }).join("")
         | 
| 962 | 
            +
                  end
         | 
| 963 | 
            +
                end
         | 
| 964 | 
            +
             | 
| 965 | 
            +
                class << self
         | 
| 966 | 
            +
                  alias_method :encode_component, :encode_component
         | 
| 967 | 
            +
                end
         | 
| 968 | 
            +
             | 
| 969 | 
            +
                ##
         | 
| 970 | 
            +
                # Unencodes any percent encoded characters within a URI component.
         | 
| 971 | 
            +
                # This method may be used for unencoding either components or full URIs,
         | 
| 972 | 
            +
                # however, it is recommended to use the <tt>unencode_component</tt> alias
         | 
| 973 | 
            +
                # when unencoding components.
         | 
| 974 | 
            +
                #
         | 
| 975 | 
            +
                # @param [String, Addressable::URI, #to_str] uri
         | 
| 976 | 
            +
                #   The URI or component to unencode.
         | 
| 977 | 
            +
                #
         | 
| 978 | 
            +
                # @param [Class] returning
         | 
| 979 | 
            +
                #   The type of object to return.  This value may only be set to
         | 
| 980 | 
            +
                #   <tt>String</tt> or <tt>Addressable::URI</tt>.  All other values
         | 
| 981 | 
            +
                #   are invalid.  Defaults to <tt>String</tt>.
         | 
| 982 | 
            +
                #
         | 
| 983 | 
            +
                # @return [String, Addressable::URI]
         | 
| 984 | 
            +
                #   The unencoded component or URI.  The return type is determined by
         | 
| 985 | 
            +
                #   the <tt>returning</tt> parameter.
         | 
| 986 | 
            +
                def self.unencode(uri, returning=String)
         | 
| 987 | 
            +
                  return nil if uri.nil?
         | 
| 988 | 
            +
                  if !uri.respond_to?(:to_str)
         | 
| 989 | 
            +
                    raise TypeError, "Can't convert #{uri.class} into String."
         | 
| 990 | 
            +
                  end
         | 
| 991 | 
            +
                  if ![String, ::Addressable::URI].include?(returning)
         | 
| 992 | 
            +
                    raise TypeError,
         | 
| 993 | 
            +
                      "Expected String or Addressable::URI, got #{returning.inspect}"
         | 
| 994 | 
            +
                  end
         | 
| 995 | 
            +
                  result = uri.to_str.gsub(/%[0-9a-f]{2}/i) do |sequence|
         | 
| 367 996 | 
             
                    sequence[1..3].to_i(16).chr
         | 
| 368 997 | 
             
                  end
         | 
| 998 | 
            +
                  result.force_encoding("utf-8") if result.respond_to?(:force_encoding)
         | 
| 999 | 
            +
                  if returning == String
         | 
| 1000 | 
            +
                    return result
         | 
| 1001 | 
            +
                  elsif returning == ::Addressable::URI
         | 
| 1002 | 
            +
                    return ::Addressable::URI.parse(result)
         | 
| 1003 | 
            +
                  end
         | 
| 369 1004 | 
             
                end
         | 
| 370 | 
            -
             | 
| 371 | 
            -
                 | 
| 372 | 
            -
             | 
| 373 | 
            -
             | 
| 374 | 
            -
                   | 
| 375 | 
            -
             | 
| 376 | 
            -
             | 
| 1005 | 
            +
             | 
| 1006 | 
            +
                class << self
         | 
| 1007 | 
            +
                  alias_method :unescape, :unencode
         | 
| 1008 | 
            +
                  alias_method :unencode_component, :unencode
         | 
| 1009 | 
            +
                  alias_method :unescape_component, :unencode
         | 
| 1010 | 
            +
                end
         | 
| 1011 | 
            +
             | 
| 1012 | 
            +
                ##
         | 
| 1013 | 
            +
                # Percent encodes any special characters in the URI.
         | 
| 1014 | 
            +
                #
         | 
| 1015 | 
            +
                # @param [String, Addressable::URI, #to_str] uri
         | 
| 1016 | 
            +
                #   The URI to encode.
         | 
| 1017 | 
            +
                #
         | 
| 1018 | 
            +
                # @param [Class] returning
         | 
| 1019 | 
            +
                #   The type of object to return.  This value may only be set to
         | 
| 1020 | 
            +
                #   <tt>String</tt> or <tt>Addressable::URI</tt>.  All other values
         | 
| 1021 | 
            +
                #   are invalid.  Defaults to <tt>String</tt>.
         | 
| 1022 | 
            +
                #
         | 
| 1023 | 
            +
                # @return [String, Addressable::URI]
         | 
| 1024 | 
            +
                #   The encoded URI.  The return type is determined by
         | 
| 1025 | 
            +
                #   the <tt>returning</tt> parameter.
         | 
| 1026 | 
            +
                def self.encode(uri, returning=String)
         | 
| 1027 | 
            +
                  return nil if uri.nil?
         | 
| 1028 | 
            +
                  if !uri.respond_to?(:to_str)
         | 
| 1029 | 
            +
                    raise TypeError, "Can't convert #{uri.class} into String."
         | 
| 1030 | 
            +
                  end
         | 
| 1031 | 
            +
                  if ![String, ::Addressable::URI].include?(returning)
         | 
| 1032 | 
            +
                    raise TypeError,
         | 
| 1033 | 
            +
                      "Expected String or Addressable::URI, got #{returning.inspect}"
         | 
| 1034 | 
            +
                  end
         | 
| 1035 | 
            +
                  uri_object = uri.kind_of?(self) ? uri : self.parse(uri.to_str)
         | 
| 1036 | 
            +
                  encoded_uri = Addressable::URI.new(
         | 
| 1037 | 
            +
                    :scheme => self.encode_component(uri_object.scheme,
         | 
| 377 1038 | 
             
                      Addressable::URI::CharacterClasses::SCHEME),
         | 
| 378 | 
            -
                    self. | 
| 379 | 
            -
                      Addressable::URI::CharacterClasses::AUTHORITY),
         | 
| 380 | 
            -
                    self.encode_segment(uri_object.password,
         | 
| 381 | 
            -
                      Addressable::URI::CharacterClasses::AUTHORITY),
         | 
| 382 | 
            -
                    self.encode_segment(uri_object.host,
         | 
| 383 | 
            -
                      Addressable::URI::CharacterClasses::AUTHORITY),
         | 
| 384 | 
            -
                    self.encode_segment(uri_object.specified_port,
         | 
| 1039 | 
            +
                    :authority => self.encode_component(uri_object.authority,
         | 
| 385 1040 | 
             
                      Addressable::URI::CharacterClasses::AUTHORITY),
         | 
| 386 | 
            -
                    self. | 
| 1041 | 
            +
                    :path => self.encode_component(uri_object.path,
         | 
| 387 1042 | 
             
                      Addressable::URI::CharacterClasses::PATH),
         | 
| 388 | 
            -
                    self. | 
| 1043 | 
            +
                    :query => self.encode_component(uri_object.query,
         | 
| 389 1044 | 
             
                      Addressable::URI::CharacterClasses::QUERY),
         | 
| 390 | 
            -
                    self. | 
| 1045 | 
            +
                    :fragment => self.encode_component(uri_object.fragment,
         | 
| 391 1046 | 
             
                      Addressable::URI::CharacterClasses::FRAGMENT)
         | 
| 392 | 
            -
                  ) | 
| 1047 | 
            +
                  )
         | 
| 1048 | 
            +
                  if returning == String
         | 
| 1049 | 
            +
                    return encoded_uri.to_s
         | 
| 1050 | 
            +
                  elsif returning == ::Addressable::URI
         | 
| 1051 | 
            +
                    return encoded_uri
         | 
| 1052 | 
            +
                  end
         | 
| 393 1053 | 
             
                end
         | 
| 394 | 
            -
             | 
| 1054 | 
            +
             | 
| 395 1055 | 
             
                class << self
         | 
| 396 1056 | 
             
                  alias_method :escape, :encode
         | 
| 397 1057 | 
             
                end
         | 
| 398 | 
            -
             | 
| 1058 | 
            +
             | 
| 1059 | 
            +
                ##
         | 
| 399 1060 | 
             
                # Normalizes the encoding of a URI.  Characters within a hostname are
         | 
| 400 1061 | 
             
                # not percent encoded to allow for internationalized domain names.
         | 
| 401 | 
            -
                 | 
| 402 | 
            -
             | 
| 403 | 
            -
             | 
| 404 | 
            -
             | 
| 405 | 
            -
             | 
| 406 | 
            -
             | 
| 407 | 
            -
             | 
| 408 | 
            -
             | 
| 409 | 
            -
             | 
| 410 | 
            -
             | 
| 411 | 
            -
             | 
| 1062 | 
            +
                #
         | 
| 1063 | 
            +
                # @param [String, Addressable::URI, #to_str] uri
         | 
| 1064 | 
            +
                #   The URI to encode.
         | 
| 1065 | 
            +
                #
         | 
| 1066 | 
            +
                # @param [Class] returning
         | 
| 1067 | 
            +
                #   The type of object to return.  This value may only be set to
         | 
| 1068 | 
            +
                #   <tt>String</tt> or <tt>Addressable::URI</tt>.  All other values
         | 
| 1069 | 
            +
                #   are invalid.  Defaults to <tt>String</tt>.
         | 
| 1070 | 
            +
                #
         | 
| 1071 | 
            +
                # @return [String, Addressable::URI]
         | 
| 1072 | 
            +
                #   The encoded URI.  The return type is determined by
         | 
| 1073 | 
            +
                #   the <tt>returning</tt> parameter.
         | 
| 1074 | 
            +
                def self.normalized_encode(uri, returning=String)
         | 
| 1075 | 
            +
                  if !uri.respond_to?(:to_str)
         | 
| 1076 | 
            +
                    raise TypeError, "Can't convert #{uri.class} into String."
         | 
| 1077 | 
            +
                  end
         | 
| 1078 | 
            +
                  if ![String, ::Addressable::URI].include?(returning)
         | 
| 1079 | 
            +
                    raise TypeError,
         | 
| 1080 | 
            +
                      "Expected String or Addressable::URI, got #{returning.inspect}"
         | 
| 1081 | 
            +
                  end
         | 
| 1082 | 
            +
                  uri_object = uri.kind_of?(self) ? uri : self.parse(uri.to_str)
         | 
| 1083 | 
            +
                  components = {
         | 
| 1084 | 
            +
                    :scheme => self.unencode_component(uri_object.scheme),
         | 
| 1085 | 
            +
                    :user => self.unencode_component(uri_object.user),
         | 
| 1086 | 
            +
                    :password => self.unencode_component(uri_object.password),
         | 
| 1087 | 
            +
                    :host => self.unencode_component(uri_object.host),
         | 
| 1088 | 
            +
                    :port => uri_object.port,
         | 
| 1089 | 
            +
                    :path => self.unencode_component(uri_object.path),
         | 
| 1090 | 
            +
                    :query => self.unencode_component(uri_object.query),
         | 
| 1091 | 
            +
                    :fragment => self.unencode_component(uri_object.fragment)
         | 
| 412 1092 | 
             
                  }
         | 
| 413 | 
            -
                   | 
| 414 | 
            -
                     | 
| 415 | 
            -
                       | 
| 416 | 
            -
                        segments[key] = IDN::Stringprep.nfkc_normalize(value.to_s)
         | 
| 417 | 
            -
                      end
         | 
| 1093 | 
            +
                  components.each do |key, value|
         | 
| 1094 | 
            +
                    if value != nil
         | 
| 1095 | 
            +
                      components[key] = Addressable::IDNA.unicode_normalize_kc(value.to_s)
         | 
| 418 1096 | 
             
                    end
         | 
| 419 1097 | 
             
                  end
         | 
| 420 | 
            -
                   | 
| 421 | 
            -
                    self. | 
| 1098 | 
            +
                  encoded_uri = Addressable::URI.new(
         | 
| 1099 | 
            +
                    :scheme => self.encode_component(components[:scheme],
         | 
| 422 1100 | 
             
                      Addressable::URI::CharacterClasses::SCHEME),
         | 
| 423 | 
            -
                    self. | 
| 1101 | 
            +
                    :user => self.encode_component(components[:user],
         | 
| 424 1102 | 
             
                      Addressable::URI::CharacterClasses::AUTHORITY),
         | 
| 425 | 
            -
                    self. | 
| 1103 | 
            +
                    :password => self.encode_component(components[:password],
         | 
| 426 1104 | 
             
                      Addressable::URI::CharacterClasses::AUTHORITY),
         | 
| 427 | 
            -
                     | 
| 428 | 
            -
                     | 
| 429 | 
            -
                    self. | 
| 1105 | 
            +
                    :host => components[:host],
         | 
| 1106 | 
            +
                    :port => components[:port],
         | 
| 1107 | 
            +
                    :path => self.encode_component(components[:path],
         | 
| 430 1108 | 
             
                      Addressable::URI::CharacterClasses::PATH),
         | 
| 431 | 
            -
                    self. | 
| 1109 | 
            +
                    :query => self.encode_component(components[:query],
         | 
| 432 1110 | 
             
                      Addressable::URI::CharacterClasses::QUERY),
         | 
| 433 | 
            -
                    self. | 
| 1111 | 
            +
                    :fragment => self.encode_component(components[:fragment],
         | 
| 434 1112 | 
             
                      Addressable::URI::CharacterClasses::FRAGMENT)
         | 
| 435 | 
            -
                  ) | 
| 1113 | 
            +
                  )
         | 
| 1114 | 
            +
                  if returning == String
         | 
| 1115 | 
            +
                    return encoded_uri.to_s
         | 
| 1116 | 
            +
                  elsif returning == ::Addressable::URI
         | 
| 1117 | 
            +
                    return encoded_uri
         | 
| 1118 | 
            +
                  end
         | 
| 436 1119 | 
             
                end
         | 
| 437 1120 |  | 
| 1121 | 
            +
                ##
         | 
| 438 1122 | 
             
                # Extracts uris from an arbitrary body of text.
         | 
| 1123 | 
            +
                #
         | 
| 1124 | 
            +
                # @param [String, #to_str] text
         | 
| 1125 | 
            +
                #   The body of text to extract URIs from.
         | 
| 1126 | 
            +
                #
         | 
| 1127 | 
            +
                # @option [String, Addressable::URI, #to_str] base
         | 
| 1128 | 
            +
                #   Causes any relative URIs to be resolved against the base URI.
         | 
| 1129 | 
            +
                #
         | 
| 1130 | 
            +
                # @option [TrueClass, FalseClass] parse
         | 
| 1131 | 
            +
                #   If parse is true, all extracted URIs will be parsed.  If parse is
         | 
| 1132 | 
            +
                #   false, the return value with be an <tt>Array</tt> of <tt>Strings</aa>.
         | 
| 1133 | 
            +
                #   Defaults to false.
         | 
| 1134 | 
            +
                #
         | 
| 1135 | 
            +
                # @return [Array] The extracted URIs.
         | 
| 439 1136 | 
             
                def self.extract(text, options={})
         | 
| 440 | 
            -
                  defaults = {:base => nil, :parse => false} | 
| 1137 | 
            +
                  defaults = {:base => nil, :parse => false}
         | 
| 441 1138 | 
             
                  options = defaults.merge(options)
         | 
| 442 1139 | 
             
                  raise InvalidOptionError unless (options.keys - defaults.keys).empty?
         | 
| 443 1140 | 
             
                  # This regular expression needs to be less forgiving or else it would
         | 
| @@ -470,16 +1167,10 @@ module Addressable | |
| 470 1167 | 
             
                      nil
         | 
| 471 1168 | 
             
                    end
         | 
| 472 1169 | 
             
                  end
         | 
| 473 | 
            -
                  parsed_uris. | 
| 474 | 
            -
                    ( | 
| 475 | 
            -
             | 
| 476 | 
            -
             | 
| 477 | 
            -
                     uri.scheme == "thr" ||
         | 
| 478 | 
            -
                     uri.scheme == "this" ||
         | 
| 479 | 
            -
                     uri.scheme == "float" ||
         | 
| 480 | 
            -
                     uri.scheme == "user" ||
         | 
| 481 | 
            -
                     uri.scheme == "username" ||
         | 
| 482 | 
            -
                     uri.scheme == "out")
         | 
| 1170 | 
            +
                  parsed_uris = parsed_uris.select do |uri|
         | 
| 1171 | 
            +
                    (self.ip_based_schemes | [
         | 
| 1172 | 
            +
                      "file", "git", "svn", "mailto", "tel"
         | 
| 1173 | 
            +
                    ]).include?(uri.normalized_scheme)
         | 
| 483 1174 | 
             
                  end
         | 
| 484 1175 | 
             
                  if options[:parse]
         | 
| 485 1176 | 
             
                    return parsed_uris
         | 
| @@ -487,51 +1178,126 @@ module Addressable | |
| 487 1178 | 
             
                    return parsed_uris.collect { |uri| uri.to_s }
         | 
| 488 1179 | 
             
                  end
         | 
| 489 1180 | 
             
                end
         | 
| 490 | 
            -
             | 
| 491 | 
            -
                 | 
| 492 | 
            -
                #  | 
| 493 | 
            -
                 | 
| 494 | 
            -
             | 
| 495 | 
            -
             | 
| 496 | 
            -
             | 
| 497 | 
            -
             | 
| 498 | 
            -
             | 
| 499 | 
            -
             | 
| 500 | 
            -
             | 
| 501 | 
            -
             | 
| 502 | 
            -
             | 
| 503 | 
            -
             | 
| 1181 | 
            +
             | 
| 1182 | 
            +
                ##
         | 
| 1183 | 
            +
                # Creates a new uri object from component parts.
         | 
| 1184 | 
            +
                #
         | 
| 1185 | 
            +
                # @option [String, #to_str] scheme The scheme component.
         | 
| 1186 | 
            +
                # @option [String, #to_str] user The user component.
         | 
| 1187 | 
            +
                # @option [String, #to_str] password The password component.
         | 
| 1188 | 
            +
                # @option [String, #to_str] userinfo
         | 
| 1189 | 
            +
                #   The userinfo component.  If this is supplied, the user and password
         | 
| 1190 | 
            +
                #   components must be omitted.
         | 
| 1191 | 
            +
                # @option [String, #to_str] host The host component.
         | 
| 1192 | 
            +
                # @option [String, #to_str] port The port component.
         | 
| 1193 | 
            +
                # @option [String, #to_str] authority
         | 
| 1194 | 
            +
                #   The authority component.  If this is supplied, the user, password,
         | 
| 1195 | 
            +
                #   userinfo, host, and port components must be omitted.
         | 
| 1196 | 
            +
                # @option [String, #to_str] path The path component.
         | 
| 1197 | 
            +
                # @option [String, #to_str] query The query component.
         | 
| 1198 | 
            +
                # @option [String, #to_str] fragment The fragment component.
         | 
| 1199 | 
            +
                #
         | 
| 1200 | 
            +
                # @return [Addressable::URI] The constructed URI object.
         | 
| 1201 | 
            +
                def initialize(options={})
         | 
| 1202 | 
            +
                  if options.has_key?(:authority)
         | 
| 1203 | 
            +
                    if (options.keys & [:userinfo, :user, :password, :host, :port]).any?
         | 
| 1204 | 
            +
                      raise ArgumentError,
         | 
| 1205 | 
            +
                        "Cannot specify both an authority and any of the components " +
         | 
| 1206 | 
            +
                        "within the authority."
         | 
| 1207 | 
            +
                    end
         | 
| 504 1208 | 
             
                  end
         | 
| 505 | 
            -
                   | 
| 506 | 
            -
             | 
| 507 | 
            -
             | 
| 508 | 
            -
             | 
| 509 | 
            -
                     | 
| 1209 | 
            +
                  if options.has_key?(:userinfo)
         | 
| 1210 | 
            +
                    if (options.keys & [:user, :password]).any?
         | 
| 1211 | 
            +
                      raise ArgumentError,
         | 
| 1212 | 
            +
                        "Cannot specify both a userinfo and either the user or password."
         | 
| 1213 | 
            +
                    end
         | 
| 510 1214 | 
             
                  end
         | 
| 511 | 
            -
                  @query = query
         | 
| 512 | 
            -
                  @fragment = fragment
         | 
| 513 1215 |  | 
| 514 | 
            -
                   | 
| 1216 | 
            +
                  self.validation_deferred = true
         | 
| 1217 | 
            +
                  self.scheme = options[:scheme] if options[:scheme]
         | 
| 1218 | 
            +
                  self.user = options[:user] if options[:user]
         | 
| 1219 | 
            +
                  self.password = options[:password] if options[:password]
         | 
| 1220 | 
            +
                  self.userinfo = options[:userinfo] if options[:userinfo]
         | 
| 1221 | 
            +
                  self.host = options[:host] if options[:host]
         | 
| 1222 | 
            +
                  self.port = options[:port] if options[:port]
         | 
| 1223 | 
            +
                  self.authority = options[:authority] if options[:authority]
         | 
| 1224 | 
            +
                  self.path = options[:path] if options[:path]
         | 
| 1225 | 
            +
                  self.query = options[:query] if options[:query]
         | 
| 1226 | 
            +
                  self.fragment = options[:fragment] if options[:fragment]
         | 
| 1227 | 
            +
                  self.validation_deferred = false
         | 
| 515 1228 | 
             
                end
         | 
| 516 | 
            -
             | 
| 517 | 
            -
                 | 
| 1229 | 
            +
             | 
| 1230 | 
            +
                ##
         | 
| 1231 | 
            +
                # The scheme component for this URI.
         | 
| 1232 | 
            +
                #
         | 
| 1233 | 
            +
                # @return [String] The scheme component.
         | 
| 518 1234 | 
             
                def scheme
         | 
| 519 1235 | 
             
                  return @scheme
         | 
| 520 1236 | 
             
                end
         | 
| 521 | 
            -
             | 
| 522 | 
            -
                 | 
| 1237 | 
            +
             | 
| 1238 | 
            +
                ##
         | 
| 1239 | 
            +
                # The scheme component for this URI, normalized.
         | 
| 1240 | 
            +
                #
         | 
| 1241 | 
            +
                # @return [String] The scheme component, normalized.
         | 
| 1242 | 
            +
                def normalized_scheme
         | 
| 1243 | 
            +
                  @normalized_scheme ||= (begin
         | 
| 1244 | 
            +
                    if self.scheme != nil
         | 
| 1245 | 
            +
                      if self.scheme =~ /^\s*ssh\+svn\s*$/i
         | 
| 1246 | 
            +
                        "svn+ssh"
         | 
| 1247 | 
            +
                      else
         | 
| 1248 | 
            +
                        self.scheme.strip.downcase
         | 
| 1249 | 
            +
                      end
         | 
| 1250 | 
            +
                    else
         | 
| 1251 | 
            +
                      nil
         | 
| 1252 | 
            +
                    end
         | 
| 1253 | 
            +
                  end)
         | 
| 1254 | 
            +
                end
         | 
| 1255 | 
            +
             | 
| 1256 | 
            +
                ##
         | 
| 1257 | 
            +
                # Sets the scheme component for this URI.
         | 
| 1258 | 
            +
                #
         | 
| 1259 | 
            +
                # @param [String, #to_str] new_scheme The new scheme component.
         | 
| 523 1260 | 
             
                def scheme=(new_scheme)
         | 
| 524 | 
            -
                  @scheme = new_scheme
         | 
| 1261 | 
            +
                  @scheme = new_scheme ? new_scheme.to_str : nil
         | 
| 1262 | 
            +
                  @scheme = nil if @scheme.to_s.strip == ""
         | 
| 1263 | 
            +
             | 
| 1264 | 
            +
                  # Reset dependant values
         | 
| 1265 | 
            +
                  @normalized_scheme = nil
         | 
| 525 1266 | 
             
                end
         | 
| 526 | 
            -
             | 
| 527 | 
            -
                 | 
| 1267 | 
            +
             | 
| 1268 | 
            +
                ##
         | 
| 1269 | 
            +
                # The user component for this URI.
         | 
| 1270 | 
            +
                #
         | 
| 1271 | 
            +
                # @return [String] The user component.
         | 
| 528 1272 | 
             
                def user
         | 
| 529 1273 | 
             
                  return @user
         | 
| 530 1274 | 
             
                end
         | 
| 531 | 
            -
             | 
| 532 | 
            -
                 | 
| 1275 | 
            +
             | 
| 1276 | 
            +
                ##
         | 
| 1277 | 
            +
                # The user component for this URI, normalized.
         | 
| 1278 | 
            +
                #
         | 
| 1279 | 
            +
                # @return [String] The user component, normalized.
         | 
| 1280 | 
            +
                def normalized_user
         | 
| 1281 | 
            +
                  @normalized_user ||= (begin
         | 
| 1282 | 
            +
                    if self.user
         | 
| 1283 | 
            +
                      if normalized_scheme =~ /https?/ && self.user.strip == "" &&
         | 
| 1284 | 
            +
                          (!self.password || self.password.strip == "")
         | 
| 1285 | 
            +
                        nil
         | 
| 1286 | 
            +
                      else
         | 
| 1287 | 
            +
                        self.user.strip
         | 
| 1288 | 
            +
                      end
         | 
| 1289 | 
            +
                    else
         | 
| 1290 | 
            +
                      nil
         | 
| 1291 | 
            +
                    end
         | 
| 1292 | 
            +
                  end)
         | 
| 1293 | 
            +
                end
         | 
| 1294 | 
            +
             | 
| 1295 | 
            +
                ##
         | 
| 1296 | 
            +
                # Sets the user component for this URI.
         | 
| 1297 | 
            +
                #
         | 
| 1298 | 
            +
                # @param [String, #to_str] new_user The new user component.
         | 
| 533 1299 | 
             
                def user=(new_user)
         | 
| 534 | 
            -
                  @user = new_user
         | 
| 1300 | 
            +
                  @user = new_user ? new_user.to_str : nil
         | 
| 535 1301 |  | 
| 536 1302 | 
             
                  # You can't have a nil user with a non-nil password
         | 
| 537 1303 | 
             
                  if @password != nil
         | 
| @@ -540,20 +1306,47 @@ module Addressable | |
| 540 1306 |  | 
| 541 1307 | 
             
                  # Reset dependant values
         | 
| 542 1308 | 
             
                  @userinfo = nil
         | 
| 1309 | 
            +
                  @normalized_userinfo = nil
         | 
| 543 1310 | 
             
                  @authority = nil
         | 
| 1311 | 
            +
                  @normalized_user = nil
         | 
| 544 1312 |  | 
| 545 1313 | 
             
                  # Ensure we haven't created an invalid URI
         | 
| 546 1314 | 
             
                  validate()
         | 
| 547 1315 | 
             
                end
         | 
| 548 | 
            -
             | 
| 549 | 
            -
                 | 
| 1316 | 
            +
             | 
| 1317 | 
            +
                ##
         | 
| 1318 | 
            +
                # The password component for this URI.
         | 
| 1319 | 
            +
                #
         | 
| 1320 | 
            +
                # @return [String] The password component.
         | 
| 550 1321 | 
             
                def password
         | 
| 551 1322 | 
             
                  return @password
         | 
| 552 1323 | 
             
                end
         | 
| 553 1324 |  | 
| 554 | 
            -
                 | 
| 1325 | 
            +
                ##
         | 
| 1326 | 
            +
                # The password component for this URI, normalized.
         | 
| 1327 | 
            +
                #
         | 
| 1328 | 
            +
                # @return [String] The password component, normalized.
         | 
| 1329 | 
            +
                def normalized_password
         | 
| 1330 | 
            +
                  @normalized_password ||= (begin
         | 
| 1331 | 
            +
                    if self.password
         | 
| 1332 | 
            +
                      if normalized_scheme =~ /https?/ && self.password.strip == "" &&
         | 
| 1333 | 
            +
                          (!self.user || self.user.strip == "")
         | 
| 1334 | 
            +
                        nil
         | 
| 1335 | 
            +
                      else
         | 
| 1336 | 
            +
                        self.password.strip
         | 
| 1337 | 
            +
                      end
         | 
| 1338 | 
            +
                    else
         | 
| 1339 | 
            +
                      nil
         | 
| 1340 | 
            +
                    end
         | 
| 1341 | 
            +
                  end)
         | 
| 1342 | 
            +
                end
         | 
| 1343 | 
            +
             | 
| 1344 | 
            +
                ##
         | 
| 1345 | 
            +
                # Sets the password component for this URI.
         | 
| 1346 | 
            +
                #
         | 
| 1347 | 
            +
                # @param [String, #to_str] new_password The new password component.
         | 
| 555 1348 | 
             
                def password=(new_password)
         | 
| 556 | 
            -
                  @password = new_password
         | 
| 1349 | 
            +
                  @password = new_password ? new_password.to_str : nil
         | 
| 557 1350 |  | 
| 558 1351 | 
             
                  # You can't have a nil user with a non-nil password
         | 
| 559 1352 | 
             
                  if @password != nil
         | 
| @@ -562,33 +1355,65 @@ module Addressable | |
| 562 1355 |  | 
| 563 1356 | 
             
                  # Reset dependant values
         | 
| 564 1357 | 
             
                  @userinfo = nil
         | 
| 1358 | 
            +
                  @normalized_userinfo = nil
         | 
| 565 1359 | 
             
                  @authority = nil
         | 
| 1360 | 
            +
                  @normalized_password = nil
         | 
| 566 1361 |  | 
| 567 1362 | 
             
                  # Ensure we haven't created an invalid URI
         | 
| 568 1363 | 
             
                  validate()
         | 
| 569 1364 | 
             
                end
         | 
| 570 | 
            -
             | 
| 571 | 
            -
                 | 
| 1365 | 
            +
             | 
| 1366 | 
            +
                ##
         | 
| 1367 | 
            +
                # The userinfo component for this URI.
         | 
| 1368 | 
            +
                # Combines the user and password components.
         | 
| 1369 | 
            +
                #
         | 
| 1370 | 
            +
                # @return [String] The userinfo component.
         | 
| 572 1371 | 
             
                def userinfo
         | 
| 573 | 
            -
                   | 
| 1372 | 
            +
                  @userinfo ||= (begin
         | 
| 574 1373 | 
             
                    current_user = self.user
         | 
| 575 1374 | 
             
                    current_password = self.password
         | 
| 576 1375 | 
             
                    if !current_user && !current_password
         | 
| 577 | 
            -
                       | 
| 1376 | 
            +
                      nil
         | 
| 1377 | 
            +
                    elsif current_user && current_password
         | 
| 1378 | 
            +
                      "#{current_user}:#{current_password}"
         | 
| 1379 | 
            +
                    elsif current_user && !current_password
         | 
| 1380 | 
            +
                      "#{current_user}"
         | 
| 1381 | 
            +
                    end
         | 
| 1382 | 
            +
                  end)
         | 
| 1383 | 
            +
                end
         | 
| 1384 | 
            +
             | 
| 1385 | 
            +
                ##
         | 
| 1386 | 
            +
                # The userinfo component for this URI, normalized.
         | 
| 1387 | 
            +
                #
         | 
| 1388 | 
            +
                # @return [String] The userinfo component, normalized.
         | 
| 1389 | 
            +
                def normalized_userinfo
         | 
| 1390 | 
            +
                  @normalized_userinfo ||= (begin
         | 
| 1391 | 
            +
                    current_user = self.normalized_user
         | 
| 1392 | 
            +
                    current_password = self.normalized_password
         | 
| 1393 | 
            +
                    if !current_user && !current_password
         | 
| 1394 | 
            +
                      nil
         | 
| 578 1395 | 
             
                    elsif current_user && current_password
         | 
| 579 | 
            -
                       | 
| 1396 | 
            +
                      "#{current_user}:#{current_password}"
         | 
| 580 1397 | 
             
                    elsif current_user && !current_password
         | 
| 581 | 
            -
                       | 
| 1398 | 
            +
                      "#{current_user}"
         | 
| 582 1399 | 
             
                    end
         | 
| 583 | 
            -
                  end
         | 
| 584 | 
            -
                  return @userinfo
         | 
| 1400 | 
            +
                  end)
         | 
| 585 1401 | 
             
                end
         | 
| 586 | 
            -
             | 
| 587 | 
            -
                 | 
| 1402 | 
            +
             | 
| 1403 | 
            +
                ##
         | 
| 1404 | 
            +
                # Sets the userinfo component for this URI.
         | 
| 1405 | 
            +
                #
         | 
| 1406 | 
            +
                # @param [String, #to_str] new_userinfo The new userinfo component.
         | 
| 588 1407 | 
             
                def userinfo=(new_userinfo)
         | 
| 589 | 
            -
                  new_user = new_userinfo | 
| 590 | 
            -
             | 
| 591 | 
            -
             | 
| 1408 | 
            +
                  new_user, new_password = if new_userinfo
         | 
| 1409 | 
            +
                    [
         | 
| 1410 | 
            +
                      new_userinfo.to_str.strip[/^(.*):/, 1],
         | 
| 1411 | 
            +
                      new_userinfo.to_str.strip[/:(.*)$/, 1]
         | 
| 1412 | 
            +
                    ]
         | 
| 1413 | 
            +
                  else
         | 
| 1414 | 
            +
                    [nil, nil]
         | 
| 1415 | 
            +
                  end
         | 
| 1416 | 
            +
             | 
| 592 1417 | 
             
                  # Password assigned first to ensure validity in case of nil
         | 
| 593 1418 | 
             
                  self.password = new_password
         | 
| 594 1419 | 
             
                  self.user = new_user
         | 
| @@ -599,62 +1424,129 @@ module Addressable | |
| 599 1424 | 
             
                  # Ensure we haven't created an invalid URI
         | 
| 600 1425 | 
             
                  validate()
         | 
| 601 1426 | 
             
                end
         | 
| 602 | 
            -
             | 
| 603 | 
            -
                 | 
| 1427 | 
            +
             | 
| 1428 | 
            +
                ##
         | 
| 1429 | 
            +
                # The host component for this URI.
         | 
| 1430 | 
            +
                #
         | 
| 1431 | 
            +
                # @return [String] The host component.
         | 
| 604 1432 | 
             
                def host
         | 
| 605 1433 | 
             
                  return @host
         | 
| 606 1434 | 
             
                end
         | 
| 607 | 
            -
             | 
| 608 | 
            -
                 | 
| 1435 | 
            +
             | 
| 1436 | 
            +
                ##
         | 
| 1437 | 
            +
                # The host component for this URI, normalized.
         | 
| 1438 | 
            +
                #
         | 
| 1439 | 
            +
                # @return [String] The host component, normalized.
         | 
| 1440 | 
            +
                def normalized_host
         | 
| 1441 | 
            +
                  @normalized_host ||= (begin
         | 
| 1442 | 
            +
                    if self.host != nil
         | 
| 1443 | 
            +
                      if self.host.strip != ""
         | 
| 1444 | 
            +
                        result = ::Addressable::IDNA.to_ascii(
         | 
| 1445 | 
            +
                          self.class.unencode_component(self.host.strip.downcase)
         | 
| 1446 | 
            +
                        )
         | 
| 1447 | 
            +
                        if result[-1..-1] == "."
         | 
| 1448 | 
            +
                          # Trailing dots are unnecessary
         | 
| 1449 | 
            +
                          result = result[0...-1]
         | 
| 1450 | 
            +
                        end
         | 
| 1451 | 
            +
                        result
         | 
| 1452 | 
            +
                      else
         | 
| 1453 | 
            +
                        ""
         | 
| 1454 | 
            +
                      end
         | 
| 1455 | 
            +
                    else
         | 
| 1456 | 
            +
                      nil
         | 
| 1457 | 
            +
                    end
         | 
| 1458 | 
            +
                  end)
         | 
| 1459 | 
            +
                end
         | 
| 1460 | 
            +
             | 
| 1461 | 
            +
                ##
         | 
| 1462 | 
            +
                # Sets the host component for this URI.
         | 
| 1463 | 
            +
                #
         | 
| 1464 | 
            +
                # @param [String, #to_str] new_host The new host component.
         | 
| 609 1465 | 
             
                def host=(new_host)
         | 
| 610 | 
            -
                  @host = new_host
         | 
| 1466 | 
            +
                  @host = new_host ? new_host.to_str : nil
         | 
| 611 1467 |  | 
| 612 1468 | 
             
                  # Reset dependant values
         | 
| 613 1469 | 
             
                  @authority = nil
         | 
| 1470 | 
            +
                  @normalized_host = nil
         | 
| 614 1471 |  | 
| 615 1472 | 
             
                  # Ensure we haven't created an invalid URI
         | 
| 616 1473 | 
             
                  validate()
         | 
| 617 1474 | 
             
                end
         | 
| 618 | 
            -
             | 
| 619 | 
            -
                 | 
| 1475 | 
            +
             | 
| 1476 | 
            +
                ##
         | 
| 1477 | 
            +
                # The authority component for this URI.
         | 
| 1478 | 
            +
                # Combines the user, password, host, and port components.
         | 
| 1479 | 
            +
                #
         | 
| 1480 | 
            +
                # @return [String] The authority component.
         | 
| 620 1481 | 
             
                def authority
         | 
| 621 | 
            -
                   | 
| 622 | 
            -
                     | 
| 623 | 
            -
             | 
| 624 | 
            -
                     | 
| 625 | 
            -
                       | 
| 1482 | 
            +
                  @authority ||= (begin
         | 
| 1483 | 
            +
                    if self.host.nil?
         | 
| 1484 | 
            +
                      nil
         | 
| 1485 | 
            +
                    else
         | 
| 1486 | 
            +
                      authority = ""
         | 
| 1487 | 
            +
                      if self.userinfo != nil
         | 
| 1488 | 
            +
                        authority << "#{self.userinfo}@"
         | 
| 1489 | 
            +
                      end
         | 
| 1490 | 
            +
                      authority << self.host
         | 
| 1491 | 
            +
                      if self.port != nil
         | 
| 1492 | 
            +
                        authority << ":#{self.port}"
         | 
| 1493 | 
            +
                      end
         | 
| 1494 | 
            +
                      authority
         | 
| 626 1495 | 
             
                    end
         | 
| 627 | 
            -
             | 
| 628 | 
            -
             | 
| 629 | 
            -
             | 
| 1496 | 
            +
                  end)
         | 
| 1497 | 
            +
                end
         | 
| 1498 | 
            +
             | 
| 1499 | 
            +
                ##
         | 
| 1500 | 
            +
                # The authority component for this URI, normalized.
         | 
| 1501 | 
            +
                #
         | 
| 1502 | 
            +
                # @return [String] The authority component, normalized.
         | 
| 1503 | 
            +
                def normalized_authority
         | 
| 1504 | 
            +
                  @normalized_authority ||= (begin
         | 
| 1505 | 
            +
                    if self.normalized_host.nil?
         | 
| 1506 | 
            +
                      nil
         | 
| 1507 | 
            +
                    else
         | 
| 1508 | 
            +
                      authority = ""
         | 
| 1509 | 
            +
                      if self.normalized_userinfo != nil
         | 
| 1510 | 
            +
                        authority << "#{self.normalized_userinfo}@"
         | 
| 1511 | 
            +
                      end
         | 
| 1512 | 
            +
                      authority << self.normalized_host
         | 
| 1513 | 
            +
                      if self.normalized_port != nil
         | 
| 1514 | 
            +
                        authority << ":#{self.normalized_port}"
         | 
| 1515 | 
            +
                      end
         | 
| 1516 | 
            +
                      authority
         | 
| 630 1517 | 
             
                    end
         | 
| 631 | 
            -
                  end
         | 
| 632 | 
            -
                  return @authority
         | 
| 1518 | 
            +
                  end)
         | 
| 633 1519 | 
             
                end
         | 
| 634 | 
            -
             | 
| 635 | 
            -
                 | 
| 1520 | 
            +
             | 
| 1521 | 
            +
                ##
         | 
| 1522 | 
            +
                # Sets the authority component for this URI.
         | 
| 1523 | 
            +
                #
         | 
| 1524 | 
            +
                # @param [String, #to_str] new_authority The new authority component.
         | 
| 636 1525 | 
             
                def authority=(new_authority)
         | 
| 637 1526 | 
             
                  if new_authority
         | 
| 638 | 
            -
                     | 
| 1527 | 
            +
                    new_authority = new_authority.to_str
         | 
| 1528 | 
            +
                    new_userinfo = new_authority[/^([^\[\]]*)@/, 1]
         | 
| 639 1529 | 
             
                    if new_userinfo
         | 
| 640 | 
            -
                      new_user = new_userinfo.strip | 
| 641 | 
            -
                      new_password = new_userinfo.strip | 
| 1530 | 
            +
                      new_user = new_userinfo.strip[/^([^:]*):?/, 1]
         | 
| 1531 | 
            +
                      new_password = new_userinfo.strip[/:(.*)$/, 1]
         | 
| 642 1532 | 
             
                    end
         | 
| 643 1533 | 
             
                    new_host =
         | 
| 644 1534 | 
             
                      new_authority.gsub(/^([^\[\]]*)@/, "").gsub(/:([^:@\[\]]*?)$/, "")
         | 
| 645 1535 | 
             
                    new_port =
         | 
| 646 | 
            -
                      new_authority | 
| 1536 | 
            +
                      new_authority[/:([^:@\[\]]*?)$/, 1]
         | 
| 647 1537 | 
             
                  end
         | 
| 648 | 
            -
             | 
| 1538 | 
            +
             | 
| 649 1539 | 
             
                  # Password assigned first to ensure validity in case of nil
         | 
| 650 1540 | 
             
                  self.password = new_password
         | 
| 651 1541 | 
             
                  self.user = new_user
         | 
| 652 1542 | 
             
                  self.host = new_host
         | 
| 653 | 
            -
                  
         | 
| 654 | 
            -
             | 
| 655 | 
            -
                   | 
| 656 | 
            -
                  @ | 
| 657 | 
            -
                  
         | 
| 1543 | 
            +
                  self.port = new_port
         | 
| 1544 | 
            +
             | 
| 1545 | 
            +
                  # Reset dependant values
         | 
| 1546 | 
            +
                  @inferred_port = nil
         | 
| 1547 | 
            +
                  @userinfo = nil
         | 
| 1548 | 
            +
                  @normalized_userinfo = nil
         | 
| 1549 | 
            +
             | 
| 658 1550 | 
             
                  # Ensure we haven't created an invalid URI
         | 
| 659 1551 | 
             
                  validate()
         | 
| 660 1552 | 
             
                end
         | 
| @@ -663,112 +1555,311 @@ module Addressable | |
| 663 1555 | 
             
                # use a similar URI form:
         | 
| 664 1556 | 
             
                # //<user>:<password>@<host>:<port>/<url-path>
         | 
| 665 1557 | 
             
                def self.ip_based_schemes
         | 
| 666 | 
            -
                  return self. | 
| 1558 | 
            +
                  return self.port_mapping.keys
         | 
| 667 1559 | 
             
                end
         | 
| 668 1560 |  | 
| 669 1561 | 
             
                # Returns a hash of common IP-based schemes and their default port
         | 
| 670 1562 | 
             
                # numbers.  Adding new schemes to this hash, as necessary, will allow
         | 
| 671 1563 | 
             
                # for better URI normalization.
         | 
| 672 | 
            -
                def self. | 
| 673 | 
            -
                   | 
| 674 | 
            -
                     | 
| 675 | 
            -
             | 
| 676 | 
            -
             | 
| 677 | 
            -
             | 
| 678 | 
            -
             | 
| 679 | 
            -
             | 
| 680 | 
            -
             | 
| 681 | 
            -
             | 
| 682 | 
            -
             | 
| 683 | 
            -
             | 
| 684 | 
            -
             | 
| 685 | 
            -
             | 
| 686 | 
            -
             | 
| 687 | 
            -
             | 
| 688 | 
            -
             | 
| 689 | 
            -
             | 
| 690 | 
            -
                 | 
| 691 | 
            -
                
         | 
| 692 | 
            -
                #  | 
| 693 | 
            -
                #  | 
| 694 | 
            -
                # | 
| 1564 | 
            +
                def self.port_mapping
         | 
| 1565 | 
            +
                  @port_mapping ||= {
         | 
| 1566 | 
            +
                    "http" => 80,
         | 
| 1567 | 
            +
                    "https" => 443,
         | 
| 1568 | 
            +
                    "ftp" => 21,
         | 
| 1569 | 
            +
                    "tftp" => 69,
         | 
| 1570 | 
            +
                    "sftp" => 22,
         | 
| 1571 | 
            +
                    "ssh" => 22,
         | 
| 1572 | 
            +
                    "svn+ssh" => 22,
         | 
| 1573 | 
            +
                    "telnet" => 23,
         | 
| 1574 | 
            +
                    "nntp" => 119,
         | 
| 1575 | 
            +
                    "gopher" => 70,
         | 
| 1576 | 
            +
                    "wais" => 210,
         | 
| 1577 | 
            +
                    "ldap" => 389,
         | 
| 1578 | 
            +
                    "prospero" => 1525
         | 
| 1579 | 
            +
                  }
         | 
| 1580 | 
            +
                end
         | 
| 1581 | 
            +
             | 
| 1582 | 
            +
                ##
         | 
| 1583 | 
            +
                # The port component for this URI.
         | 
| 1584 | 
            +
                # This is the port number actually given in the URI.  This does not
         | 
| 1585 | 
            +
                # infer port numbers from default values.
         | 
| 1586 | 
            +
                #
         | 
| 1587 | 
            +
                # @return [Integer] The port component.
         | 
| 695 1588 | 
             
                def port
         | 
| 696 | 
            -
                   | 
| 697 | 
            -
             | 
| 698 | 
            -
             | 
| 1589 | 
            +
                  return @port
         | 
| 1590 | 
            +
                end
         | 
| 1591 | 
            +
             | 
| 1592 | 
            +
                ##
         | 
| 1593 | 
            +
                # The port component for this URI, normalized.
         | 
| 1594 | 
            +
                #
         | 
| 1595 | 
            +
                # @return [Integer] The port component, normalized.
         | 
| 1596 | 
            +
                def normalized_port
         | 
| 1597 | 
            +
                  @normalized_port ||= (begin
         | 
| 1598 | 
            +
                    if self.class.port_mapping[normalized_scheme] == self.port
         | 
| 1599 | 
            +
                      nil
         | 
| 699 1600 | 
             
                    else
         | 
| 700 | 
            -
                       | 
| 1601 | 
            +
                      self.port
         | 
| 701 1602 | 
             
                    end
         | 
| 702 | 
            -
             | 
| 703 | 
            -
                  else
         | 
| 704 | 
            -
                    @port = @port.to_i
         | 
| 705 | 
            -
                    return @port
         | 
| 706 | 
            -
                  end
         | 
| 1603 | 
            +
                  end)
         | 
| 707 1604 | 
             
                end
         | 
| 708 | 
            -
             | 
| 709 | 
            -
                 | 
| 1605 | 
            +
             | 
| 1606 | 
            +
                ##
         | 
| 1607 | 
            +
                # Sets the port component for this URI.
         | 
| 1608 | 
            +
                #
         | 
| 1609 | 
            +
                # @param [String, Integer, #to_s] new_port The new port component.
         | 
| 710 1610 | 
             
                def port=(new_port)
         | 
| 1611 | 
            +
                  if new_port != nil && !(new_port.to_s =~ /^\d+$/)
         | 
| 1612 | 
            +
                    raise InvalidURIError,
         | 
| 1613 | 
            +
                      "Invalid port number: #{new_port.inspect}"
         | 
| 1614 | 
            +
                  end
         | 
| 1615 | 
            +
             | 
| 711 1616 | 
             
                  @port = new_port.to_s.to_i
         | 
| 712 | 
            -
                  @ | 
| 1617 | 
            +
                  @port = nil if @port == 0
         | 
| 1618 | 
            +
             | 
| 1619 | 
            +
                  # Reset dependant values
         | 
| 713 1620 | 
             
                  @authority = nil
         | 
| 1621 | 
            +
                  @inferred_port = nil
         | 
| 1622 | 
            +
                  @normalized_port = nil
         | 
| 1623 | 
            +
             | 
| 1624 | 
            +
                  # Ensure we haven't created an invalid URI
         | 
| 1625 | 
            +
                  validate()
         | 
| 714 1626 | 
             
                end
         | 
| 715 | 
            -
             | 
| 716 | 
            -
                 | 
| 717 | 
            -
                 | 
| 718 | 
            -
             | 
| 719 | 
            -
             | 
| 720 | 
            -
             | 
| 721 | 
            -
             | 
| 722 | 
            -
             | 
| 723 | 
            -
                   | 
| 1627 | 
            +
             | 
| 1628 | 
            +
                ##
         | 
| 1629 | 
            +
                # The inferred port component for this URI.
         | 
| 1630 | 
            +
                # This method will normalize to the default port for the URI's scheme if
         | 
| 1631 | 
            +
                # the port isn't explicitly specified in the URI.
         | 
| 1632 | 
            +
                #
         | 
| 1633 | 
            +
                # @return [Integer] The inferred port component.
         | 
| 1634 | 
            +
                def inferred_port
         | 
| 1635 | 
            +
                  @inferred_port ||= (begin
         | 
| 1636 | 
            +
                    if port.to_i == 0
         | 
| 1637 | 
            +
                      if scheme
         | 
| 1638 | 
            +
                        self.class.port_mapping[scheme.strip.downcase]
         | 
| 1639 | 
            +
                      else
         | 
| 1640 | 
            +
                        nil
         | 
| 1641 | 
            +
                      end
         | 
| 1642 | 
            +
                    else
         | 
| 1643 | 
            +
                      port.to_i
         | 
| 1644 | 
            +
                    end
         | 
| 1645 | 
            +
                  end)
         | 
| 724 1646 | 
             
                end
         | 
| 725 | 
            -
             | 
| 726 | 
            -
                 | 
| 1647 | 
            +
             | 
| 1648 | 
            +
                ##
         | 
| 1649 | 
            +
                # The path component for this URI.
         | 
| 1650 | 
            +
                #
         | 
| 1651 | 
            +
                # @return [String] The path component.
         | 
| 727 1652 | 
             
                def path
         | 
| 728 | 
            -
                  return @path
         | 
| 1653 | 
            +
                  return (@path || "")
         | 
| 1654 | 
            +
                end
         | 
| 1655 | 
            +
             | 
| 1656 | 
            +
                ##
         | 
| 1657 | 
            +
                # The path component for this URI, normalized.
         | 
| 1658 | 
            +
                #
         | 
| 1659 | 
            +
                # @return [String] The path component, normalized.
         | 
| 1660 | 
            +
                def normalized_path
         | 
| 1661 | 
            +
                  @normalized_path ||= (begin
         | 
| 1662 | 
            +
                    result = self.class.normalize_path(self.path.strip)
         | 
| 1663 | 
            +
                    if result == "" &&
         | 
| 1664 | 
            +
                        ["http", "https", "ftp", "tftp"].include?(self.normalized_scheme)
         | 
| 1665 | 
            +
                      result = "/"
         | 
| 1666 | 
            +
                    end
         | 
| 1667 | 
            +
                    result
         | 
| 1668 | 
            +
                  end)
         | 
| 729 1669 | 
             
                end
         | 
| 730 | 
            -
             | 
| 731 | 
            -
                 | 
| 1670 | 
            +
             | 
| 1671 | 
            +
                ##
         | 
| 1672 | 
            +
                # Sets the path component for this URI.
         | 
| 1673 | 
            +
                #
         | 
| 1674 | 
            +
                # @param [String, #to_str] new_path The new path component.
         | 
| 732 1675 | 
             
                def path=(new_path)
         | 
| 733 | 
            -
                  @path = (new_path || "")
         | 
| 1676 | 
            +
                  @path = (new_path || "").to_str
         | 
| 1677 | 
            +
                  if @path != "" && @path[0..0] != "/" && host != nil
         | 
| 1678 | 
            +
                    @path = "/#{@path}"
         | 
| 1679 | 
            +
                  end
         | 
| 1680 | 
            +
             | 
| 1681 | 
            +
                  # Reset dependant values
         | 
| 1682 | 
            +
                  @normalized_path = nil
         | 
| 734 1683 | 
             
                end
         | 
| 735 1684 |  | 
| 736 | 
            -
                 | 
| 737 | 
            -
                #  | 
| 1685 | 
            +
                ##
         | 
| 1686 | 
            +
                # The basename, if any, of the file in the path component.
         | 
| 1687 | 
            +
                #
         | 
| 1688 | 
            +
                # @return [String] The path's basename.
         | 
| 738 1689 | 
             
                def basename
         | 
| 739 1690 | 
             
                  # Path cannot be nil
         | 
| 740 1691 | 
             
                  return File.basename(self.path).gsub(/;[^\/]*$/, "")
         | 
| 741 1692 | 
             
                end
         | 
| 742 | 
            -
             | 
| 743 | 
            -
                 | 
| 744 | 
            -
                #  | 
| 745 | 
            -
                #  | 
| 1693 | 
            +
             | 
| 1694 | 
            +
                ##
         | 
| 1695 | 
            +
                # The extname, if any, of the file in the path component.
         | 
| 1696 | 
            +
                # Empty string if there is no extension.
         | 
| 1697 | 
            +
                #
         | 
| 1698 | 
            +
                # @return [String] The path's extname.
         | 
| 746 1699 | 
             
                def extname
         | 
| 747 1700 | 
             
                  return nil unless self.path
         | 
| 748 1701 | 
             
                  return File.extname(self.basename)
         | 
| 749 1702 | 
             
                end
         | 
| 750 | 
            -
             | 
| 751 | 
            -
                 | 
| 1703 | 
            +
             | 
| 1704 | 
            +
                ##
         | 
| 1705 | 
            +
                # The query component for this URI.
         | 
| 1706 | 
            +
                #
         | 
| 1707 | 
            +
                # @return [String] The query component.
         | 
| 752 1708 | 
             
                def query
         | 
| 753 1709 | 
             
                  return @query
         | 
| 754 1710 | 
             
                end
         | 
| 755 | 
            -
             | 
| 756 | 
            -
                 | 
| 1711 | 
            +
             | 
| 1712 | 
            +
                ##
         | 
| 1713 | 
            +
                # The query component for this URI, normalized.
         | 
| 1714 | 
            +
                #
         | 
| 1715 | 
            +
                # @return [String] The query component, normalized.
         | 
| 1716 | 
            +
                def normalized_query
         | 
| 1717 | 
            +
                  @normalized_query ||= (self.query ? self.query.strip : nil)
         | 
| 1718 | 
            +
                end
         | 
| 1719 | 
            +
             | 
| 1720 | 
            +
                ##
         | 
| 1721 | 
            +
                # Sets the query component for this URI.
         | 
| 1722 | 
            +
                #
         | 
| 1723 | 
            +
                # @param [String, #to_str] new_query The new query component.
         | 
| 757 1724 | 
             
                def query=(new_query)
         | 
| 758 | 
            -
                  @query = new_query
         | 
| 1725 | 
            +
                  @query = new_query.to_str
         | 
| 1726 | 
            +
             | 
| 1727 | 
            +
                  # Reset dependant values
         | 
| 1728 | 
            +
                  @normalized_query = nil
         | 
| 1729 | 
            +
                end
         | 
| 1730 | 
            +
             | 
| 1731 | 
            +
                ##
         | 
| 1732 | 
            +
                # Converts the query component to a Hash value.
         | 
| 1733 | 
            +
                #
         | 
| 1734 | 
            +
                # @option [Symbol] notation
         | 
| 1735 | 
            +
                #   May be one of <tt>:flat</tt>, <tt>:dot</tt>, or <tt>:subscript</tt>.
         | 
| 1736 | 
            +
                #   The <tt>:dot</tt> notation is not supported for assignment.
         | 
| 1737 | 
            +
                #   Default value is <tt>:subscript</tt>.
         | 
| 1738 | 
            +
                #
         | 
| 1739 | 
            +
                # @return [Hash] The query string parsed as a Hash object.
         | 
| 1740 | 
            +
                #
         | 
| 1741 | 
            +
                # @example
         | 
| 1742 | 
            +
                #   Addressable::URI.parse("?one=1&two=2&three=3").query_values
         | 
| 1743 | 
            +
                #   #=> {"one" => "1", "two" => "2", "three" => "3"}
         | 
| 1744 | 
            +
                #   Addressable::URI.parse("?one[two][three]=four").query_values
         | 
| 1745 | 
            +
                #   #=> {"one" => {"two" => {"three" => "four"}}}
         | 
| 1746 | 
            +
                #   Addressable::URI.parse("?one.two.three=four").query_values(
         | 
| 1747 | 
            +
                #     :notation => :dot
         | 
| 1748 | 
            +
                #   )
         | 
| 1749 | 
            +
                #   #=> {"one" => {"two" => {"three" => "four"}}}
         | 
| 1750 | 
            +
                #   Addressable::URI.parse("?one[two][three]=four").query_values(
         | 
| 1751 | 
            +
                #     :notation => :flat
         | 
| 1752 | 
            +
                #   )
         | 
| 1753 | 
            +
                #   #=> {"one[two][three]" => "four"}
         | 
| 1754 | 
            +
                #   Addressable::URI.parse("?one.two.three=four").query_values(
         | 
| 1755 | 
            +
                #     :notation => :flat
         | 
| 1756 | 
            +
                #   )
         | 
| 1757 | 
            +
                #   #=> {"one.two.three" => "four"}
         | 
| 1758 | 
            +
                #   Addressable::URI.parse(
         | 
| 1759 | 
            +
                #     "?one[two][three][]=four&one[two][three][]=five"
         | 
| 1760 | 
            +
                #   ).query_values
         | 
| 1761 | 
            +
                #   #=> {"one" => {"two" => {"three" => ["four", "five"]}}}
         | 
| 1762 | 
            +
                def query_values(options={})
         | 
| 1763 | 
            +
                  defaults = {:notation => :subscript}
         | 
| 1764 | 
            +
                  options = defaults.merge(options)
         | 
| 1765 | 
            +
                  if ![:flat, :dot, :subscript].include?(options[:notation])
         | 
| 1766 | 
            +
                    raise ArgumentError,
         | 
| 1767 | 
            +
                      "Invalid notation. Must be one of: [:flat, :dot, :subscript]."
         | 
| 1768 | 
            +
                  end
         | 
| 1769 | 
            +
                  return nil if self.query == nil
         | 
| 1770 | 
            +
                  return (self.query.split("&").map do |pair|
         | 
| 1771 | 
            +
                    pair.split("=")
         | 
| 1772 | 
            +
                  end).inject({}) do |accumulator, pair|
         | 
| 1773 | 
            +
                    key, value = pair
         | 
| 1774 | 
            +
                    value = true if value.nil?
         | 
| 1775 | 
            +
                    key = self.class.unencode_component(key)
         | 
| 1776 | 
            +
                    if value != true
         | 
| 1777 | 
            +
                      value = self.class.unencode_component(value).gsub(/\+/, " ")
         | 
| 1778 | 
            +
                    end
         | 
| 1779 | 
            +
                    if options[:notation] == :flat
         | 
| 1780 | 
            +
                      if accumulator[key]
         | 
| 1781 | 
            +
                        raise ArgumentError, "Key was repeated: #{key.inspect}"
         | 
| 1782 | 
            +
                      end
         | 
| 1783 | 
            +
                      accumulator[key] = value
         | 
| 1784 | 
            +
                    else
         | 
| 1785 | 
            +
                      if options[:notation] == :dot
         | 
| 1786 | 
            +
                        array_value = false
         | 
| 1787 | 
            +
                        subkeys = key.split(".")
         | 
| 1788 | 
            +
                      elsif options[:notation] == :subscript
         | 
| 1789 | 
            +
                        array_value = !!(key =~ /\[\]$/)
         | 
| 1790 | 
            +
                        subkeys = key.split(/[\[\]]+/)
         | 
| 1791 | 
            +
                      end
         | 
| 1792 | 
            +
                      current_hash = accumulator
         | 
| 1793 | 
            +
                      for i in 0...(subkeys.size - 1)
         | 
| 1794 | 
            +
                        subkey = subkeys[i]
         | 
| 1795 | 
            +
                        current_hash[subkey] = {} unless current_hash[subkey]
         | 
| 1796 | 
            +
                        current_hash = current_hash[subkey]
         | 
| 1797 | 
            +
                      end
         | 
| 1798 | 
            +
                      if array_value
         | 
| 1799 | 
            +
                        current_hash[subkeys.last] = [] unless current_hash[subkeys.last]
         | 
| 1800 | 
            +
                        current_hash[subkeys.last] << value
         | 
| 1801 | 
            +
                      else
         | 
| 1802 | 
            +
                        current_hash[subkeys.last] = value
         | 
| 1803 | 
            +
                      end
         | 
| 1804 | 
            +
                    end
         | 
| 1805 | 
            +
                    accumulator
         | 
| 1806 | 
            +
                  end
         | 
| 1807 | 
            +
                end
         | 
| 1808 | 
            +
             | 
| 1809 | 
            +
                ##
         | 
| 1810 | 
            +
                # Sets the query component for this URI from a Hash object.
         | 
| 1811 | 
            +
                #
         | 
| 1812 | 
            +
                # @param [Hash, #to_hash] new_query_values The new query values.
         | 
| 1813 | 
            +
                def query_values=(new_query_values)
         | 
| 1814 | 
            +
                  @query = (new_query_values.to_hash.inject([]) do |accumulator, pair|
         | 
| 1815 | 
            +
                    key, value = pair
         | 
| 1816 | 
            +
                    key = self.class.encode_component(key, CharacterClasses::UNRESERVED)
         | 
| 1817 | 
            +
                    if value == true
         | 
| 1818 | 
            +
                      accumulator << "#{key}"
         | 
| 1819 | 
            +
                    else
         | 
| 1820 | 
            +
                      value = self.class.encode_component(
         | 
| 1821 | 
            +
                        value, CharacterClasses::UNRESERVED)
         | 
| 1822 | 
            +
                      accumulator << "#{key}=#{value}"
         | 
| 1823 | 
            +
                    end
         | 
| 1824 | 
            +
                  end).join("&")
         | 
| 1825 | 
            +
             | 
| 1826 | 
            +
                  # Reset dependant values
         | 
| 1827 | 
            +
                  @normalized_query = nil
         | 
| 759 1828 | 
             
                end
         | 
| 760 | 
            -
             | 
| 761 | 
            -
                 | 
| 1829 | 
            +
             | 
| 1830 | 
            +
                ##
         | 
| 1831 | 
            +
                # The fragment component for this URI.
         | 
| 1832 | 
            +
                #
         | 
| 1833 | 
            +
                # @return [String] The fragment component.
         | 
| 762 1834 | 
             
                def fragment
         | 
| 763 1835 | 
             
                  return @fragment
         | 
| 764 1836 | 
             
                end
         | 
| 765 | 
            -
             | 
| 766 | 
            -
                 | 
| 1837 | 
            +
             | 
| 1838 | 
            +
                ##
         | 
| 1839 | 
            +
                # The fragment component for this URI, normalized.
         | 
| 1840 | 
            +
                #
         | 
| 1841 | 
            +
                # @return [String] The fragment component, normalized.
         | 
| 1842 | 
            +
                def normalized_fragment
         | 
| 1843 | 
            +
                  @normalized_fragment ||= (self.fragment ? self.fragment.strip : nil)
         | 
| 1844 | 
            +
                end
         | 
| 1845 | 
            +
             | 
| 1846 | 
            +
                ##
         | 
| 1847 | 
            +
                # Sets the fragment component for this URI.
         | 
| 1848 | 
            +
                #
         | 
| 1849 | 
            +
                # @param [String, #to_str] new_fragment The new fragment component.
         | 
| 767 1850 | 
             
                def fragment=(new_fragment)
         | 
| 768 | 
            -
                  @fragment = new_fragment
         | 
| 1851 | 
            +
                  @fragment = new_fragment ? new_fragment.to_str : nil
         | 
| 1852 | 
            +
             | 
| 1853 | 
            +
                  # Reset dependant values
         | 
| 1854 | 
            +
                  @normalized_fragment = nil
         | 
| 769 1855 | 
             
                end
         | 
| 770 | 
            -
             | 
| 771 | 
            -
                 | 
| 1856 | 
            +
             | 
| 1857 | 
            +
                ##
         | 
| 1858 | 
            +
                # Determines if the scheme indicates an IP-based protocol.
         | 
| 1859 | 
            +
                #
         | 
| 1860 | 
            +
                # @return [TrueClass, FalseClass]
         | 
| 1861 | 
            +
                #   <tt>true</tt> if the scheme indicates an IP-based protocol.
         | 
| 1862 | 
            +
                #   <tt>false</tt> otherwise.
         | 
| 772 1863 | 
             
                def ip_based?
         | 
| 773 1864 | 
             
                  if self.scheme
         | 
| 774 1865 | 
             
                    return self.class.ip_based_schemes.include?(
         | 
| @@ -776,26 +1867,45 @@ module Addressable | |
| 776 1867 | 
             
                  end
         | 
| 777 1868 | 
             
                  return false
         | 
| 778 1869 | 
             
                end
         | 
| 779 | 
            -
             | 
| 780 | 
            -
                 | 
| 1870 | 
            +
             | 
| 1871 | 
            +
                ##
         | 
| 1872 | 
            +
                # Determines if the URI is relative.
         | 
| 1873 | 
            +
                #
         | 
| 1874 | 
            +
                # @return [TrueClass, FalseClass]
         | 
| 1875 | 
            +
                #   <tt>true</tt> if the URI is relative.
         | 
| 1876 | 
            +
                #   <tt>false</tt> otherwise.
         | 
| 781 1877 | 
             
                def relative?
         | 
| 782 1878 | 
             
                  return self.scheme.nil?
         | 
| 783 1879 | 
             
                end
         | 
| 784 | 
            -
             | 
| 785 | 
            -
                 | 
| 1880 | 
            +
             | 
| 1881 | 
            +
                ##
         | 
| 1882 | 
            +
                # Determines if the URI is absolute.
         | 
| 1883 | 
            +
                #
         | 
| 1884 | 
            +
                # @return [TrueClass, FalseClass]
         | 
| 1885 | 
            +
                #   <tt>true</tt> if the URI is absolute.
         | 
| 1886 | 
            +
                #   <tt>false</tt> otherwise.
         | 
| 786 1887 | 
             
                def absolute?
         | 
| 787 1888 | 
             
                  return !relative?
         | 
| 788 1889 | 
             
                end
         | 
| 789 | 
            -
             | 
| 1890 | 
            +
             | 
| 1891 | 
            +
                ##
         | 
| 790 1892 | 
             
                # Joins two URIs together.
         | 
| 791 | 
            -
                 | 
| 1893 | 
            +
                #
         | 
| 1894 | 
            +
                # @param [String, Addressable::URI, #to_str] The URI to join with.
         | 
| 1895 | 
            +
                #
         | 
| 1896 | 
            +
                # @return [Addressable::URI] The joined URI.
         | 
| 1897 | 
            +
                def join(uri)
         | 
| 1898 | 
            +
                  if !uri.respond_to?(:to_str)
         | 
| 1899 | 
            +
                    raise TypeError, "Can't convert #{uri.class} into String."
         | 
| 1900 | 
            +
                  end
         | 
| 792 1901 | 
             
                  if !uri.kind_of?(self.class)
         | 
| 793 | 
            -
                     | 
| 1902 | 
            +
                    # Otherwise, convert to a String, then parse.
         | 
| 1903 | 
            +
                    uri = self.class.parse(uri.to_str)
         | 
| 794 1904 | 
             
                  end
         | 
| 795 1905 | 
             
                  if uri.to_s == ""
         | 
| 796 1906 | 
             
                    return self.dup
         | 
| 797 1907 | 
             
                  end
         | 
| 798 | 
            -
             | 
| 1908 | 
            +
             | 
| 799 1909 | 
             
                  joined_scheme = nil
         | 
| 800 1910 | 
             
                  joined_user = nil
         | 
| 801 1911 | 
             
                  joined_password = nil
         | 
| @@ -804,14 +1914,14 @@ module Addressable | |
| 804 1914 | 
             
                  joined_path = nil
         | 
| 805 1915 | 
             
                  joined_query = nil
         | 
| 806 1916 | 
             
                  joined_fragment = nil
         | 
| 807 | 
            -
             | 
| 1917 | 
            +
             | 
| 808 1918 | 
             
                  # Section 5.2.2 of RFC 3986
         | 
| 809 1919 | 
             
                  if uri.scheme != nil
         | 
| 810 1920 | 
             
                    joined_scheme = uri.scheme
         | 
| 811 1921 | 
             
                    joined_user = uri.user
         | 
| 812 1922 | 
             
                    joined_password = uri.password
         | 
| 813 1923 | 
             
                    joined_host = uri.host
         | 
| 814 | 
            -
                    joined_port = uri. | 
| 1924 | 
            +
                    joined_port = uri.port
         | 
| 815 1925 | 
             
                    joined_path = self.class.normalize_path(uri.path)
         | 
| 816 1926 | 
             
                    joined_query = uri.query
         | 
| 817 1927 | 
             
                  else
         | 
| @@ -819,7 +1929,7 @@ module Addressable | |
| 819 1929 | 
             
                      joined_user = uri.user
         | 
| 820 1930 | 
             
                      joined_password = uri.password
         | 
| 821 1931 | 
             
                      joined_host = uri.host
         | 
| 822 | 
            -
                      joined_port = uri. | 
| 1932 | 
            +
                      joined_port = uri.port
         | 
| 823 1933 | 
             
                      joined_path = self.class.normalize_path(uri.path)
         | 
| 824 1934 | 
             
                      joined_query = uri.query
         | 
| 825 1935 | 
             
                    else
         | 
| @@ -846,13 +1956,13 @@ module Addressable | |
| 846 1956 | 
             
                          else
         | 
| 847 1957 | 
             
                            base_path = ""
         | 
| 848 1958 | 
             
                          end
         | 
| 849 | 
            -
             | 
| 1959 | 
            +
             | 
| 850 1960 | 
             
                          # If the base path is empty and an authority segment has been
         | 
| 851 1961 | 
             
                          # defined, use a base path of "/"
         | 
| 852 1962 | 
             
                          if base_path == "" && self.authority != nil
         | 
| 853 1963 | 
             
                            base_path = "/"
         | 
| 854 1964 | 
             
                          end
         | 
| 855 | 
            -
             | 
| 1965 | 
            +
             | 
| 856 1966 | 
             
                          joined_path = self.class.normalize_path(base_path + uri.path)
         | 
| 857 1967 | 
             
                        end
         | 
| 858 1968 | 
             
                        joined_query = uri.query
         | 
| @@ -860,39 +1970,124 @@ module Addressable | |
| 860 1970 | 
             
                      joined_user = self.user
         | 
| 861 1971 | 
             
                      joined_password = self.password
         | 
| 862 1972 | 
             
                      joined_host = self.host
         | 
| 863 | 
            -
                      joined_port = self. | 
| 1973 | 
            +
                      joined_port = self.port
         | 
| 864 1974 | 
             
                    end
         | 
| 865 1975 | 
             
                    joined_scheme = self.scheme
         | 
| 866 1976 | 
             
                  end
         | 
| 867 1977 | 
             
                  joined_fragment = uri.fragment
         | 
| 868 | 
            -
             | 
| 1978 | 
            +
             | 
| 869 1979 | 
             
                  return Addressable::URI.new(
         | 
| 870 | 
            -
                    joined_scheme,
         | 
| 871 | 
            -
                    joined_user,
         | 
| 872 | 
            -
                    joined_password,
         | 
| 873 | 
            -
                    joined_host,
         | 
| 874 | 
            -
                    joined_port,
         | 
| 875 | 
            -
                    joined_path,
         | 
| 876 | 
            -
                    joined_query,
         | 
| 877 | 
            -
                    joined_fragment
         | 
| 1980 | 
            +
                    :scheme => joined_scheme,
         | 
| 1981 | 
            +
                    :user => joined_user,
         | 
| 1982 | 
            +
                    :password => joined_password,
         | 
| 1983 | 
            +
                    :host => joined_host,
         | 
| 1984 | 
            +
                    :port => joined_port,
         | 
| 1985 | 
            +
                    :path => joined_path,
         | 
| 1986 | 
            +
                    :query => joined_query,
         | 
| 1987 | 
            +
                    :fragment => joined_fragment
         | 
| 878 1988 | 
             
                  )
         | 
| 879 1989 | 
             
                end
         | 
| 880 | 
            -
                
         | 
| 881 | 
            -
             | 
| 882 | 
            -
                 | 
| 883 | 
            -
             | 
| 1990 | 
            +
                alias_method :+, :join
         | 
| 1991 | 
            +
             | 
| 1992 | 
            +
                ##
         | 
| 1993 | 
            +
                # Destructive form of <tt>join</tt>.
         | 
| 1994 | 
            +
                #
         | 
| 1995 | 
            +
                # @param [String, Addressable::URI, #to_str] The URI to join with.
         | 
| 1996 | 
            +
                #
         | 
| 1997 | 
            +
                # @return [Addressable::URI] The joined URI.
         | 
| 1998 | 
            +
                #
         | 
| 1999 | 
            +
                # @see Addressable::URI#join
         | 
| 2000 | 
            +
                def join!(uri)
         | 
| 2001 | 
            +
                  replace_self(self.join(uri))
         | 
| 2002 | 
            +
                end
         | 
| 2003 | 
            +
             | 
| 2004 | 
            +
                ##
         | 
| 2005 | 
            +
                # Merges a URI with a <tt>Hash</tt> of components.
         | 
| 2006 | 
            +
                # This method has different behavior from <tt>join</tt>.  Any components
         | 
| 2007 | 
            +
                # present in the <tt>hash</tt> parameter will override the original
         | 
| 2008 | 
            +
                # components.  The path component is not treated specially.
         | 
| 2009 | 
            +
                #
         | 
| 2010 | 
            +
                # @param [Hash, Addressable::URI, #to_hash] The components to merge with.
         | 
| 2011 | 
            +
                #
         | 
| 2012 | 
            +
                # @return [Addressable::URI] The merged URI.
         | 
| 2013 | 
            +
                #
         | 
| 2014 | 
            +
                # @see Hash#merge
         | 
| 2015 | 
            +
                def merge(hash)
         | 
| 2016 | 
            +
                  if !hash.respond_to?(:to_hash)
         | 
| 2017 | 
            +
                    raise TypeError, "Can't convert #{hash.class} into Hash."
         | 
| 2018 | 
            +
                  end
         | 
| 2019 | 
            +
                  hash = hash.to_hash
         | 
| 2020 | 
            +
             | 
| 2021 | 
            +
                  if hash.has_key?(:authority)
         | 
| 2022 | 
            +
                    if (hash.keys & [:userinfo, :user, :password, :host, :port]).any?
         | 
| 2023 | 
            +
                      raise ArgumentError,
         | 
| 2024 | 
            +
                        "Cannot specify both an authority and any of the components " +
         | 
| 2025 | 
            +
                        "within the authority."
         | 
| 2026 | 
            +
                    end
         | 
| 2027 | 
            +
                  end
         | 
| 2028 | 
            +
                  if hash.has_key?(:userinfo)
         | 
| 2029 | 
            +
                    if (hash.keys & [:user, :password]).any?
         | 
| 2030 | 
            +
                      raise ArgumentError,
         | 
| 2031 | 
            +
                        "Cannot specify both a userinfo and either the user or password."
         | 
| 2032 | 
            +
                    end
         | 
| 2033 | 
            +
                  end
         | 
| 2034 | 
            +
             | 
| 2035 | 
            +
                  uri = Addressable::URI.new
         | 
| 2036 | 
            +
                  uri.validation_deferred = true
         | 
| 2037 | 
            +
                  uri.scheme =
         | 
| 2038 | 
            +
                    hash.has_key?(:scheme) ? hash[:scheme] : self.scheme
         | 
| 2039 | 
            +
                  if hash.has_key?(:authority)
         | 
| 2040 | 
            +
                    uri.authority =
         | 
| 2041 | 
            +
                      hash.has_key?(:authority) ? hash[:authority] : self.authority
         | 
| 2042 | 
            +
                  end
         | 
| 2043 | 
            +
                  if hash.has_key?(:userinfo)
         | 
| 2044 | 
            +
                    uri.userinfo =
         | 
| 2045 | 
            +
                      hash.has_key?(:userinfo) ? hash[:userinfo] : self.userinfo
         | 
| 2046 | 
            +
                  end
         | 
| 2047 | 
            +
                  if !hash.has_key?(:userinfo) && !hash.has_key?(:authority)
         | 
| 2048 | 
            +
                    uri.user =
         | 
| 2049 | 
            +
                      hash.has_key?(:user) ? hash[:user] : self.user
         | 
| 2050 | 
            +
                    uri.password =
         | 
| 2051 | 
            +
                      hash.has_key?(:password) ? hash[:password] : self.password
         | 
| 2052 | 
            +
                  end
         | 
| 2053 | 
            +
                  if !hash.has_key?(:authority)
         | 
| 2054 | 
            +
                    uri.host =
         | 
| 2055 | 
            +
                      hash.has_key?(:host) ? hash[:host] : self.host
         | 
| 2056 | 
            +
                    uri.port =
         | 
| 2057 | 
            +
                      hash.has_key?(:port) ? hash[:port] : self.port
         | 
| 2058 | 
            +
                  end
         | 
| 2059 | 
            +
                  uri.path =
         | 
| 2060 | 
            +
                    hash.has_key?(:path) ? hash[:path] : self.path
         | 
| 2061 | 
            +
                  uri.query =
         | 
| 2062 | 
            +
                    hash.has_key?(:query) ? hash[:query] : self.query
         | 
| 2063 | 
            +
                  uri.fragment =
         | 
| 2064 | 
            +
                    hash.has_key?(:fragment) ? hash[:fragment] : self.fragment
         | 
| 2065 | 
            +
                  uri.validation_deferred = false
         | 
| 2066 | 
            +
             | 
| 2067 | 
            +
                  return uri
         | 
| 884 2068 | 
             
                end
         | 
| 885 | 
            -
             | 
| 886 | 
            -
                
         | 
| 887 | 
            -
                # Destructive form of merge | 
| 2069 | 
            +
             | 
| 2070 | 
            +
                ##
         | 
| 2071 | 
            +
                # Destructive form of <tt>merge</tt>.
         | 
| 2072 | 
            +
                #
         | 
| 2073 | 
            +
                # @param [Hash, Addressable::URI, #to_hash] The components to merge with.
         | 
| 2074 | 
            +
                #
         | 
| 2075 | 
            +
                # @return [Addressable::URI] The merged URI.
         | 
| 2076 | 
            +
                #
         | 
| 2077 | 
            +
                # @see Addressable::URI#merge
         | 
| 888 2078 | 
             
                def merge!(uri)
         | 
| 889 2079 | 
             
                  replace_self(self.merge(uri))
         | 
| 890 2080 | 
             
                end
         | 
| 891 | 
            -
             | 
| 892 | 
            -
                
         | 
| 2081 | 
            +
             | 
| 2082 | 
            +
                ##
         | 
| 893 2083 | 
             
                # Returns the shortest normalized relative form of this URI that uses the
         | 
| 894 2084 | 
             
                # supplied URI as a base for resolution.  Returns an absolute URI if
         | 
| 895 | 
            -
                # necessary.
         | 
| 2085 | 
            +
                # necessary.  This is effectively the opposite of <tt>route_to</tt>.
         | 
| 2086 | 
            +
                #
         | 
| 2087 | 
            +
                # @param [String, Addressable::URI, #to_str] uri The URI to route from.
         | 
| 2088 | 
            +
                #
         | 
| 2089 | 
            +
                # @return [Addressable::URI]
         | 
| 2090 | 
            +
                #   The normalized relative URI that is equivalent to the original URI.
         | 
| 896 2091 | 
             
                def route_from(uri)
         | 
| 897 2092 | 
             
                  uri = self.class.parse(uri).normalize
         | 
| 898 2093 | 
             
                  normalized_self = self.normalize
         | 
| @@ -905,50 +2100,57 @@ module Addressable | |
| 905 2100 | 
             
                  if normalized_self == uri
         | 
| 906 2101 | 
             
                    return Addressable::URI.parse("##{normalized_self.fragment}")
         | 
| 907 2102 | 
             
                  end
         | 
| 908 | 
            -
                   | 
| 2103 | 
            +
                  components = normalized_self.to_hash
         | 
| 909 2104 | 
             
                  if normalized_self.scheme == uri.scheme
         | 
| 910 | 
            -
                     | 
| 2105 | 
            +
                    components[:scheme] = nil
         | 
| 911 2106 | 
             
                    if normalized_self.authority == uri.authority
         | 
| 912 | 
            -
                       | 
| 913 | 
            -
                       | 
| 914 | 
            -
                       | 
| 915 | 
            -
                       | 
| 2107 | 
            +
                      components[:user] = nil
         | 
| 2108 | 
            +
                      components[:password] = nil
         | 
| 2109 | 
            +
                      components[:host] = nil
         | 
| 2110 | 
            +
                      components[:port] = nil
         | 
| 916 2111 | 
             
                      if normalized_self.path == uri.path
         | 
| 917 | 
            -
                         | 
| 2112 | 
            +
                        components[:path] = nil
         | 
| 918 2113 | 
             
                        if normalized_self.query == uri.query
         | 
| 919 | 
            -
                           | 
| 2114 | 
            +
                          components[:query] = nil
         | 
| 920 2115 | 
             
                        end
         | 
| 921 2116 | 
             
                      else
         | 
| 922 2117 | 
             
                        if uri.path != "/"
         | 
| 923 | 
            -
                           | 
| 2118 | 
            +
                          components[:path].gsub!(
         | 
| 924 2119 | 
             
                            Regexp.new("^" + Regexp.escape(uri.path)), "")
         | 
| 925 2120 | 
             
                        end
         | 
| 926 2121 | 
             
                      end
         | 
| 927 2122 | 
             
                    end
         | 
| 928 2123 | 
             
                  end
         | 
| 929 2124 | 
             
                  # Avoid network-path references.
         | 
| 930 | 
            -
                  if  | 
| 931 | 
            -
                     | 
| 2125 | 
            +
                  if components[:host] != nil
         | 
| 2126 | 
            +
                    components[:scheme] = normalized_self.scheme
         | 
| 932 2127 | 
             
                  end
         | 
| 933 2128 | 
             
                  return Addressable::URI.new(
         | 
| 934 | 
            -
                     | 
| 935 | 
            -
                     | 
| 936 | 
            -
                     | 
| 937 | 
            -
                     | 
| 938 | 
            -
                     | 
| 939 | 
            -
                     | 
| 940 | 
            -
                     | 
| 941 | 
            -
                     | 
| 2129 | 
            +
                    :scheme => components[:scheme],
         | 
| 2130 | 
            +
                    :user => components[:user],
         | 
| 2131 | 
            +
                    :password => components[:password],
         | 
| 2132 | 
            +
                    :host => components[:host],
         | 
| 2133 | 
            +
                    :port => components[:port],
         | 
| 2134 | 
            +
                    :path => components[:path],
         | 
| 2135 | 
            +
                    :query => components[:query],
         | 
| 2136 | 
            +
                    :fragment => components[:fragment]
         | 
| 942 2137 | 
             
                  )
         | 
| 943 2138 | 
             
                end
         | 
| 944 | 
            -
             | 
| 2139 | 
            +
             | 
| 2140 | 
            +
                ##
         | 
| 945 2141 | 
             
                # Returns the shortest normalized relative form of the supplied URI that
         | 
| 946 2142 | 
             
                # uses this URI as a base for resolution.  Returns an absolute URI if
         | 
| 947 | 
            -
                # necessary.
         | 
| 2143 | 
            +
                # necessary.  This is effectively the opposite of <tt>route_from</tt>.
         | 
| 2144 | 
            +
                #
         | 
| 2145 | 
            +
                # @param [String, Addressable::URI, #to_str] uri The URI to route to.
         | 
| 2146 | 
            +
                #
         | 
| 2147 | 
            +
                # @return [Addressable::URI]
         | 
| 2148 | 
            +
                #   The normalized relative URI that is equivalent to the supplied URI.
         | 
| 948 2149 | 
             
                def route_to(uri)
         | 
| 949 2150 | 
             
                  return self.class.parse(uri).route_from(self)
         | 
| 950 2151 | 
             
                end
         | 
| 951 | 
            -
             | 
| 2152 | 
            +
             | 
| 2153 | 
            +
                ##
         | 
| 952 2154 | 
             
                # Returns a normalized URI object.
         | 
| 953 2155 | 
             
                #
         | 
| 954 2156 | 
             
                # NOTE: This method does not attempt to fully conform to specifications.
         | 
| @@ -956,154 +2158,176 @@ module Addressable | |
| 956 2158 | 
             
                # specifications, and also to deal with caching issues since several
         | 
| 957 2159 | 
             
                # different URIs may represent the same resource and should not be
         | 
| 958 2160 | 
             
                # cached multiple times.
         | 
| 2161 | 
            +
                #
         | 
| 2162 | 
            +
                # @return [Addressable::URI] The normalized URI.
         | 
| 959 2163 | 
             
                def normalize
         | 
| 960 | 
            -
                   | 
| 961 | 
            -
                   | 
| 962 | 
            -
                  normalized_scheme = "svn+ssh" if normalized_scheme == "ssh+svn"
         | 
| 2164 | 
            +
                  # This is a special exception for the frequently misused feed
         | 
| 2165 | 
            +
                  # URI scheme.
         | 
| 963 2166 | 
             
                  if normalized_scheme == "feed"
         | 
| 964 2167 | 
             
                    if self.to_s =~ /^feed:\/*http:\/*/
         | 
| 965 2168 | 
             
                      return self.class.parse(
         | 
| 966 | 
            -
                        self.to_s | 
| 967 | 
            -
             | 
| 968 | 
            -
                  end
         | 
| 969 | 
            -
                  normalized_user = nil
         | 
| 970 | 
            -
                  normalized_user = self.user.strip if self.user != nil
         | 
| 971 | 
            -
                  normalized_password = nil
         | 
| 972 | 
            -
                  normalized_password = self.password.strip if self.password != nil
         | 
| 973 | 
            -
                  
         | 
| 974 | 
            -
                  # If we are using http or https and user/password are blank,
         | 
| 975 | 
            -
                  # then we remove them
         | 
| 976 | 
            -
                  if normalized_scheme =~ /https?/ && normalized_user == "" &&
         | 
| 977 | 
            -
                      (!normalized_password || normalized_password == "")
         | 
| 978 | 
            -
                    normalized_user = nil
         | 
| 979 | 
            -
                    normalized_password = nil
         | 
| 980 | 
            -
                  end
         | 
| 981 | 
            -
                  
         | 
| 982 | 
            -
                  normalized_host = nil
         | 
| 983 | 
            -
                  normalized_host = self.host.strip.downcase if self.host != nil
         | 
| 984 | 
            -
                  if normalized_host != nil
         | 
| 985 | 
            -
                    begin
         | 
| 986 | 
            -
                      normalized_host = URI::IDNA.to_ascii(normalized_host)
         | 
| 987 | 
            -
                    rescue Exception
         | 
| 988 | 
            -
                      nil
         | 
| 989 | 
            -
                    end
         | 
| 990 | 
            -
                    if normalized_host[-1..-1] == "."
         | 
| 991 | 
            -
                      normalized_host = normalized_host[0...-1]
         | 
| 992 | 
            -
                    end
         | 
| 993 | 
            -
                  end
         | 
| 994 | 
            -
                  
         | 
| 995 | 
            -
                  normalized_port = self.port
         | 
| 996 | 
            -
                  if self.class.scheme_mapping[normalized_scheme] == normalized_port
         | 
| 997 | 
            -
                    normalized_port = nil
         | 
| 998 | 
            -
                  end
         | 
| 999 | 
            -
                  normalized_path = nil
         | 
| 1000 | 
            -
                  normalized_path = self.path.strip if self.path != nil
         | 
| 1001 | 
            -
                  if normalized_path != nil
         | 
| 1002 | 
            -
                    normalized_path = self.class.normalize_path(normalized_path)
         | 
| 1003 | 
            -
                  end
         | 
| 1004 | 
            -
                  if normalized_path == ""
         | 
| 1005 | 
            -
                    if ["http", "https", "ftp", "tftp"].include?(normalized_scheme)
         | 
| 1006 | 
            -
                      normalized_path = "/"
         | 
| 2169 | 
            +
                        self.to_s[/^feed:\/*(http:\/*.*)/, 1]
         | 
| 2170 | 
            +
                      ).normalize
         | 
| 1007 2171 | 
             
                    end
         | 
| 1008 2172 | 
             
                  end
         | 
| 1009 2173 |  | 
| 1010 | 
            -
                   | 
| 1011 | 
            -
             | 
| 1012 | 
            -
             | 
| 1013 | 
            -
             | 
| 1014 | 
            -
             | 
| 1015 | 
            -
             | 
| 1016 | 
            -
             | 
| 1017 | 
            -
             | 
| 1018 | 
            -
             | 
| 1019 | 
            -
             | 
| 1020 | 
            -
                      normalized_host,
         | 
| 1021 | 
            -
                      normalized_port,
         | 
| 1022 | 
            -
                      normalized_path,
         | 
| 1023 | 
            -
                      normalized_query,
         | 
| 1024 | 
            -
                      normalized_fragment
         | 
| 1025 | 
            -
                    )))
         | 
| 2174 | 
            +
                  return Addressable::URI.normalized_encode(
         | 
| 2175 | 
            +
                    Addressable::URI.new(
         | 
| 2176 | 
            +
                      :scheme => normalized_scheme,
         | 
| 2177 | 
            +
                      :authority => normalized_authority,
         | 
| 2178 | 
            +
                      :path => normalized_path,
         | 
| 2179 | 
            +
                      :query => normalized_query,
         | 
| 2180 | 
            +
                      :fragment => normalized_fragment
         | 
| 2181 | 
            +
                    ),
         | 
| 2182 | 
            +
                    ::Addressable::URI
         | 
| 2183 | 
            +
                  )
         | 
| 1026 2184 | 
             
                end
         | 
| 1027 2185 |  | 
| 2186 | 
            +
                ##
         | 
| 1028 2187 | 
             
                # Destructively normalizes this URI object.
         | 
| 2188 | 
            +
                #
         | 
| 2189 | 
            +
                # @return [Addressable::URI] The normalized URI.
         | 
| 2190 | 
            +
                #
         | 
| 2191 | 
            +
                # @see Addressable::URI#normalize
         | 
| 1029 2192 | 
             
                def normalize!
         | 
| 1030 2193 | 
             
                  replace_self(self.normalize)
         | 
| 1031 2194 | 
             
                end
         | 
| 1032 | 
            -
             | 
| 2195 | 
            +
             | 
| 2196 | 
            +
                ##
         | 
| 1033 2197 | 
             
                # Creates a URI suitable for display to users.  If semantic attacks are
         | 
| 1034 2198 | 
             
                # likely, the application should try to detect these and warn the user.
         | 
| 1035 | 
            -
                # See RFC 3986 | 
| 2199 | 
            +
                # See <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>,
         | 
| 2200 | 
            +
                # section 7.6 for more information.
         | 
| 2201 | 
            +
                #
         | 
| 2202 | 
            +
                # @return [Addressable::URI] A URI suitable for display purposes.
         | 
| 1036 2203 | 
             
                def display_uri
         | 
| 1037 2204 | 
             
                  display_uri = self.normalize
         | 
| 1038 | 
            -
                   | 
| 1039 | 
            -
                    display_uri. | 
| 1040 | 
            -
                      URI::IDNA.to_unicode(display_uri.host))
         | 
| 1041 | 
            -
                  rescue Exception
         | 
| 1042 | 
            -
                    nil
         | 
| 1043 | 
            -
                  end
         | 
| 2205 | 
            +
                  display_uri.instance_variable_set("@host",
         | 
| 2206 | 
            +
                    ::Addressable::IDNA.to_unicode(display_uri.host))
         | 
| 1044 2207 | 
             
                  return display_uri
         | 
| 1045 2208 | 
             
                end
         | 
| 1046 | 
            -
             | 
| 1047 | 
            -
                 | 
| 1048 | 
            -
                #  | 
| 1049 | 
            -
                #  | 
| 2209 | 
            +
             | 
| 2210 | 
            +
                ##
         | 
| 2211 | 
            +
                # Returns <tt>true</tt> if the URI objects are equal.  This method
         | 
| 2212 | 
            +
                # normalizes both URIs before doing the comparison, and allows comparison
         | 
| 2213 | 
            +
                # against <tt>Strings</tt>.
         | 
| 2214 | 
            +
                #
         | 
| 2215 | 
            +
                # @param [Object] uri The URI to compare.
         | 
| 2216 | 
            +
                #
         | 
| 2217 | 
            +
                # @return [TrueClass, FalseClass]
         | 
| 2218 | 
            +
                #   <tt>true</tt> if the URIs are equivalent, <tt>false</tt> otherwise.
         | 
| 1050 2219 | 
             
                def ===(uri)
         | 
| 1051 2220 | 
             
                  if uri.respond_to?(:normalize)
         | 
| 1052 2221 | 
             
                    uri_string = uri.normalize.to_s
         | 
| 1053 2222 | 
             
                  else
         | 
| 1054 2223 | 
             
                    begin
         | 
| 1055 | 
            -
                      uri_string = URI.parse(uri | 
| 1056 | 
            -
                    rescue InvalidURIError
         | 
| 2224 | 
            +
                      uri_string = ::Addressable::URI.parse(uri).normalize.to_s
         | 
| 2225 | 
            +
                    rescue InvalidURIError, TypeError
         | 
| 1057 2226 | 
             
                      return false
         | 
| 1058 2227 | 
             
                    end
         | 
| 1059 2228 | 
             
                  end
         | 
| 1060 2229 | 
             
                  return self.normalize.to_s == uri_string
         | 
| 1061 2230 | 
             
                end
         | 
| 1062 | 
            -
             | 
| 1063 | 
            -
                 | 
| 1064 | 
            -
                #  | 
| 2231 | 
            +
             | 
| 2232 | 
            +
                ##
         | 
| 2233 | 
            +
                # Returns <tt>true</tt> if the URI objects are equal.  This method
         | 
| 2234 | 
            +
                # normalizes both URIs before doing the comparison.
         | 
| 2235 | 
            +
                #
         | 
| 2236 | 
            +
                # @param [Object] uri The URI to compare.
         | 
| 2237 | 
            +
                #
         | 
| 2238 | 
            +
                # @return [TrueClass, FalseClass]
         | 
| 2239 | 
            +
                #   <tt>true</tt> if the URIs are equivalent, <tt>false</tt> otherwise.
         | 
| 1065 2240 | 
             
                def ==(uri)
         | 
| 1066 | 
            -
                  return false unless uri.kind_of?(self.class) | 
| 2241 | 
            +
                  return false unless uri.kind_of?(self.class)
         | 
| 1067 2242 | 
             
                  return self.normalize.to_s == uri.normalize.to_s
         | 
| 1068 2243 | 
             
                end
         | 
| 1069 2244 |  | 
| 1070 | 
            -
                 | 
| 1071 | 
            -
                #  | 
| 2245 | 
            +
                ##
         | 
| 2246 | 
            +
                # Returns <tt>true</tt> if the URI objects are equal.  This method
         | 
| 2247 | 
            +
                # does NOT normalize either URI before doing the comparison.
         | 
| 2248 | 
            +
                #
         | 
| 2249 | 
            +
                # @param [Object] uri The URI to compare.
         | 
| 2250 | 
            +
                #
         | 
| 2251 | 
            +
                # @return [TrueClass, FalseClass]
         | 
| 2252 | 
            +
                #   <tt>true</tt> if the URIs are equivalent, <tt>false</tt> otherwise.
         | 
| 1072 2253 | 
             
                def eql?(uri)
         | 
| 1073 | 
            -
                  return false unless uri.kind_of?(self.class) | 
| 2254 | 
            +
                  return false unless uri.kind_of?(self.class)
         | 
| 1074 2255 | 
             
                  return self.to_s == uri.to_s
         | 
| 1075 2256 | 
             
                end
         | 
| 1076 2257 |  | 
| 1077 | 
            -
                 | 
| 2258 | 
            +
                ##
         | 
| 2259 | 
            +
                # A hash value that will make a URI equivalent to its normalized
         | 
| 1078 2260 | 
             
                # form.
         | 
| 2261 | 
            +
                #
         | 
| 2262 | 
            +
                # @return [Integer] A hash of the URI.
         | 
| 1079 2263 | 
             
                def hash
         | 
| 1080 2264 | 
             
                  return (self.normalize.to_s.hash * -1)
         | 
| 1081 2265 | 
             
                end
         | 
| 1082 | 
            -
             | 
| 2266 | 
            +
             | 
| 2267 | 
            +
                ##
         | 
| 1083 2268 | 
             
                # Clones the URI object.
         | 
| 2269 | 
            +
                #
         | 
| 2270 | 
            +
                # @return [Addressable::URI] The cloned URI.
         | 
| 1084 2271 | 
             
                def dup
         | 
| 1085 | 
            -
                  duplicated_scheme = self.scheme ? self.scheme.dup : nil
         | 
| 1086 | 
            -
                  duplicated_user = self.user ? self.user.dup : nil
         | 
| 1087 | 
            -
                  duplicated_password = self.password ? self.password.dup : nil
         | 
| 1088 | 
            -
                  duplicated_host = self.host ? self.host.dup : nil
         | 
| 1089 | 
            -
                  duplicated_port = self.specified_port
         | 
| 1090 | 
            -
                  duplicated_path = self.path ? self.path.dup : nil
         | 
| 1091 | 
            -
                  duplicated_query = self.query ? self.query.dup : nil
         | 
| 1092 | 
            -
                  duplicated_fragment = self.fragment ? self.fragment.dup : nil
         | 
| 1093 2272 | 
             
                  duplicated_uri = Addressable::URI.new(
         | 
| 1094 | 
            -
                     | 
| 1095 | 
            -
                     | 
| 1096 | 
            -
                     | 
| 1097 | 
            -
                     | 
| 1098 | 
            -
                     | 
| 1099 | 
            -
                     | 
| 1100 | 
            -
                     | 
| 1101 | 
            -
                     | 
| 2273 | 
            +
                    :scheme => self.scheme ? self.scheme.dup : nil,
         | 
| 2274 | 
            +
                    :user => self.user ? self.user.dup : nil,
         | 
| 2275 | 
            +
                    :password => self.password ? self.password.dup : nil,
         | 
| 2276 | 
            +
                    :host => self.host ? self.host.dup : nil,
         | 
| 2277 | 
            +
                    :port => self.port,
         | 
| 2278 | 
            +
                    :path => self.path ? self.path.dup : nil,
         | 
| 2279 | 
            +
                    :query => self.query ? self.query.dup : nil,
         | 
| 2280 | 
            +
                    :fragment => self.fragment ? self.fragment.dup : nil
         | 
| 1102 2281 | 
             
                  )
         | 
| 1103 2282 | 
             
                  return duplicated_uri
         | 
| 1104 2283 | 
             
                end
         | 
| 1105 | 
            -
             | 
| 1106 | 
            -
                 | 
| 2284 | 
            +
             | 
| 2285 | 
            +
                ##
         | 
| 2286 | 
            +
                # Omits components from a URI.
         | 
| 2287 | 
            +
                #
         | 
| 2288 | 
            +
                # @param [Symbol] *components The components to be omitted.
         | 
| 2289 | 
            +
                #
         | 
| 2290 | 
            +
                # @return [Addressable::URI] The URI with components omitted.
         | 
| 2291 | 
            +
                #
         | 
| 2292 | 
            +
                # @example
         | 
| 2293 | 
            +
                #   uri = Addressable::URI.parse("http://example.com/path?query")
         | 
| 2294 | 
            +
                #   #=> #<Addressable::URI:0xcc5e7a URI:http://example.com/path?query>
         | 
| 2295 | 
            +
                #   uri.omit(:scheme, :authority)
         | 
| 2296 | 
            +
                #   #=> #<Addressable::URI:0xcc4d86 URI:/path?query>
         | 
| 2297 | 
            +
                def omit(*components)
         | 
| 2298 | 
            +
                  invalid_components = components - [
         | 
| 2299 | 
            +
                    :scheme, :user, :password, :userinfo, :host, :port, :authority,
         | 
| 2300 | 
            +
                    :path, :query, :fragment
         | 
| 2301 | 
            +
                  ]
         | 
| 2302 | 
            +
                  unless invalid_components.empty?
         | 
| 2303 | 
            +
                    raise ArgumentError,
         | 
| 2304 | 
            +
                      "Invalid component names: #{invalid_components.inspect}."
         | 
| 2305 | 
            +
                  end
         | 
| 2306 | 
            +
                  duplicated_uri = self.dup
         | 
| 2307 | 
            +
                  duplicated_uri.validation_deferred = true
         | 
| 2308 | 
            +
                  components.each do |component|
         | 
| 2309 | 
            +
                    duplicated_uri.send((component.to_s + "=").to_sym, nil)
         | 
| 2310 | 
            +
                  end
         | 
| 2311 | 
            +
                  duplicated_uri.validation_deferred = false
         | 
| 2312 | 
            +
                  duplicated_uri
         | 
| 2313 | 
            +
                end
         | 
| 2314 | 
            +
             | 
| 2315 | 
            +
                ##
         | 
| 2316 | 
            +
                # Destructive form of omit.
         | 
| 2317 | 
            +
                #
         | 
| 2318 | 
            +
                # @param [Symbol] *components The components to be omitted.
         | 
| 2319 | 
            +
                #
         | 
| 2320 | 
            +
                # @return [Addressable::URI] The URI with components omitted.
         | 
| 2321 | 
            +
                #
         | 
| 2322 | 
            +
                # @see Addressable::URI#omit
         | 
| 2323 | 
            +
                def omit!(*components)
         | 
| 2324 | 
            +
                  replace_self(self.omit(*components))
         | 
| 2325 | 
            +
                end
         | 
| 2326 | 
            +
             | 
| 2327 | 
            +
                ##
         | 
| 2328 | 
            +
                # Converts the URI to a <tt>String</tt>.
         | 
| 2329 | 
            +
                #
         | 
| 2330 | 
            +
                # @return [String] The URI's <tt>String</tt> representation.
         | 
| 1107 2331 | 
             
                def to_s
         | 
| 1108 2332 | 
             
                  uri_string = ""
         | 
| 1109 2333 | 
             
                  uri_string << "#{self.scheme}:" if self.scheme != nil
         | 
| @@ -1111,82 +2335,72 @@ module Addressable | |
| 1111 2335 | 
             
                  uri_string << self.path.to_s
         | 
| 1112 2336 | 
             
                  uri_string << "?#{self.query}" if self.query != nil
         | 
| 1113 2337 | 
             
                  uri_string << "##{self.fragment}" if self.fragment != nil
         | 
| 2338 | 
            +
                  if uri_string.respond_to?(:force_encoding)
         | 
| 2339 | 
            +
                    uri_string.force_encoding(Encoding::UTF_8)
         | 
| 2340 | 
            +
                  end
         | 
| 1114 2341 | 
             
                  return uri_string
         | 
| 1115 2342 | 
             
                end
         | 
| 1116 | 
            -
             | 
| 1117 | 
            -
                 | 
| 2343 | 
            +
             | 
| 2344 | 
            +
                ##
         | 
| 2345 | 
            +
                # URI's are glorified <tt>Strings</tt>.  Allow implicit conversion.
         | 
| 2346 | 
            +
                alias_method :to_str, :to_s
         | 
| 2347 | 
            +
             | 
| 2348 | 
            +
                ##
         | 
| 2349 | 
            +
                # Returns a Hash of the URI components.
         | 
| 2350 | 
            +
                #
         | 
| 2351 | 
            +
                # @return [Hash] The URI as a <tt>Hash</tt> of components.
         | 
| 1118 2352 | 
             
                def to_hash
         | 
| 1119 2353 | 
             
                  return {
         | 
| 1120 2354 | 
             
                    :scheme => self.scheme,
         | 
| 1121 2355 | 
             
                    :user => self.user,
         | 
| 1122 2356 | 
             
                    :password => self.password,
         | 
| 1123 2357 | 
             
                    :host => self.host,
         | 
| 1124 | 
            -
                    :port => self. | 
| 2358 | 
            +
                    :port => self.port,
         | 
| 1125 2359 | 
             
                    :path => self.path,
         | 
| 1126 2360 | 
             
                    :query => self.query,
         | 
| 1127 2361 | 
             
                    :fragment => self.fragment
         | 
| 1128 2362 | 
             
                  }
         | 
| 1129 2363 | 
             
                end
         | 
| 1130 | 
            -
             | 
| 1131 | 
            -
                 | 
| 2364 | 
            +
             | 
| 2365 | 
            +
                ##
         | 
| 2366 | 
            +
                # Returns a <tt>String</tt> representation of the URI object's state.
         | 
| 2367 | 
            +
                #
         | 
| 2368 | 
            +
                # @return [String] The URI object's state, as a <tt>String</tt>.
         | 
| 1132 2369 | 
             
                def inspect
         | 
| 1133 2370 | 
             
                  sprintf("#<%s:%#0x URI:%s>", self.class.to_s, self.object_id, self.to_s)
         | 
| 1134 2371 | 
             
                end
         | 
| 1135 | 
            -
             | 
| 1136 | 
            -
                 | 
| 1137 | 
            -
                #  | 
| 1138 | 
            -
                # | 
| 1139 | 
            -
                #  | 
| 1140 | 
            -
                 | 
| 1141 | 
            -
             | 
| 1142 | 
            -
             | 
| 1143 | 
            -
             | 
| 1144 | 
            -
             | 
| 1145 | 
            -
             | 
| 1146 | 
            -
             | 
| 1147 | 
            -
             | 
| 1148 | 
            -
             | 
| 1149 | 
            -
             | 
| 1150 | 
            -
             | 
| 1151 | 
            -
             | 
| 1152 | 
            -
             | 
| 1153 | 
            -
                   | 
| 1154 | 
            -
                   | 
| 1155 | 
            -
                    return nil if label.nil?
         | 
| 1156 | 
            -
                    if self.use_libidn?
         | 
| 1157 | 
            -
                      return IDN::Idna.toUnicode(label)
         | 
| 1158 | 
            -
                    else
         | 
| 1159 | 
            -
                      raise NotImplementedError,
         | 
| 1160 | 
            -
                        "There is no available pure-ruby implementation.  " +
         | 
| 1161 | 
            -
                        "Install libidn bindings."
         | 
| 1162 | 
            -
                    end
         | 
| 1163 | 
            -
                  end
         | 
| 1164 | 
            -
                  
         | 
| 1165 | 
            -
                private
         | 
| 1166 | 
            -
                  # Determines if the libidn bindings are available and able to be used.
         | 
| 1167 | 
            -
                  def self.use_libidn?
         | 
| 1168 | 
            -
                    if !defined?(@use_libidn) || @use_libidn.nil?
         | 
| 1169 | 
            -
                      begin
         | 
| 1170 | 
            -
                        require 'rubygems'
         | 
| 1171 | 
            -
                      rescue LoadError
         | 
| 1172 | 
            -
                        nil
         | 
| 1173 | 
            -
                      end
         | 
| 1174 | 
            -
                      begin
         | 
| 1175 | 
            -
                        require 'idn'
         | 
| 1176 | 
            -
                      rescue LoadError
         | 
| 1177 | 
            -
                        nil
         | 
| 1178 | 
            -
                      end
         | 
| 1179 | 
            -
                      @use_libidn = !!(defined?(IDN::Idna))
         | 
| 1180 | 
            -
                    end
         | 
| 1181 | 
            -
                    return @use_libidn
         | 
| 1182 | 
            -
                  end
         | 
| 2372 | 
            +
             | 
| 2373 | 
            +
                ##
         | 
| 2374 | 
            +
                # If URI validation needs to be disabled, this can be set to true.
         | 
| 2375 | 
            +
                #
         | 
| 2376 | 
            +
                # @return [TrueClass, FalseClass]
         | 
| 2377 | 
            +
                #   <tt>true</tt> if validation has been deferred,
         | 
| 2378 | 
            +
                #   <tt>false</tt> otherwise.
         | 
| 2379 | 
            +
                def validation_deferred
         | 
| 2380 | 
            +
                  @validation_deferred ||= false
         | 
| 2381 | 
            +
                end
         | 
| 2382 | 
            +
             | 
| 2383 | 
            +
                ##
         | 
| 2384 | 
            +
                # If URI validation needs to be disabled, this can be set to true.
         | 
| 2385 | 
            +
                #
         | 
| 2386 | 
            +
                # @param [TrueClass, FalseClass] new_validation_deferred
         | 
| 2387 | 
            +
                #   <tt>true</tt> if validation will be deferred,
         | 
| 2388 | 
            +
                #   <tt>false</tt> otherwise.
         | 
| 2389 | 
            +
                def validation_deferred=(new_validation_deferred)
         | 
| 2390 | 
            +
                  @validation_deferred = new_validation_deferred
         | 
| 2391 | 
            +
                  validate unless @validation_deferred
         | 
| 1183 2392 | 
             
                end
         | 
| 1184 | 
            -
             | 
| 2393 | 
            +
             | 
| 1185 2394 | 
             
              private
         | 
| 2395 | 
            +
                ##
         | 
| 1186 2396 | 
             
                # Resolves paths to their simplest form.
         | 
| 2397 | 
            +
                #
         | 
| 2398 | 
            +
                # @param [String] path The path to normalize.
         | 
| 2399 | 
            +
                #
         | 
| 2400 | 
            +
                # @return [String] The normalized path.
         | 
| 1187 2401 | 
             
                def self.normalize_path(path)
         | 
| 1188 2402 | 
             
                  # Section 5.2.4 of RFC 3986
         | 
| 1189 | 
            -
             | 
| 2403 | 
            +
             | 
| 1190 2404 | 
             
                  return nil if path.nil?
         | 
| 1191 2405 | 
             
                  normalized_path = path.dup
         | 
| 1192 2406 | 
             
                  previous_state = normalized_path.dup
         | 
| @@ -1194,11 +2408,11 @@ module Addressable | |
| 1194 2408 | 
             
                    previous_state = normalized_path.dup
         | 
| 1195 2409 | 
             
                    normalized_path.gsub!(/\/\.\//, "/")
         | 
| 1196 2410 | 
             
                    normalized_path.gsub!(/\/\.$/, "/")
         | 
| 1197 | 
            -
                    parent = normalized_path | 
| 2411 | 
            +
                    parent = normalized_path[/\/([^\/]+)\/\.\.\//, 1]
         | 
| 1198 2412 | 
             
                    if parent != "." && parent != ".."
         | 
| 1199 2413 | 
             
                      normalized_path.gsub!(/\/#{parent}\/\.\.\//, "/")
         | 
| 1200 2414 | 
             
                    end
         | 
| 1201 | 
            -
                    parent = normalized_path | 
| 2415 | 
            +
                    parent = normalized_path[/\/([^\/]+)\/\.\.$/, 1]
         | 
| 1202 2416 | 
             
                    if parent != "." && parent != ".."
         | 
| 1203 2417 | 
             
                      normalized_path.gsub!(/\/#{parent}\/\.\.$/, "/")
         | 
| 1204 2418 | 
             
                    end
         | 
| @@ -1208,36 +2422,44 @@ module Addressable | |
| 1208 2422 | 
             
                  return normalized_path
         | 
| 1209 2423 | 
             
                end
         | 
| 1210 2424 |  | 
| 2425 | 
            +
                ##
         | 
| 1211 2426 | 
             
                # Ensures that the URI is valid.
         | 
| 1212 2427 | 
             
                def validate
         | 
| 2428 | 
            +
                  return if self.validation_deferred
         | 
| 1213 2429 | 
             
                  if self.scheme != nil &&
         | 
| 1214 2430 | 
             
                      (self.host == nil || self.host == "") &&
         | 
| 1215 2431 | 
             
                      (self.path == nil || self.path == "")
         | 
| 1216 2432 | 
             
                    raise InvalidURIError,
         | 
| 1217 | 
            -
                      "Absolute URI missing hierarchical segment."
         | 
| 2433 | 
            +
                      "Absolute URI missing hierarchical segment: '#{self.to_s}'"
         | 
| 1218 2434 | 
             
                  end
         | 
| 1219 2435 | 
             
                  if self.host == nil
         | 
| 1220 | 
            -
                    if self. | 
| 2436 | 
            +
                    if self.port != nil ||
         | 
| 1221 2437 | 
             
                        self.user != nil ||
         | 
| 1222 2438 | 
             
                        self.password != nil
         | 
| 1223 | 
            -
                      raise InvalidURIError, "Hostname not supplied."
         | 
| 2439 | 
            +
                      raise InvalidURIError, "Hostname not supplied: '#{self.to_s}'"
         | 
| 1224 2440 | 
             
                    end
         | 
| 1225 2441 | 
             
                  end
         | 
| 2442 | 
            +
                  return nil
         | 
| 1226 2443 | 
             
                end
         | 
| 1227 | 
            -
             | 
| 2444 | 
            +
             | 
| 2445 | 
            +
                ##
         | 
| 1228 2446 | 
             
                # Replaces the internal state of self with the specified URI's state.
         | 
| 1229 2447 | 
             
                # Used in destructive operations to avoid massive code repetition.
         | 
| 2448 | 
            +
                #
         | 
| 2449 | 
            +
                # @param [Addressable::URI] uri The URI to replace <tt>self</tt> with.
         | 
| 2450 | 
            +
                #
         | 
| 2451 | 
            +
                # @return [Addressable::URI] <tt>self</tt>.
         | 
| 1230 2452 | 
             
                def replace_self(uri)
         | 
| 1231 2453 | 
             
                  # Reset dependant values
         | 
| 1232 | 
            -
                   | 
| 1233 | 
            -
             | 
| 1234 | 
            -
                  
         | 
| 2454 | 
            +
                  instance_variables.each do |var|
         | 
| 2455 | 
            +
                    instance_variable_set(var, nil)
         | 
| 2456 | 
            +
                  end
         | 
| 2457 | 
            +
             | 
| 1235 2458 | 
             
                  @scheme = uri.scheme
         | 
| 1236 2459 | 
             
                  @user = uri.user
         | 
| 1237 2460 | 
             
                  @password = uri.password
         | 
| 1238 2461 | 
             
                  @host = uri.host
         | 
| 1239 | 
            -
                  @ | 
| 1240 | 
            -
                  @port = @specified_port.to_s.to_i
         | 
| 2462 | 
            +
                  @port = uri.port
         | 
| 1241 2463 | 
             
                  @path = uri.path
         | 
| 1242 2464 | 
             
                  @query = uri.query
         | 
| 1243 2465 | 
             
                  @fragment = uri.fragment
         |