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.
- data/README.textile +77 -0
- data/Rakefile +46 -0
- data/bin/couchy +80 -0
- data/couchy.gemspec +58 -0
- data/lib/couchy.rb +21 -0
- data/lib/couchy/database.rb +129 -0
- data/lib/couchy/server.rb +89 -0
- data/spec/couchy_spec.rb +71 -0
- data/spec/database_spec.rb +417 -0
- data/spec/fixtures/attachments/test.html +11 -0
- data/spec/fixtures/views/lib.js +3 -0
- data/spec/fixtures/views/test_view/lib.js +3 -0
- data/spec/fixtures/views/test_view/only-map.js +4 -0
- data/spec/fixtures/views/test_view/test-map.js +3 -0
- data/spec/fixtures/views/test_view/test-reduce.js +3 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +5 -0
- data/test/couchy_test.rb +13 -0
- data/test/database_test.rb +193 -0
- data/test/server_test.rb +211 -0
- data/test/test_helper.rb +10 -0
- data/vendor/addressable/.gitignore +7 -0
- data/vendor/addressable/CHANGELOG +51 -0
- data/vendor/addressable/LICENSE +20 -0
- data/vendor/addressable/README +24 -0
- data/vendor/addressable/Rakefile +51 -0
- data/vendor/addressable/lib/addressable/idna.rb +4867 -0
- data/vendor/addressable/lib/addressable/uri.rb +2212 -0
- data/vendor/addressable/lib/addressable/version.rb +35 -0
- data/vendor/addressable/spec/addressable/idna_spec.rb +196 -0
- data/vendor/addressable/spec/addressable/uri_spec.rb +3827 -0
- data/vendor/addressable/spec/data/rfc3986.txt +3419 -0
- data/vendor/addressable/tasks/clobber.rake +2 -0
- data/vendor/addressable/tasks/gem.rake +62 -0
- data/vendor/addressable/tasks/git.rake +40 -0
- data/vendor/addressable/tasks/metrics.rake +22 -0
- data/vendor/addressable/tasks/rdoc.rake +29 -0
- data/vendor/addressable/tasks/rubyforge.rake +89 -0
- data/vendor/addressable/tasks/spec.rake +107 -0
- data/vendor/addressable/website/index.html +107 -0
- 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
|