sr-couchy 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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