cerberus 0.7.6 → 0.7.7

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