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