mislav-addressable 2.1.1

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.
@@ -0,0 +1,1980 @@
1
+ # encoding:utf-8
2
+ #--
3
+ # Addressable, Copyright (c) 2006-2008 Bob Aman
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ #++
24
+
25
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '/..')))
26
+ $:.uniq!
27
+
28
+ require "addressable/version"
29
+ require "addressable/idna"
30
+
31
+ module Addressable
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>.
36
+ class URI
37
+ ##
38
+ # Raised if something other than a uri is supplied.
39
+ class InvalidURIError < StandardError
40
+ end
41
+
42
+ ##
43
+ # Raised if an invalid method option is supplied.
44
+ class InvalidOptionError < StandardError
45
+ end
46
+
47
+ ##
48
+ # Container for the character classes specified in
49
+ # <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
50
+ module CharacterClasses
51
+ ALPHA = "a-zA-Z"
52
+ DIGIT = "0-9"
53
+ GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@"
54
+ SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\="
55
+ RESERVED = GEN_DELIMS + SUB_DELIMS
56
+ UNRESERVED = ALPHA + DIGIT + "\\-\\.\\_\\~"
57
+ PCHAR = UNRESERVED + SUB_DELIMS + "\\:\\@"
58
+ SCHEME = ALPHA + DIGIT + "\\-\\+\\."
59
+ AUTHORITY = PCHAR
60
+ PATH = PCHAR + "\\/"
61
+ QUERY = PCHAR + "\\/\\?"
62
+ FRAGMENT = PCHAR + "\\/\\?"
63
+ end
64
+
65
+ ##
66
+ # Returns a URI object based on the parsed string.
67
+ #
68
+ # @param [String, Addressable::URI, #to_str] uri
69
+ # The URI string to parse. No parsing is performed if the object is
70
+ # already an <tt>Addressable::URI</tt>.
71
+ #
72
+ # @return [Addressable::URI] The parsed URI.
73
+ def self.parse(uri)
74
+ # If we were given nil, return nil.
75
+ return nil unless uri
76
+ # If a URI object is passed, just return itself.
77
+ return uri if uri.kind_of?(self)
78
+
79
+ # If a URI object of the Ruby standard library variety is passed,
80
+ # convert it to a string, then parse the string.
81
+ # We do the check this way because we don't want to accidentally
82
+ # cause a missing constant exception to be thrown.
83
+ if uri.class.name =~ /^URI\b/
84
+ uri = uri.to_s
85
+ end
86
+
87
+ if !uri.respond_to?(:to_str)
88
+ raise TypeError, "Can't convert #{uri.class} into String."
89
+ end
90
+ # Otherwise, convert to a String
91
+ uri = uri.to_str
92
+
93
+ # This Regexp supplied as an example in RFC 3986, and it works great.
94
+ uri_regex =
95
+ /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/
96
+ scan = uri.scan(uri_regex)
97
+ fragments = scan[0]
98
+ scheme = fragments[1]
99
+ authority = fragments[3]
100
+ path = fragments[4]
101
+ query = fragments[6]
102
+ fragment = fragments[8]
103
+ user = nil
104
+ password = nil
105
+ host = nil
106
+ port = nil
107
+ if authority != nil
108
+ # The Regexp above doesn't split apart the authority.
109
+ userinfo = authority[/^([^\[\]]*)@/, 1]
110
+ if userinfo != nil
111
+ user = userinfo.strip[/^([^:]*):?/, 1]
112
+ password = userinfo.strip[/:(.*)$/, 1]
113
+ end
114
+ host = authority.gsub(/^([^\[\]]*)@/, "").gsub(/:([^:@\[\]]*?)$/, "")
115
+ port = authority[/:([^:@\[\]]*?)$/, 1]
116
+ end
117
+ if port == ""
118
+ port = nil
119
+ end
120
+
121
+ return Addressable::URI.new(
122
+ :scheme => scheme,
123
+ :user => user,
124
+ :password => password,
125
+ :host => host,
126
+ :port => port,
127
+ :path => path,
128
+ :query => query,
129
+ :fragment => fragment
130
+ )
131
+ end
132
+
133
+ ##
134
+ # Converts an input to a URI. The input does not have to be a valid
135
+ # URI — the method will use heuristics to guess what URI was intended.
136
+ # This is not standards-compliant, merely user-friendly.
137
+ #
138
+ # @param [String, Addressable::URI, #to_str] uri
139
+ # The URI string to parse. No parsing is performed if the object is
140
+ # already an <tt>Addressable::URI</tt>.
141
+ # @param [Hash] hints
142
+ # A <tt>Hash</tt> of hints to the heuristic parser. Defaults to
143
+ # <tt>{:scheme => "http"}</tt>.
144
+ #
145
+ # @return [Addressable::URI] The parsed URI.
146
+ def self.heuristic_parse(uri, hints={})
147
+ # If we were given nil, return nil.
148
+ return nil unless uri
149
+ # If a URI object is passed, just return itself.
150
+ return uri if uri.kind_of?(self)
151
+ if !uri.respond_to?(:to_str)
152
+ raise TypeError, "Can't convert #{uri.class} into String."
153
+ end
154
+ # Otherwise, convert to a String
155
+ uri = uri.to_str.dup
156
+ hints = {
157
+ :scheme => "http"
158
+ }.merge(hints)
159
+ case uri
160
+ when /^http:\/+/
161
+ uri.gsub!(/^http:\/+/, "http://")
162
+ when /^feed:\/+http:\/+/
163
+ uri.gsub!(/^feed:\/+http:\/+/, "feed:http://")
164
+ when /^feed:\/+/
165
+ uri.gsub!(/^feed:\/+/, "feed://")
166
+ when /^file:\/+/
167
+ uri.gsub!(/^file:\/+/, "file:///")
168
+ end
169
+ parsed = self.parse(uri)
170
+ if parsed.scheme =~ /^[^\/?#\.]+\.[^\/?#]+$/
171
+ parsed = self.parse(hints[:scheme] + "://" + uri)
172
+ end
173
+ if parsed.path.include?(".")
174
+ new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1]
175
+ if new_host
176
+ new_path = parsed.path.gsub(
177
+ Regexp.new("^" + Regexp.escape(new_host)), "")
178
+ parsed.host = new_host
179
+ parsed.path = new_path
180
+ parsed.scheme = hints[:scheme] unless parsed.scheme
181
+ end
182
+ end
183
+ return parsed
184
+ end
185
+
186
+ ##
187
+ # Converts a path to a file scheme URI. If the path supplied is
188
+ # relative, it will be returned as a relative URI. If the path supplied
189
+ # is actually a non-file URI, it will parse the URI as if it had been
190
+ # parsed with <tt>Addressable::URI.parse</tt>. Handles all of the
191
+ # various Microsoft-specific formats for specifying paths.
192
+ #
193
+ # @param [String, Addressable::URI, #to_str] path
194
+ # Typically a <tt>String</tt> path to a file or directory, but
195
+ # will return a sensible return value if an absolute URI is supplied
196
+ # instead.
197
+ #
198
+ # @return [Addressable::URI]
199
+ # The parsed file scheme URI or the original URI if some other URI
200
+ # scheme was provided.
201
+ #
202
+ # @example
203
+ # base = Addressable::URI.convert_path("/absolute/path/")
204
+ # uri = Addressable::URI.convert_path("relative/path")
205
+ # (base + uri).to_s
206
+ # #=> "file:///absolute/path/relative/path"
207
+ #
208
+ # Addressable::URI.convert_path(
209
+ # "c:\\windows\\My Documents 100%20\\foo.txt"
210
+ # ).to_s
211
+ # #=> "file:///c:/windows/My%20Documents%20100%20/foo.txt"
212
+ #
213
+ # Addressable::URI.convert_path("http://example.com/").to_s
214
+ # #=> "http://example.com/"
215
+ def self.convert_path(path)
216
+ # If we were given nil, return nil.
217
+ return nil unless path
218
+ # If a URI object is passed, just return itself.
219
+ return path if path.kind_of?(self)
220
+ if !path.respond_to?(:to_str)
221
+ raise TypeError, "Can't convert #{path.class} into String."
222
+ end
223
+ # Otherwise, convert to a String
224
+ path = path.to_str.strip
225
+
226
+ path.gsub!(/^file:\/?\/?/, "") if path =~ /^file:\/?\/?/
227
+ path = "/" + path if path =~ /^([a-zA-Z])(\||:)/
228
+ uri = self.parse(path)
229
+
230
+ if uri.scheme == nil
231
+ # Adjust windows-style uris
232
+ uri.path.gsub!(/^\/?([a-zA-Z])\|(\\|\/)/, "/\\1:/")
233
+ uri.path.gsub!(/\\/, "/")
234
+ if File.exists?(uri.path) &&
235
+ File.stat(uri.path).directory?
236
+ uri.path.gsub!(/\/$/, "")
237
+ uri.path = uri.path + '/'
238
+ end
239
+
240
+ # If the path is absolute, set the scheme and host.
241
+ if uri.path =~ /^\//
242
+ uri.scheme = "file"
243
+ uri.host = ""
244
+ end
245
+ uri.normalize!
246
+ end
247
+
248
+ return uri
249
+ end
250
+
251
+ ##
252
+ # Joins several URIs together.
253
+ #
254
+ # @param [String, Addressable::URI, #to_str] *uris
255
+ # The URIs to join.
256
+ #
257
+ # @return [Addressable::URI] The joined URI.
258
+ #
259
+ # @example
260
+ # base = "http://example.com/"
261
+ # uri = Addressable::URI.parse("relative/path")
262
+ # Addressable::URI.join(base, uri)
263
+ # #=> #<Addressable::URI:0xcab390 URI:http://example.com/relative/path>
264
+ def self.join(*uris)
265
+ uri_objects = uris.collect do |uri|
266
+ if !uri.respond_to?(:to_str)
267
+ raise TypeError, "Can't convert #{uri.class} into String."
268
+ end
269
+ uri.kind_of?(self) ? uri : self.parse(uri.to_str)
270
+ end
271
+ result = uri_objects.shift.dup
272
+ for uri in uri_objects
273
+ result.join!(uri)
274
+ end
275
+ return result
276
+ end
277
+
278
+ ##
279
+ # Percent encodes a URI component.
280
+ #
281
+ # @param [String, #to_str] component The URI component to encode.
282
+ #
283
+ # @param [String, Regexp] character_class
284
+ # The characters which are not percent encoded. If a <tt>String</tt>
285
+ # is passed, the <tt>String</tt> must be formatted as a regular
286
+ # expression character class. (Do not include the surrounding square
287
+ # brackets.) For example, <tt>"b-zB-Z0-9"</tt> would cause everything
288
+ # but the letters 'b' through 'z' and the numbers '0' through '9' to be
289
+ # percent encoded. If a <tt>Regexp</tt> is passed, the value
290
+ # <tt>/[^b-zB-Z0-9]/</tt> would have the same effect.
291
+ # A set of useful <tt>String</tt> values may be found in the
292
+ # <tt>Addressable::URI::CharacterClasses</tt> module. The default value
293
+ # is the reserved plus unreserved character classes specified in
294
+ # <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
295
+ #
296
+ # @return [String] The encoded component.
297
+ #
298
+ # @example
299
+ # Addressable::URI.encode_component("simple/example", "b-zB-Z0-9")
300
+ # => "simple%2Fex%61mple"
301
+ # Addressable::URI.encode_component("simple/example", /[^b-zB-Z0-9]/)
302
+ # => "simple%2Fex%61mple"
303
+ # Addressable::URI.encode_component(
304
+ # "simple/example", Addressable::URI::CharacterClasses::UNRESERVED
305
+ # )
306
+ # => "simple%2Fexample"
307
+ def self.encode_component(component, character_class=
308
+ CharacterClasses::RESERVED + CharacterClasses::UNRESERVED)
309
+ return nil if component.nil?
310
+ if !component.respond_to?(:to_str)
311
+ raise TypeError, "Can't convert #{component.class} into String."
312
+ end
313
+ component = component.to_str
314
+ if ![String, Regexp].include?(character_class.class)
315
+ raise TypeError,
316
+ "Expected String or Regexp, got #{character_class.inspect}"
317
+ end
318
+ if character_class.kind_of?(String)
319
+ character_class = /[^#{character_class}]/
320
+ end
321
+ if component.respond_to?(:force_encoding)
322
+ # We can't perform regexps on invalid UTF sequences, but
323
+ # here we need to, so switch to ASCII.
324
+ component = component.dup
325
+ component.force_encoding(Encoding::ASCII_8BIT)
326
+ end
327
+ return component.gsub(character_class) do |sequence|
328
+ (sequence.unpack('C*').map { |c| "%" + ("%02x" % c).upcase }).join("")
329
+ end
330
+ end
331
+
332
+ class << self
333
+ alias_method :encode_component, :encode_component
334
+ end
335
+
336
+ ##
337
+ # Unencodes any percent encoded characters within a URI component.
338
+ # This method may be used for unencoding either components or full URIs,
339
+ # however, it is recommended to use the <tt>unencode_component</tt> alias
340
+ # when unencoding components.
341
+ #
342
+ # @param [String, Addressable::URI, #to_str] uri
343
+ # The URI or component to unencode.
344
+ #
345
+ # @param [Class] returning
346
+ # The type of object to return. This value may only be set to
347
+ # <tt>String</tt> or <tt>Addressable::URI</tt>. All other values
348
+ # are invalid. Defaults to <tt>String</tt>.
349
+ #
350
+ # @return [String, Addressable::URI]
351
+ # The unencoded component or URI. The return type is determined by
352
+ # the <tt>returning</tt> parameter.
353
+ def self.unencode(uri, returning=String)
354
+ return nil if uri.nil?
355
+ if !uri.respond_to?(:to_str)
356
+ raise TypeError, "Can't convert #{uri.class} into String."
357
+ end
358
+ if ![String, ::Addressable::URI].include?(returning)
359
+ raise TypeError,
360
+ "Expected Class (String or Addressable::URI), " +
361
+ "got #{returning.inspect}"
362
+ end
363
+ result = uri.to_str.gsub(/%[0-9a-f]{2}/i) do |sequence|
364
+ sequence[1..3].to_i(16).chr
365
+ end
366
+ result.force_encoding("utf-8") if result.respond_to?(:force_encoding)
367
+ if returning == String
368
+ return result
369
+ elsif returning == ::Addressable::URI
370
+ return ::Addressable::URI.parse(result)
371
+ end
372
+ end
373
+
374
+ class << self
375
+ alias_method :unescape, :unencode
376
+ alias_method :unencode_component, :unencode
377
+ alias_method :unescape_component, :unencode
378
+ end
379
+
380
+ ##
381
+ # Percent encodes any special characters in the URI.
382
+ #
383
+ # @param [String, Addressable::URI, #to_str] uri
384
+ # The URI to encode.
385
+ #
386
+ # @param [Class] returning
387
+ # The type of object to return. This value may only be set to
388
+ # <tt>String</tt> or <tt>Addressable::URI</tt>. All other values
389
+ # are invalid. Defaults to <tt>String</tt>.
390
+ #
391
+ # @return [String, Addressable::URI]
392
+ # The encoded URI. The return type is determined by
393
+ # the <tt>returning</tt> parameter.
394
+ def self.encode(uri, returning=String)
395
+ return nil if uri.nil?
396
+ if !uri.respond_to?(:to_str)
397
+ raise TypeError, "Can't convert #{uri.class} into String."
398
+ end
399
+ if ![String, ::Addressable::URI].include?(returning)
400
+ raise TypeError,
401
+ "Expected Class (String or Addressable::URI), " +
402
+ "got #{returning.inspect}"
403
+ end
404
+ uri_object = uri.kind_of?(self) ? uri : self.parse(uri.to_str)
405
+ encoded_uri = Addressable::URI.new(
406
+ :scheme => self.encode_component(uri_object.scheme,
407
+ Addressable::URI::CharacterClasses::SCHEME),
408
+ :authority => self.encode_component(uri_object.authority,
409
+ Addressable::URI::CharacterClasses::AUTHORITY),
410
+ :path => self.encode_component(uri_object.path,
411
+ Addressable::URI::CharacterClasses::PATH),
412
+ :query => self.encode_component(uri_object.query,
413
+ Addressable::URI::CharacterClasses::QUERY),
414
+ :fragment => self.encode_component(uri_object.fragment,
415
+ Addressable::URI::CharacterClasses::FRAGMENT)
416
+ )
417
+ if returning == String
418
+ return encoded_uri.to_s
419
+ elsif returning == ::Addressable::URI
420
+ return encoded_uri
421
+ end
422
+ end
423
+
424
+ class << self
425
+ alias_method :escape, :encode
426
+ end
427
+
428
+ ##
429
+ # Normalizes the encoding of a URI. Characters within a hostname are
430
+ # not percent encoded to allow for internationalized domain names.
431
+ #
432
+ # @param [String, Addressable::URI, #to_str] uri
433
+ # The URI to encode.
434
+ #
435
+ # @param [Class] returning
436
+ # The type of object to return. This value may only be set to
437
+ # <tt>String</tt> or <tt>Addressable::URI</tt>. All other values
438
+ # are invalid. Defaults to <tt>String</tt>.
439
+ #
440
+ # @return [String, Addressable::URI]
441
+ # The encoded URI. The return type is determined by
442
+ # the <tt>returning</tt> parameter.
443
+ def self.normalized_encode(uri, returning=String)
444
+ if !uri.respond_to?(:to_str)
445
+ raise TypeError, "Can't convert #{uri.class} into String."
446
+ end
447
+ if ![String, ::Addressable::URI].include?(returning)
448
+ raise TypeError,
449
+ "Expected Class (String or Addressable::URI), " +
450
+ "got #{returning.inspect}"
451
+ end
452
+ uri_object = uri.kind_of?(self) ? uri : self.parse(uri.to_str)
453
+ components = {
454
+ :scheme => self.unencode_component(uri_object.scheme),
455
+ :user => self.unencode_component(uri_object.user),
456
+ :password => self.unencode_component(uri_object.password),
457
+ :host => self.unencode_component(uri_object.host),
458
+ :port => uri_object.port,
459
+ :path => self.unencode_component(uri_object.path),
460
+ :query => self.unencode_component(uri_object.query),
461
+ :fragment => self.unencode_component(uri_object.fragment)
462
+ }
463
+ components.each do |key, value|
464
+ if value != nil
465
+ begin
466
+ components[key] =
467
+ Addressable::IDNA.unicode_normalize_kc(value.to_str)
468
+ rescue ArgumentError
469
+ # Likely a malformed UTF-8 character, skip unicode normalization
470
+ components[key] = value.to_str
471
+ end
472
+ end
473
+ end
474
+ encoded_uri = Addressable::URI.new(
475
+ :scheme => self.encode_component(components[:scheme],
476
+ Addressable::URI::CharacterClasses::SCHEME),
477
+ :user => self.encode_component(components[:user],
478
+ Addressable::URI::CharacterClasses::UNRESERVED),
479
+ :password => self.encode_component(components[:password],
480
+ Addressable::URI::CharacterClasses::UNRESERVED),
481
+ :host => components[:host],
482
+ :port => components[:port],
483
+ :path => self.encode_component(components[:path],
484
+ Addressable::URI::CharacterClasses::PATH),
485
+ :query => self.encode_component(components[:query],
486
+ Addressable::URI::CharacterClasses::QUERY),
487
+ :fragment => self.encode_component(components[:fragment],
488
+ Addressable::URI::CharacterClasses::FRAGMENT)
489
+ )
490
+ if returning == String
491
+ return encoded_uri.to_s
492
+ elsif returning == ::Addressable::URI
493
+ return encoded_uri
494
+ end
495
+ end
496
+
497
+ ##
498
+ # Creates a new uri object from component parts.
499
+ #
500
+ # @option [String, #to_str] scheme The scheme component.
501
+ # @option [String, #to_str] user The user component.
502
+ # @option [String, #to_str] password The password component.
503
+ # @option [String, #to_str] userinfo
504
+ # The userinfo component. If this is supplied, the user and password
505
+ # components must be omitted.
506
+ # @option [String, #to_str] host The host component.
507
+ # @option [String, #to_str] port The port component.
508
+ # @option [String, #to_str] authority
509
+ # The authority component. If this is supplied, the user, password,
510
+ # userinfo, host, and port components must be omitted.
511
+ # @option [String, #to_str] path The path component.
512
+ # @option [String, #to_str] query The query component.
513
+ # @option [String, #to_str] fragment The fragment component.
514
+ #
515
+ # @return [Addressable::URI] The constructed URI object.
516
+ def initialize(options={})
517
+ if options.has_key?(:authority)
518
+ if (options.keys & [:userinfo, :user, :password, :host, :port]).any?
519
+ raise ArgumentError,
520
+ "Cannot specify both an authority and any of the components " +
521
+ "within the authority."
522
+ end
523
+ end
524
+ if options.has_key?(:userinfo)
525
+ if (options.keys & [:user, :password]).any?
526
+ raise ArgumentError,
527
+ "Cannot specify both a userinfo and either the user or password."
528
+ end
529
+ end
530
+
531
+ self.validation_deferred = true
532
+ self.scheme = options[:scheme] if options[:scheme]
533
+ self.user = options[:user] if options[:user]
534
+ self.password = options[:password] if options[:password]
535
+ self.userinfo = options[:userinfo] if options[:userinfo]
536
+ self.host = options[:host] if options[:host]
537
+ self.port = options[:port] if options[:port]
538
+ self.authority = options[:authority] if options[:authority]
539
+ self.path = options[:path] if options[:path]
540
+ self.query = options[:query] if options[:query]
541
+ self.fragment = options[:fragment] if options[:fragment]
542
+ self.validation_deferred = false
543
+ end
544
+
545
+ ##
546
+ # The scheme component for this URI.
547
+ #
548
+ # @return [String] The scheme component.
549
+ def scheme
550
+ return @scheme
551
+ end
552
+
553
+ ##
554
+ # The scheme component for this URI, normalized.
555
+ #
556
+ # @return [String] The scheme component, normalized.
557
+ def normalized_scheme
558
+ @normalized_scheme ||= (begin
559
+ if self.scheme != nil
560
+ if self.scheme =~ /^\s*ssh\+svn\s*$/i
561
+ "svn+ssh"
562
+ else
563
+ Addressable::URI.encode_component(
564
+ Addressable::IDNA.unicode_normalize_kc(
565
+ Addressable::URI.unencode_component(
566
+ self.scheme.strip.downcase)),
567
+ Addressable::URI::CharacterClasses::SCHEME
568
+ )
569
+ end
570
+ else
571
+ nil
572
+ end
573
+ end)
574
+ end
575
+
576
+ ##
577
+ # Sets the scheme component for this URI.
578
+ #
579
+ # @param [String, #to_str] new_scheme The new scheme component.
580
+ def scheme=(new_scheme)
581
+ # Check for frozenness
582
+ raise TypeError, "Can't modify frozen URI." if self.frozen?
583
+
584
+ if new_scheme && !new_scheme.respond_to?(:to_str)
585
+ raise TypeError, "Can't convert #{new_scheme.class} into String."
586
+ end
587
+ @scheme = new_scheme ? new_scheme.to_str : nil
588
+ @scheme = nil if @scheme.to_s.strip == ""
589
+
590
+ # Reset dependant values
591
+ @normalized_scheme = nil
592
+ @uri_string = nil
593
+
594
+ # Ensure we haven't created an invalid URI
595
+ validate()
596
+ end
597
+
598
+ ##
599
+ # The user component for this URI.
600
+ #
601
+ # @return [String] The user component.
602
+ def user
603
+ return @user
604
+ end
605
+
606
+ ##
607
+ # The user component for this URI, normalized.
608
+ #
609
+ # @return [String] The user component, normalized.
610
+ def normalized_user
611
+ @normalized_user ||= (begin
612
+ if self.user
613
+ if normalized_scheme =~ /https?/ && self.user.strip == "" &&
614
+ (!self.password || self.password.strip == "")
615
+ nil
616
+ else
617
+ Addressable::URI.encode_component(
618
+ Addressable::IDNA.unicode_normalize_kc(
619
+ Addressable::URI.unencode_component(self.user.strip)),
620
+ Addressable::URI::CharacterClasses::UNRESERVED
621
+ )
622
+ end
623
+ else
624
+ nil
625
+ end
626
+ end)
627
+ end
628
+
629
+ ##
630
+ # Sets the user component for this URI.
631
+ #
632
+ # @param [String, #to_str] new_user The new user component.
633
+ def user=(new_user)
634
+ # Check for frozenness
635
+ raise TypeError, "Can't modify frozen URI." if self.frozen?
636
+
637
+ if new_user && !new_user.respond_to?(:to_str)
638
+ raise TypeError, "Can't convert #{new_user.class} into String."
639
+ end
640
+ @user = new_user ? new_user.to_str : nil
641
+
642
+ # You can't have a nil user with a non-nil password
643
+ @password ||= nil
644
+ if @password != nil
645
+ @user = "" if @user.nil?
646
+ end
647
+
648
+ # Reset dependant values
649
+ @userinfo = nil
650
+ @normalized_userinfo = nil
651
+ @authority = nil
652
+ @normalized_user = nil
653
+ @uri_string = nil
654
+
655
+ # Ensure we haven't created an invalid URI
656
+ validate()
657
+ end
658
+
659
+ ##
660
+ # The password component for this URI.
661
+ #
662
+ # @return [String] The password component.
663
+ def password
664
+ return @password
665
+ end
666
+
667
+ ##
668
+ # The password component for this URI, normalized.
669
+ #
670
+ # @return [String] The password component, normalized.
671
+ def normalized_password
672
+ @normalized_password ||= (begin
673
+ if self.password
674
+ if normalized_scheme =~ /https?/ && self.password.strip == "" &&
675
+ (!self.user || self.user.strip == "")
676
+ nil
677
+ else
678
+ Addressable::URI.encode_component(
679
+ Addressable::IDNA.unicode_normalize_kc(
680
+ Addressable::URI.unencode_component(self.password.strip)),
681
+ Addressable::URI::CharacterClasses::UNRESERVED
682
+ )
683
+ end
684
+ else
685
+ nil
686
+ end
687
+ end)
688
+ end
689
+
690
+ ##
691
+ # Sets the password component for this URI.
692
+ #
693
+ # @param [String, #to_str] new_password The new password component.
694
+ def password=(new_password)
695
+ # Check for frozenness
696
+ raise TypeError, "Can't modify frozen URI." if self.frozen?
697
+
698
+ if new_password && !new_password.respond_to?(:to_str)
699
+ raise TypeError, "Can't convert #{new_password.class} into String."
700
+ end
701
+ @password = new_password ? new_password.to_str : nil
702
+
703
+ # You can't have a nil user with a non-nil password
704
+ @password ||= nil
705
+ @user ||= nil
706
+ if @password != nil
707
+ @user = "" if @user.nil?
708
+ end
709
+
710
+ # Reset dependant values
711
+ @userinfo = nil
712
+ @normalized_userinfo = nil
713
+ @authority = nil
714
+ @normalized_password = nil
715
+ @uri_string = nil
716
+
717
+ # Ensure we haven't created an invalid URI
718
+ validate()
719
+ end
720
+
721
+ ##
722
+ # The userinfo component for this URI.
723
+ # Combines the user and password components.
724
+ #
725
+ # @return [String] The userinfo component.
726
+ def userinfo
727
+ @userinfo ||= (begin
728
+ current_user = self.user
729
+ current_password = self.password
730
+ if !current_user && !current_password
731
+ nil
732
+ elsif current_user && current_password
733
+ "#{current_user}:#{current_password}"
734
+ elsif current_user && !current_password
735
+ "#{current_user}"
736
+ end
737
+ end)
738
+ end
739
+
740
+ ##
741
+ # The userinfo component for this URI, normalized.
742
+ #
743
+ # @return [String] The userinfo component, normalized.
744
+ def normalized_userinfo
745
+ @normalized_userinfo ||= (begin
746
+ current_user = self.normalized_user
747
+ current_password = self.normalized_password
748
+ if !current_user && !current_password
749
+ nil
750
+ elsif current_user && current_password
751
+ "#{current_user}:#{current_password}"
752
+ elsif current_user && !current_password
753
+ "#{current_user}"
754
+ end
755
+ end)
756
+ end
757
+
758
+ ##
759
+ # Sets the userinfo component for this URI.
760
+ #
761
+ # @param [String, #to_str] new_userinfo The new userinfo component.
762
+ def userinfo=(new_userinfo)
763
+ # Check for frozenness
764
+ raise TypeError, "Can't modify frozen URI." if self.frozen?
765
+
766
+ if new_userinfo && !new_userinfo.respond_to?(:to_str)
767
+ raise TypeError, "Can't convert #{new_userinfo.class} into String."
768
+ end
769
+ new_user, new_password = if new_userinfo
770
+ [
771
+ new_userinfo.to_str.strip[/^(.*):/, 1],
772
+ new_userinfo.to_str.strip[/:(.*)$/, 1]
773
+ ]
774
+ else
775
+ [nil, nil]
776
+ end
777
+
778
+ # Password assigned first to ensure validity in case of nil
779
+ self.password = new_password
780
+ self.user = new_user
781
+
782
+ # Reset dependant values
783
+ @authority = nil
784
+ @uri_string = nil
785
+
786
+ # Ensure we haven't created an invalid URI
787
+ validate()
788
+ end
789
+
790
+ ##
791
+ # The host component for this URI.
792
+ #
793
+ # @return [String] The host component.
794
+ def host
795
+ return @host
796
+ end
797
+
798
+ ##
799
+ # The host component for this URI, normalized.
800
+ #
801
+ # @return [String] The host component, normalized.
802
+ def normalized_host
803
+ @normalized_host ||= (begin
804
+ if self.host != nil
805
+ if self.host.strip != ""
806
+ result = ::Addressable::IDNA.to_ascii(
807
+ self.class.unencode_component(self.host.strip.downcase)
808
+ )
809
+ if result[-1..-1] == "."
810
+ # Trailing dots are unnecessary
811
+ result = result[0...-1]
812
+ end
813
+ result
814
+ else
815
+ ""
816
+ end
817
+ else
818
+ nil
819
+ end
820
+ end)
821
+ end
822
+
823
+ ##
824
+ # Sets the host component for this URI.
825
+ #
826
+ # @param [String, #to_str] new_host The new host component.
827
+ def host=(new_host)
828
+ # Check for frozenness
829
+ raise TypeError, "Can't modify frozen URI." if self.frozen?
830
+
831
+ if new_host && !new_host.respond_to?(:to_str)
832
+ raise TypeError, "Can't convert #{new_host.class} into String."
833
+ end
834
+ @host = new_host ? new_host.to_str : nil
835
+
836
+ # Reset dependant values
837
+ @authority = nil
838
+ @normalized_host = nil
839
+ @uri_string = nil
840
+
841
+ # Ensure we haven't created an invalid URI
842
+ validate()
843
+ end
844
+
845
+ ##
846
+ # The authority component for this URI.
847
+ # Combines the user, password, host, and port components.
848
+ #
849
+ # @return [String] The authority component.
850
+ def authority
851
+ @authority ||= (begin
852
+ if self.host.nil?
853
+ nil
854
+ else
855
+ authority = ""
856
+ if self.userinfo != nil
857
+ authority << "#{self.userinfo}@"
858
+ end
859
+ authority << self.host
860
+ if self.port != nil
861
+ authority << ":#{self.port}"
862
+ end
863
+ authority
864
+ end
865
+ end)
866
+ end
867
+
868
+ ##
869
+ # The authority component for this URI, normalized.
870
+ #
871
+ # @return [String] The authority component, normalized.
872
+ def normalized_authority
873
+ @normalized_authority ||= (begin
874
+ if self.normalized_host.nil?
875
+ nil
876
+ else
877
+ authority = ""
878
+ if self.normalized_userinfo != nil
879
+ authority << "#{self.normalized_userinfo}@"
880
+ end
881
+ authority << self.normalized_host
882
+ if self.normalized_port != nil
883
+ authority << ":#{self.normalized_port}"
884
+ end
885
+ authority
886
+ end
887
+ end)
888
+ end
889
+
890
+ ##
891
+ # Sets the authority component for this URI.
892
+ #
893
+ # @param [String, #to_str] new_authority The new authority component.
894
+ def authority=(new_authority)
895
+ # Check for frozenness
896
+ raise TypeError, "Can't modify frozen URI." if self.frozen?
897
+
898
+ if new_authority
899
+ if !new_authority.respond_to?(:to_str)
900
+ raise TypeError, "Can't convert #{new_authority.class} into String."
901
+ end
902
+ new_authority = new_authority.to_str
903
+ new_userinfo = new_authority[/^([^\[\]]*)@/, 1]
904
+ if new_userinfo
905
+ new_user = new_userinfo.strip[/^([^:]*):?/, 1]
906
+ new_password = new_userinfo.strip[/:(.*)$/, 1]
907
+ end
908
+ new_host =
909
+ new_authority.gsub(/^([^\[\]]*)@/, "").gsub(/:([^:@\[\]]*?)$/, "")
910
+ new_port =
911
+ new_authority[/:([^:@\[\]]*?)$/, 1]
912
+ end
913
+
914
+ # Password assigned first to ensure validity in case of nil
915
+ self.password = defined?(new_password) ? new_password : nil
916
+ self.user = defined?(new_user) ? new_user : nil
917
+ self.host = defined?(new_host) ? new_host : nil
918
+ self.port = defined?(new_port) ? new_port : nil
919
+
920
+ # Reset dependant values
921
+ @inferred_port = nil
922
+ @userinfo = nil
923
+ @normalized_userinfo = nil
924
+ @uri_string = nil
925
+
926
+ # Ensure we haven't created an invalid URI
927
+ validate()
928
+ end
929
+
930
+ # Returns an array of known ip-based schemes. These schemes typically
931
+ # use a similar URI form:
932
+ # //<user>:<password>@<host>:<port>/<url-path>
933
+ def self.ip_based_schemes
934
+ return self.port_mapping.keys
935
+ end
936
+
937
+ # Returns a hash of common IP-based schemes and their default port
938
+ # numbers. Adding new schemes to this hash, as necessary, will allow
939
+ # for better URI normalization.
940
+ def self.port_mapping
941
+ @port_mapping ||= {
942
+ "http" => 80,
943
+ "https" => 443,
944
+ "ftp" => 21,
945
+ "tftp" => 69,
946
+ "sftp" => 22,
947
+ "ssh" => 22,
948
+ "svn+ssh" => 22,
949
+ "telnet" => 23,
950
+ "nntp" => 119,
951
+ "gopher" => 70,
952
+ "wais" => 210,
953
+ "ldap" => 389,
954
+ "prospero" => 1525
955
+ }
956
+ end
957
+
958
+ ##
959
+ # The port component for this URI.
960
+ # This is the port number actually given in the URI. This does not
961
+ # infer port numbers from default values.
962
+ #
963
+ # @return [Integer] The port component.
964
+ def port
965
+ return @port ||= nil
966
+ end
967
+
968
+ ##
969
+ # The port component for this URI, normalized.
970
+ #
971
+ # @return [Integer] The port component, normalized.
972
+ def normalized_port
973
+ @normalized_port ||= (begin
974
+ if self.class.port_mapping[normalized_scheme] == self.port
975
+ nil
976
+ else
977
+ self.port
978
+ end
979
+ end)
980
+ end
981
+
982
+ ##
983
+ # Sets the port component for this URI.
984
+ #
985
+ # @param [String, Integer, #to_s] new_port The new port component.
986
+ def port=(new_port)
987
+ # Check for frozenness
988
+ raise TypeError, "Can't modify frozen URI." if self.frozen?
989
+
990
+ if new_port != nil && new_port.respond_to?(:to_str)
991
+ new_port = Addressable::URI.unencode_component(new_port.to_str)
992
+ end
993
+ if new_port != nil && !(new_port.to_s =~ /^\d+$/)
994
+ raise InvalidURIError,
995
+ "Invalid port number: #{new_port.inspect}"
996
+ end
997
+
998
+ @port = new_port.to_s.to_i
999
+ @port = nil if @port == 0
1000
+
1001
+ # Reset dependant values
1002
+ @authority = nil
1003
+ @inferred_port = nil
1004
+ @normalized_port = nil
1005
+ @uri_string = nil
1006
+
1007
+ # Ensure we haven't created an invalid URI
1008
+ validate()
1009
+ end
1010
+
1011
+ ##
1012
+ # The inferred port component for this URI.
1013
+ # This method will normalize to the default port for the URI's scheme if
1014
+ # the port isn't explicitly specified in the URI.
1015
+ #
1016
+ # @return [Integer] The inferred port component.
1017
+ def inferred_port
1018
+ @inferred_port ||= (begin
1019
+ if port.to_i == 0
1020
+ if scheme
1021
+ self.class.port_mapping[scheme.strip.downcase]
1022
+ else
1023
+ nil
1024
+ end
1025
+ else
1026
+ port.to_i
1027
+ end
1028
+ end)
1029
+ end
1030
+
1031
+ ##
1032
+ # The path component for this URI.
1033
+ #
1034
+ # @return [String] The path component.
1035
+ def path
1036
+ return (@path || "")
1037
+ end
1038
+
1039
+ ##
1040
+ # The path component for this URI, normalized.
1041
+ #
1042
+ # @return [String] The path component, normalized.
1043
+ def normalized_path
1044
+ @normalized_path ||= (begin
1045
+ begin
1046
+ result = Addressable::URI.encode_component(
1047
+ Addressable::IDNA.unicode_normalize_kc(
1048
+ Addressable::URI.unencode_component(self.path.strip)),
1049
+ Addressable::URI::CharacterClasses::PATH
1050
+ )
1051
+ rescue ArgumentError
1052
+ # Likely a malformed UTF-8 character, skip unicode normalization
1053
+ result = Addressable::URI.encode_component(
1054
+ Addressable::URI.unencode_component(self.path.strip),
1055
+ Addressable::URI::CharacterClasses::PATH
1056
+ )
1057
+ end
1058
+ result = self.class.normalize_path(result)
1059
+ if result == "" &&
1060
+ ["http", "https", "ftp", "tftp"].include?(self.normalized_scheme)
1061
+ result = "/"
1062
+ end
1063
+ result
1064
+ end)
1065
+ end
1066
+
1067
+ ##
1068
+ # Sets the path component for this URI.
1069
+ #
1070
+ # @param [String, #to_str] new_path The new path component.
1071
+ def path=(new_path)
1072
+ # Check for frozenness
1073
+ raise TypeError, "Can't modify frozen URI." if self.frozen?
1074
+
1075
+ if new_path && !new_path.respond_to?(:to_str)
1076
+ raise TypeError, "Can't convert #{new_path.class} into String."
1077
+ end
1078
+ @path = (new_path || "").to_str
1079
+ if @path != "" && @path[0..0] != "/" && host != nil
1080
+ @path = "/#{@path}"
1081
+ end
1082
+
1083
+ # Reset dependant values
1084
+ @normalized_path = nil
1085
+ @uri_string = nil
1086
+ end
1087
+
1088
+ ##
1089
+ # The basename, if any, of the file in the path component.
1090
+ #
1091
+ # @return [String] The path's basename.
1092
+ def basename
1093
+ # Path cannot be nil
1094
+ return File.basename(self.path).gsub(/;[^\/]*$/, "")
1095
+ end
1096
+
1097
+ ##
1098
+ # The extname, if any, of the file in the path component.
1099
+ # Empty string if there is no extension.
1100
+ #
1101
+ # @return [String] The path's extname.
1102
+ def extname
1103
+ return nil unless self.path
1104
+ return File.extname(self.basename)
1105
+ end
1106
+
1107
+ ##
1108
+ # The query component for this URI.
1109
+ #
1110
+ # @return [String] The query component.
1111
+ def query
1112
+ return @query
1113
+ end
1114
+
1115
+ ##
1116
+ # The query component for this URI, normalized.
1117
+ #
1118
+ # @return [String] The query component, normalized.
1119
+ def normalized_query
1120
+ @normalized_query ||= (begin
1121
+ if self.query
1122
+ Addressable::URI.encode_component(
1123
+ Addressable::IDNA.unicode_normalize_kc(
1124
+ Addressable::URI.unencode_component(self.query.strip)),
1125
+ Addressable::URI::CharacterClasses::QUERY
1126
+ )
1127
+ else
1128
+ nil
1129
+ end
1130
+ end)
1131
+ end
1132
+
1133
+ ##
1134
+ # Sets the query component for this URI.
1135
+ #
1136
+ # @param [String, #to_str] new_query The new query component.
1137
+ def query=(new_query)
1138
+ # Check for frozenness
1139
+ raise TypeError, "Can't modify frozen URI." if self.frozen?
1140
+
1141
+ if new_query && !new_query.respond_to?(:to_str)
1142
+ raise TypeError, "Can't convert #{new_query.class} into String."
1143
+ end
1144
+ @query = new_query ? new_query.to_str : nil
1145
+
1146
+ # Reset dependant values
1147
+ @normalized_query = nil
1148
+ @uri_string = nil
1149
+ end
1150
+
1151
+ ##
1152
+ # Converts the query component to a Hash value.
1153
+ #
1154
+ # @option [Symbol] notation
1155
+ # May be one of <tt>:flat</tt>, <tt>:dot</tt>, or <tt>:subscript</tt>.
1156
+ # The <tt>:dot</tt> notation is not supported for assignment.
1157
+ # Default value is <tt>:subscript</tt>.
1158
+ #
1159
+ # @return [Hash] The query string parsed as a Hash object.
1160
+ #
1161
+ # @example
1162
+ # Addressable::URI.parse("?one=1&two=2&three=3").query_values
1163
+ # #=> {"one" => "1", "two" => "2", "three" => "3"}
1164
+ # Addressable::URI.parse("?one[two][three]=four").query_values
1165
+ # #=> {"one" => {"two" => {"three" => "four"}}}
1166
+ # Addressable::URI.parse("?one.two.three=four").query_values(
1167
+ # :notation => :dot
1168
+ # )
1169
+ # #=> {"one" => {"two" => {"three" => "four"}}}
1170
+ # Addressable::URI.parse("?one[two][three]=four").query_values(
1171
+ # :notation => :flat
1172
+ # )
1173
+ # #=> {"one[two][three]" => "four"}
1174
+ # Addressable::URI.parse("?one.two.three=four").query_values(
1175
+ # :notation => :flat
1176
+ # )
1177
+ # #=> {"one.two.three" => "four"}
1178
+ # Addressable::URI.parse(
1179
+ # "?one[two][three][]=four&one[two][three][]=five"
1180
+ # ).query_values
1181
+ # #=> {"one" => {"two" => {"three" => ["four", "five"]}}}
1182
+ def query_values(options={})
1183
+ defaults = {:notation => :subscript}
1184
+ options = defaults.merge(options)
1185
+ if ![:flat, :dot, :subscript].include?(options[:notation])
1186
+ raise ArgumentError,
1187
+ "Invalid notation. Must be one of: [:flat, :dot, :subscript]."
1188
+ end
1189
+ dehash = lambda do |hash|
1190
+ hash.each do |(key, value)|
1191
+ if value.kind_of?(Hash)
1192
+ hash[key] = dehash.call(value)
1193
+ end
1194
+ end
1195
+ if hash != {} && hash.keys.all? { |key| key =~ /^\d+$/ }
1196
+ hash.sort.inject([]) do |accu, (key, value)|
1197
+ accu << value; accu
1198
+ end
1199
+ else
1200
+ hash
1201
+ end
1202
+ end
1203
+ return nil if self.query == nil
1204
+ return ((self.query.split("&").map do |pair|
1205
+ pair.split("=")
1206
+ end).inject({}) do |accumulator, (key, value)|
1207
+ value = true if value.nil?
1208
+ key = self.class.unencode_component(key)
1209
+ if value != true
1210
+ value = self.class.unencode_component(value).gsub(/\+/, " ")
1211
+ end
1212
+ if options[:notation] == :flat
1213
+ if accumulator[key]
1214
+ raise ArgumentError, "Key was repeated: #{key.inspect}"
1215
+ end
1216
+ accumulator[key] = value
1217
+ else
1218
+ if options[:notation] == :dot
1219
+ array_value = false
1220
+ subkeys = key.split(".")
1221
+ elsif options[:notation] == :subscript
1222
+ array_value = !!(key =~ /\[\]$/)
1223
+ subkeys = key.split(/[\[\]]+/)
1224
+ end
1225
+ current_hash = accumulator
1226
+ for i in 0...(subkeys.size - 1)
1227
+ subkey = subkeys[i]
1228
+ current_hash[subkey] = {} unless current_hash[subkey]
1229
+ current_hash = current_hash[subkey]
1230
+ end
1231
+ if array_value
1232
+ current_hash[subkeys.last] = [] unless current_hash[subkeys.last]
1233
+ current_hash[subkeys.last] << value
1234
+ else
1235
+ current_hash[subkeys.last] = value
1236
+ end
1237
+ end
1238
+ accumulator
1239
+ end).inject({}) do |accumulator, (key, value)|
1240
+ accumulator[key] = value.kind_of?(Hash) ? dehash.call(value) : value
1241
+ accumulator
1242
+ end
1243
+ end
1244
+
1245
+ ##
1246
+ # Sets the query component for this URI from a Hash object.
1247
+ # This method produces a query string using the :subscript notation.
1248
+ #
1249
+ # @param [Hash, #to_hash] new_query_values The new query values.
1250
+ def query_values=(new_query_values)
1251
+ # Check for frozenness
1252
+ raise TypeError, "Can't modify frozen URI." if self.frozen?
1253
+ if !new_query_values.respond_to?(:to_hash)
1254
+ raise TypeError, "Can't convert #{new_query_values.class} into Hash."
1255
+ end
1256
+ new_query_values = new_query_values.to_hash
1257
+
1258
+ # Algorithm shamelessly stolen from Julien Genestoux, slightly modified
1259
+ buffer = ""
1260
+ stack = []
1261
+ e = lambda do |component|
1262
+ component = component.to_s if component.kind_of?(Symbol)
1263
+ self.class.encode_component(component, CharacterClasses::UNRESERVED)
1264
+ end
1265
+ new_query_values.each do |key, value|
1266
+ if value.kind_of?(Hash)
1267
+ stack << [key, value]
1268
+ elsif value.kind_of?(Array)
1269
+ stack << [
1270
+ key,
1271
+ value.inject({}) { |accu, x| accu[accu.size.to_s] = x; accu }
1272
+ ]
1273
+ elsif value == true
1274
+ buffer << "#{e.call(key)}&"
1275
+ else
1276
+ buffer << "#{e.call(key)}=#{e.call(value)}&"
1277
+ end
1278
+ end
1279
+ stack.each do |(parent, hash)|
1280
+ (hash.sort_by { |key| key.to_s }).each do |(key, value)|
1281
+ if value.kind_of?(Hash)
1282
+ stack << ["#{parent}[#{key}]", value]
1283
+ elsif value == true
1284
+ buffer << "#{parent}[#{e.call(key)}]&"
1285
+ else
1286
+ buffer << "#{parent}[#{e.call(key)}]=#{e.call(value)}&"
1287
+ end
1288
+ end
1289
+ end
1290
+ @query = buffer.chop
1291
+
1292
+ # Reset dependant values
1293
+ @normalized_query = nil
1294
+ @uri_string = nil
1295
+ end
1296
+
1297
+ ##
1298
+ # The fragment component for this URI.
1299
+ #
1300
+ # @return [String] The fragment component.
1301
+ def fragment
1302
+ return @fragment
1303
+ end
1304
+
1305
+ ##
1306
+ # The fragment component for this URI, normalized.
1307
+ #
1308
+ # @return [String] The fragment component, normalized.
1309
+ def normalized_fragment
1310
+ @normalized_fragment ||= (begin
1311
+ if self.fragment
1312
+ Addressable::URI.encode_component(
1313
+ Addressable::IDNA.unicode_normalize_kc(
1314
+ Addressable::URI.unencode_component(self.fragment.strip)),
1315
+ Addressable::URI::CharacterClasses::FRAGMENT
1316
+ )
1317
+ else
1318
+ nil
1319
+ end
1320
+ end)
1321
+ end
1322
+
1323
+ ##
1324
+ # Sets the fragment component for this URI.
1325
+ #
1326
+ # @param [String, #to_str] new_fragment The new fragment component.
1327
+ def fragment=(new_fragment)
1328
+ # Check for frozenness
1329
+ raise TypeError, "Can't modify frozen URI." if self.frozen?
1330
+
1331
+ if new_fragment && !new_fragment.respond_to?(:to_str)
1332
+ raise TypeError, "Can't convert #{new_fragment.class} into String."
1333
+ end
1334
+ @fragment = new_fragment ? new_fragment.to_str : nil
1335
+
1336
+ # Reset dependant values
1337
+ @normalized_fragment = nil
1338
+ @uri_string = nil
1339
+
1340
+ # Ensure we haven't created an invalid URI
1341
+ validate()
1342
+ end
1343
+
1344
+ ##
1345
+ # Determines if the scheme indicates an IP-based protocol.
1346
+ #
1347
+ # @return [TrueClass, FalseClass]
1348
+ # <tt>true</tt> if the scheme indicates an IP-based protocol.
1349
+ # <tt>false</tt> otherwise.
1350
+ def ip_based?
1351
+ if self.scheme
1352
+ return self.class.ip_based_schemes.include?(
1353
+ self.scheme.strip.downcase)
1354
+ end
1355
+ return false
1356
+ end
1357
+
1358
+ ##
1359
+ # Determines if the URI is relative.
1360
+ #
1361
+ # @return [TrueClass, FalseClass]
1362
+ # <tt>true</tt> if the URI is relative.
1363
+ # <tt>false</tt> otherwise.
1364
+ def relative?
1365
+ return self.scheme.nil?
1366
+ end
1367
+
1368
+ ##
1369
+ # Determines if the URI is absolute.
1370
+ #
1371
+ # @return [TrueClass, FalseClass]
1372
+ # <tt>true</tt> if the URI is absolute.
1373
+ # <tt>false</tt> otherwise.
1374
+ def absolute?
1375
+ return !relative?
1376
+ end
1377
+
1378
+ ##
1379
+ # Joins two URIs together.
1380
+ #
1381
+ # @param [String, Addressable::URI, #to_str] The URI to join with.
1382
+ #
1383
+ # @return [Addressable::URI] The joined URI.
1384
+ def join(uri)
1385
+ if !uri.respond_to?(:to_str)
1386
+ raise TypeError, "Can't convert #{uri.class} into String."
1387
+ end
1388
+ if !uri.kind_of?(self.class)
1389
+ # Otherwise, convert to a String, then parse.
1390
+ uri = self.class.parse(uri.to_str)
1391
+ end
1392
+ if uri.to_s == ""
1393
+ return self.dup
1394
+ end
1395
+
1396
+ joined_scheme = nil
1397
+ joined_user = nil
1398
+ joined_password = nil
1399
+ joined_host = nil
1400
+ joined_port = nil
1401
+ joined_path = nil
1402
+ joined_query = nil
1403
+ joined_fragment = nil
1404
+
1405
+ # Section 5.2.2 of RFC 3986
1406
+ if uri.scheme != nil
1407
+ joined_scheme = uri.scheme
1408
+ joined_user = uri.user
1409
+ joined_password = uri.password
1410
+ joined_host = uri.host
1411
+ joined_port = uri.port
1412
+ joined_path = self.class.normalize_path(uri.path)
1413
+ joined_query = uri.query
1414
+ else
1415
+ if uri.authority != nil
1416
+ joined_user = uri.user
1417
+ joined_password = uri.password
1418
+ joined_host = uri.host
1419
+ joined_port = uri.port
1420
+ joined_path = self.class.normalize_path(uri.path)
1421
+ joined_query = uri.query
1422
+ else
1423
+ if uri.path == nil || uri.path == ""
1424
+ joined_path = self.path
1425
+ if uri.query != nil
1426
+ joined_query = uri.query
1427
+ else
1428
+ joined_query = self.query
1429
+ end
1430
+ else
1431
+ if uri.path[0..0] == "/"
1432
+ joined_path = self.class.normalize_path(uri.path)
1433
+ else
1434
+ base_path = self.path.dup
1435
+ base_path = "" if base_path == nil
1436
+ base_path = self.class.normalize_path(base_path)
1437
+
1438
+ # Section 5.2.3 of RFC 3986
1439
+ #
1440
+ # Removes the right-most path segment from the base path.
1441
+ if base_path =~ /\//
1442
+ base_path.gsub!(/\/[^\/]+$/, "/")
1443
+ else
1444
+ base_path = ""
1445
+ end
1446
+
1447
+ # If the base path is empty and an authority segment has been
1448
+ # defined, use a base path of "/"
1449
+ if base_path == "" && self.authority != nil
1450
+ base_path = "/"
1451
+ end
1452
+
1453
+ joined_path = self.class.normalize_path(base_path + uri.path)
1454
+ end
1455
+ joined_query = uri.query
1456
+ end
1457
+ joined_user = self.user
1458
+ joined_password = self.password
1459
+ joined_host = self.host
1460
+ joined_port = self.port
1461
+ end
1462
+ joined_scheme = self.scheme
1463
+ end
1464
+ joined_fragment = uri.fragment
1465
+
1466
+ return Addressable::URI.new(
1467
+ :scheme => joined_scheme,
1468
+ :user => joined_user,
1469
+ :password => joined_password,
1470
+ :host => joined_host,
1471
+ :port => joined_port,
1472
+ :path => joined_path,
1473
+ :query => joined_query,
1474
+ :fragment => joined_fragment
1475
+ )
1476
+ end
1477
+ alias_method :+, :join
1478
+
1479
+ ##
1480
+ # Destructive form of <tt>join</tt>.
1481
+ #
1482
+ # @param [String, Addressable::URI, #to_str] The URI to join with.
1483
+ #
1484
+ # @return [Addressable::URI] The joined URI.
1485
+ #
1486
+ # @see Addressable::URI#join
1487
+ def join!(uri)
1488
+ replace_self(self.join(uri))
1489
+ end
1490
+
1491
+ ##
1492
+ # Merges a URI with a <tt>Hash</tt> of components.
1493
+ # This method has different behavior from <tt>join</tt>. Any components
1494
+ # present in the <tt>hash</tt> parameter will override the original
1495
+ # components. The path component is not treated specially.
1496
+ #
1497
+ # @param [Hash, Addressable::URI, #to_hash] The components to merge with.
1498
+ #
1499
+ # @return [Addressable::URI] The merged URI.
1500
+ #
1501
+ # @see Hash#merge
1502
+ def merge(hash)
1503
+ if !hash.respond_to?(:to_hash)
1504
+ raise TypeError, "Can't convert #{hash.class} into Hash."
1505
+ end
1506
+ hash = hash.to_hash
1507
+
1508
+ if hash.has_key?(:authority)
1509
+ if (hash.keys & [:userinfo, :user, :password, :host, :port]).any?
1510
+ raise ArgumentError,
1511
+ "Cannot specify both an authority and any of the components " +
1512
+ "within the authority."
1513
+ end
1514
+ end
1515
+ if hash.has_key?(:userinfo)
1516
+ if (hash.keys & [:user, :password]).any?
1517
+ raise ArgumentError,
1518
+ "Cannot specify both a userinfo and either the user or password."
1519
+ end
1520
+ end
1521
+
1522
+ uri = Addressable::URI.new
1523
+ uri.validation_deferred = true
1524
+ uri.scheme =
1525
+ hash.has_key?(:scheme) ? hash[:scheme] : self.scheme
1526
+ if hash.has_key?(:authority)
1527
+ uri.authority =
1528
+ hash.has_key?(:authority) ? hash[:authority] : self.authority
1529
+ end
1530
+ if hash.has_key?(:userinfo)
1531
+ uri.userinfo =
1532
+ hash.has_key?(:userinfo) ? hash[:userinfo] : self.userinfo
1533
+ end
1534
+ if !hash.has_key?(:userinfo) && !hash.has_key?(:authority)
1535
+ uri.user =
1536
+ hash.has_key?(:user) ? hash[:user] : self.user
1537
+ uri.password =
1538
+ hash.has_key?(:password) ? hash[:password] : self.password
1539
+ end
1540
+ if !hash.has_key?(:authority)
1541
+ uri.host =
1542
+ hash.has_key?(:host) ? hash[:host] : self.host
1543
+ uri.port =
1544
+ hash.has_key?(:port) ? hash[:port] : self.port
1545
+ end
1546
+ uri.path =
1547
+ hash.has_key?(:path) ? hash[:path] : self.path
1548
+ uri.query =
1549
+ hash.has_key?(:query) ? hash[:query] : self.query
1550
+ uri.fragment =
1551
+ hash.has_key?(:fragment) ? hash[:fragment] : self.fragment
1552
+ uri.validation_deferred = false
1553
+
1554
+ return uri
1555
+ end
1556
+
1557
+ ##
1558
+ # Destructive form of <tt>merge</tt>.
1559
+ #
1560
+ # @param [Hash, Addressable::URI, #to_hash] The components to merge with.
1561
+ #
1562
+ # @return [Addressable::URI] The merged URI.
1563
+ #
1564
+ # @see Addressable::URI#merge
1565
+ def merge!(uri)
1566
+ replace_self(self.merge(uri))
1567
+ end
1568
+
1569
+ ##
1570
+ # Returns the shortest normalized relative form of this URI that uses the
1571
+ # supplied URI as a base for resolution. Returns an absolute URI if
1572
+ # necessary. This is effectively the opposite of <tt>route_to</tt>.
1573
+ #
1574
+ # @param [String, Addressable::URI, #to_str] uri The URI to route from.
1575
+ #
1576
+ # @return [Addressable::URI]
1577
+ # The normalized relative URI that is equivalent to the original URI.
1578
+ def route_from(uri)
1579
+ uri = self.class.parse(uri).normalize
1580
+ normalized_self = self.normalize
1581
+ if normalized_self.relative?
1582
+ raise ArgumentError, "Expected absolute URI, got: #{self.to_s}"
1583
+ end
1584
+ if uri.relative?
1585
+ raise ArgumentError, "Expected absolute URI, got: #{uri.to_s}"
1586
+ end
1587
+ if normalized_self == uri
1588
+ return Addressable::URI.parse("##{normalized_self.fragment}")
1589
+ end
1590
+ components = normalized_self.to_hash
1591
+ if normalized_self.scheme == uri.scheme
1592
+ components[:scheme] = nil
1593
+ if normalized_self.authority == uri.authority
1594
+ components[:user] = nil
1595
+ components[:password] = nil
1596
+ components[:host] = nil
1597
+ components[:port] = nil
1598
+ if normalized_self.path == uri.path
1599
+ components[:path] = nil
1600
+ if normalized_self.query == uri.query
1601
+ components[:query] = nil
1602
+ end
1603
+ else
1604
+ if uri.path != "/"
1605
+ components[:path].gsub!(
1606
+ Regexp.new("^" + Regexp.escape(uri.path)), "")
1607
+ end
1608
+ end
1609
+ end
1610
+ end
1611
+ # Avoid network-path references.
1612
+ if components[:host] != nil
1613
+ components[:scheme] = normalized_self.scheme
1614
+ end
1615
+ return Addressable::URI.new(
1616
+ :scheme => components[:scheme],
1617
+ :user => components[:user],
1618
+ :password => components[:password],
1619
+ :host => components[:host],
1620
+ :port => components[:port],
1621
+ :path => components[:path],
1622
+ :query => components[:query],
1623
+ :fragment => components[:fragment]
1624
+ )
1625
+ end
1626
+
1627
+ ##
1628
+ # Returns the shortest normalized relative form of the supplied URI that
1629
+ # uses this URI as a base for resolution. Returns an absolute URI if
1630
+ # necessary. This is effectively the opposite of <tt>route_from</tt>.
1631
+ #
1632
+ # @param [String, Addressable::URI, #to_str] uri The URI to route to.
1633
+ #
1634
+ # @return [Addressable::URI]
1635
+ # The normalized relative URI that is equivalent to the supplied URI.
1636
+ def route_to(uri)
1637
+ return self.class.parse(uri).route_from(self)
1638
+ end
1639
+
1640
+ ##
1641
+ # Returns a normalized URI object.
1642
+ #
1643
+ # NOTE: This method does not attempt to fully conform to specifications.
1644
+ # It exists largely to correct other people's failures to read the
1645
+ # specifications, and also to deal with caching issues since several
1646
+ # different URIs may represent the same resource and should not be
1647
+ # cached multiple times.
1648
+ #
1649
+ # @return [Addressable::URI] The normalized URI.
1650
+ def normalize
1651
+ # This is a special exception for the frequently misused feed
1652
+ # URI scheme.
1653
+ if normalized_scheme == "feed"
1654
+ if self.to_s =~ /^feed:\/*http:\/*/
1655
+ return self.class.parse(
1656
+ self.to_s[/^feed:\/*(http:\/*.*)/, 1]
1657
+ ).normalize
1658
+ end
1659
+ end
1660
+
1661
+ return Addressable::URI.new(
1662
+ :scheme => normalized_scheme,
1663
+ :authority => normalized_authority,
1664
+ :path => normalized_path,
1665
+ :query => normalized_query,
1666
+ :fragment => normalized_fragment
1667
+ )
1668
+ end
1669
+
1670
+ ##
1671
+ # Destructively normalizes this URI object.
1672
+ #
1673
+ # @return [Addressable::URI] The normalized URI.
1674
+ #
1675
+ # @see Addressable::URI#normalize
1676
+ def normalize!
1677
+ replace_self(self.normalize)
1678
+ end
1679
+
1680
+ ##
1681
+ # Creates a URI suitable for display to users. If semantic attacks are
1682
+ # likely, the application should try to detect these and warn the user.
1683
+ # See <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>,
1684
+ # section 7.6 for more information.
1685
+ #
1686
+ # @return [Addressable::URI] A URI suitable for display purposes.
1687
+ def display_uri
1688
+ display_uri = self.normalize
1689
+ display_uri.host = ::Addressable::IDNA.to_unicode(display_uri.host)
1690
+ return display_uri
1691
+ end
1692
+
1693
+ ##
1694
+ # Returns <tt>true</tt> if the URI objects are equal. This method
1695
+ # normalizes both URIs before doing the comparison, and allows comparison
1696
+ # against <tt>Strings</tt>.
1697
+ #
1698
+ # @param [Object] uri The URI to compare.
1699
+ #
1700
+ # @return [TrueClass, FalseClass]
1701
+ # <tt>true</tt> if the URIs are equivalent, <tt>false</tt> otherwise.
1702
+ def ===(uri)
1703
+ if uri.respond_to?(:normalize)
1704
+ uri_string = uri.normalize.to_s
1705
+ else
1706
+ begin
1707
+ uri_string = ::Addressable::URI.parse(uri).normalize.to_s
1708
+ rescue InvalidURIError, TypeError
1709
+ return false
1710
+ end
1711
+ end
1712
+ return self.normalize.to_s == uri_string
1713
+ end
1714
+
1715
+ ##
1716
+ # Returns <tt>true</tt> if the URI objects are equal. This method
1717
+ # normalizes both URIs before doing the comparison.
1718
+ #
1719
+ # @param [Object] uri The URI to compare.
1720
+ #
1721
+ # @return [TrueClass, FalseClass]
1722
+ # <tt>true</tt> if the URIs are equivalent, <tt>false</tt> otherwise.
1723
+ def ==(uri)
1724
+ return false unless uri.kind_of?(self.class)
1725
+ return self.normalize.to_s == uri.normalize.to_s
1726
+ end
1727
+
1728
+ ##
1729
+ # Returns <tt>true</tt> if the URI objects are equal. This method
1730
+ # does NOT normalize either URI before doing the comparison.
1731
+ #
1732
+ # @param [Object] uri The URI to compare.
1733
+ #
1734
+ # @return [TrueClass, FalseClass]
1735
+ # <tt>true</tt> if the URIs are equivalent, <tt>false</tt> otherwise.
1736
+ def eql?(uri)
1737
+ return false unless uri.kind_of?(self.class)
1738
+ return self.to_s == uri.to_s
1739
+ end
1740
+
1741
+ ##
1742
+ # A hash value that will make a URI equivalent to its normalized
1743
+ # form.
1744
+ #
1745
+ # @return [Integer] A hash of the URI.
1746
+ def hash
1747
+ return @hash ||= (self.to_s.hash * -1)
1748
+ end
1749
+
1750
+ ##
1751
+ # Clones the URI object.
1752
+ #
1753
+ # @return [Addressable::URI] The cloned URI.
1754
+ def dup
1755
+ duplicated_uri = Addressable::URI.new(
1756
+ :scheme => self.scheme ? self.scheme.dup : nil,
1757
+ :user => self.user ? self.user.dup : nil,
1758
+ :password => self.password ? self.password.dup : nil,
1759
+ :host => self.host ? self.host.dup : nil,
1760
+ :port => self.port,
1761
+ :path => self.path ? self.path.dup : nil,
1762
+ :query => self.query ? self.query.dup : nil,
1763
+ :fragment => self.fragment ? self.fragment.dup : nil
1764
+ )
1765
+ return duplicated_uri
1766
+ end
1767
+
1768
+ ##
1769
+ # Freezes the URI object.
1770
+ #
1771
+ # @return [Addressable::URI] The frozen URI.
1772
+ def freeze
1773
+ # Unfortunately, because of the memoized implementation of many of the
1774
+ # URI methods, the default freeze method will cause unexpected errors.
1775
+ # As an alternative, we freeze the string representation of the URI
1776
+ # instead. This should generally produce the desired effect.
1777
+ self.to_s.freeze
1778
+ return self
1779
+ end
1780
+
1781
+ ##
1782
+ # Determines if the URI is frozen.
1783
+ #
1784
+ # @return [TrueClass, FalseClass]
1785
+ # True if the URI is frozen, false otherwise.
1786
+ def frozen?
1787
+ self.to_s.frozen?
1788
+ end
1789
+
1790
+ ##
1791
+ # Omits components from a URI.
1792
+ #
1793
+ # @param [Symbol] *components The components to be omitted.
1794
+ #
1795
+ # @return [Addressable::URI] The URI with components omitted.
1796
+ #
1797
+ # @example
1798
+ # uri = Addressable::URI.parse("http://example.com/path?query")
1799
+ # #=> #<Addressable::URI:0xcc5e7a URI:http://example.com/path?query>
1800
+ # uri.omit(:scheme, :authority)
1801
+ # #=> #<Addressable::URI:0xcc4d86 URI:/path?query>
1802
+ def omit(*components)
1803
+ invalid_components = components - [
1804
+ :scheme, :user, :password, :userinfo, :host, :port, :authority,
1805
+ :path, :query, :fragment
1806
+ ]
1807
+ unless invalid_components.empty?
1808
+ raise ArgumentError,
1809
+ "Invalid component names: #{invalid_components.inspect}."
1810
+ end
1811
+ duplicated_uri = self.dup
1812
+ duplicated_uri.validation_deferred = true
1813
+ components.each do |component|
1814
+ duplicated_uri.send((component.to_s + "=").to_sym, nil)
1815
+ end
1816
+ duplicated_uri.user = duplicated_uri.normalized_user
1817
+ duplicated_uri.validation_deferred = false
1818
+ duplicated_uri
1819
+ end
1820
+
1821
+ ##
1822
+ # Destructive form of omit.
1823
+ #
1824
+ # @param [Symbol] *components The components to be omitted.
1825
+ #
1826
+ # @return [Addressable::URI] The URI with components omitted.
1827
+ #
1828
+ # @see Addressable::URI#omit
1829
+ def omit!(*components)
1830
+ replace_self(self.omit(*components))
1831
+ end
1832
+
1833
+ ##
1834
+ # Converts the URI to a <tt>String</tt>.
1835
+ #
1836
+ # @return [String] The URI's <tt>String</tt> representation.
1837
+ def to_s
1838
+ @uri_string ||= (begin
1839
+ uri_string = ""
1840
+ uri_string << "#{self.scheme}:" if self.scheme != nil
1841
+ uri_string << "//#{self.authority}" if self.authority != nil
1842
+ uri_string << self.path.to_s
1843
+ uri_string << "?#{self.query}" if self.query != nil
1844
+ uri_string << "##{self.fragment}" if self.fragment != nil
1845
+ if uri_string.respond_to?(:force_encoding)
1846
+ uri_string.force_encoding(Encoding::UTF_8)
1847
+ end
1848
+ uri_string
1849
+ end)
1850
+ end
1851
+
1852
+ ##
1853
+ # URI's are glorified <tt>Strings</tt>. Allow implicit conversion.
1854
+ alias_method :to_str, :to_s
1855
+
1856
+ ##
1857
+ # Returns a Hash of the URI components.
1858
+ #
1859
+ # @return [Hash] The URI as a <tt>Hash</tt> of components.
1860
+ def to_hash
1861
+ return {
1862
+ :scheme => self.scheme,
1863
+ :user => self.user,
1864
+ :password => self.password,
1865
+ :host => self.host,
1866
+ :port => self.port,
1867
+ :path => self.path,
1868
+ :query => self.query,
1869
+ :fragment => self.fragment
1870
+ }
1871
+ end
1872
+
1873
+ ##
1874
+ # Returns a <tt>String</tt> representation of the URI object's state.
1875
+ #
1876
+ # @return [String] The URI object's state, as a <tt>String</tt>.
1877
+ def inspect
1878
+ sprintf("#<%s:%#0x URI:%s>", self.class.to_s, self.object_id, self.to_s)
1879
+ end
1880
+
1881
+ ##
1882
+ # If URI validation needs to be disabled, this can be set to true.
1883
+ #
1884
+ # @return [TrueClass, FalseClass]
1885
+ # <tt>true</tt> if validation has been deferred,
1886
+ # <tt>false</tt> otherwise.
1887
+ def validation_deferred
1888
+ !!@validation_deferred
1889
+ end
1890
+
1891
+ ##
1892
+ # If URI validation needs to be disabled, this can be set to true.
1893
+ #
1894
+ # @param [TrueClass, FalseClass] new_validation_deferred
1895
+ # <tt>true</tt> if validation will be deferred,
1896
+ # <tt>false</tt> otherwise.
1897
+ def validation_deferred=(new_validation_deferred)
1898
+ # Check for frozenness
1899
+ raise TypeError, "Can't modify frozen URI." if self.frozen?
1900
+
1901
+ @validation_deferred = new_validation_deferred
1902
+ validate unless @validation_deferred
1903
+ end
1904
+
1905
+ private
1906
+ ##
1907
+ # Resolves paths to their simplest form.
1908
+ #
1909
+ # @param [String] path The path to normalize.
1910
+ #
1911
+ # @return [String] The normalized path.
1912
+ def self.normalize_path(path)
1913
+ # Section 5.2.4 of RFC 3986
1914
+
1915
+ return nil if path.nil?
1916
+ normalized_path = path.dup
1917
+ previous_state = normalized_path.dup
1918
+ begin
1919
+ previous_state = normalized_path.dup
1920
+ normalized_path.gsub!(/\/\.\//, "/")
1921
+ normalized_path.gsub!(/\/\.$/, "/")
1922
+ parent = normalized_path[/\/([^\/]+)\/\.\.\//, 1]
1923
+ if parent != "." && parent != ".."
1924
+ normalized_path.gsub!(/\/#{parent}\/\.\.\//, "/")
1925
+ end
1926
+ parent = normalized_path[/\/([^\/]+)\/\.\.$/, 1]
1927
+ if parent != "." && parent != ".."
1928
+ normalized_path.gsub!(/\/#{parent}\/\.\.$/, "/")
1929
+ end
1930
+ normalized_path.gsub!(/^\.\.?\/?/, "")
1931
+ normalized_path.gsub!(/^\/\.\.?\//, "/")
1932
+ end until previous_state == normalized_path
1933
+ return normalized_path
1934
+ end
1935
+
1936
+ ##
1937
+ # Ensures that the URI is valid.
1938
+ def validate
1939
+ return if self.validation_deferred
1940
+ if self.scheme != nil &&
1941
+ (self.host == nil || self.host == "") &&
1942
+ (self.path == nil || self.path == "")
1943
+ raise InvalidURIError,
1944
+ "Absolute URI missing hierarchical segment: '#{self.to_s}'"
1945
+ end
1946
+ if self.host == nil
1947
+ if self.port != nil ||
1948
+ self.user != nil ||
1949
+ self.password != nil
1950
+ raise InvalidURIError, "Hostname not supplied: '#{self.to_s}'"
1951
+ end
1952
+ end
1953
+ return nil
1954
+ end
1955
+
1956
+ ##
1957
+ # Replaces the internal state of self with the specified URI's state.
1958
+ # Used in destructive operations to avoid massive code repetition.
1959
+ #
1960
+ # @param [Addressable::URI] uri The URI to replace <tt>self</tt> with.
1961
+ #
1962
+ # @return [Addressable::URI] <tt>self</tt>.
1963
+ def replace_self(uri)
1964
+ # Reset dependant values
1965
+ instance_variables.each do |var|
1966
+ instance_variable_set(var, nil)
1967
+ end
1968
+
1969
+ @scheme = uri.scheme
1970
+ @user = uri.user
1971
+ @password = uri.password
1972
+ @host = uri.host
1973
+ @port = uri.port
1974
+ @path = uri.path
1975
+ @query = uri.query
1976
+ @fragment = uri.fragment
1977
+ return self
1978
+ end
1979
+ end
1980
+ end