honkster-addressable 2.1.2

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