addressable 2.0.2 → 2.1.0
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/CHANGELOG +14 -0
- data/README +18 -6
- data/Rakefile +0 -1
- data/lib/addressable/idna.rb +24 -20
- data/lib/addressable/template.rb +1024 -0
- data/lib/addressable/uri.rb +192 -765
- data/lib/addressable/version.rb +3 -2
- data/spec/addressable/idna_spec.rb +6 -5
- data/spec/addressable/template_spec.rb +2111 -0
- data/spec/addressable/uri_spec.rb +343 -1119
- metadata +4 -2
data/lib/addressable/uri.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# encoding:utf-8
|
2
2
|
#--
|
3
3
|
# Addressable, Copyright (c) 2006-2008 Bob Aman
|
4
4
|
#
|
@@ -44,21 +44,6 @@ module Addressable
|
|
44
44
|
class InvalidOptionError < StandardError
|
45
45
|
end
|
46
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
|
-
# Raised if an invalid template operator is used in a pattern.
|
59
|
-
class TemplateOperatorAbortedError < StandardError
|
60
|
-
end
|
61
|
-
|
62
47
|
##
|
63
48
|
# Container for the character classes specified in
|
64
49
|
# <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
|
@@ -90,11 +75,6 @@ module Addressable
|
|
90
75
|
return nil unless uri
|
91
76
|
# If a URI object is passed, just return itself.
|
92
77
|
return uri if uri.kind_of?(self)
|
93
|
-
if !uri.respond_to?(:to_str)
|
94
|
-
raise TypeError, "Can't convert #{uri.class} into String."
|
95
|
-
end
|
96
|
-
# Otherwise, convert to a String
|
97
|
-
uri = uri.to_str
|
98
78
|
|
99
79
|
# If a URI object of the Ruby standard library variety is passed,
|
100
80
|
# convert it to a string, then parse the string.
|
@@ -104,18 +84,22 @@ module Addressable
|
|
104
84
|
uri = uri.to_s
|
105
85
|
end
|
106
86
|
|
87
|
+
if !uri.respond_to?(:to_str)
|
88
|
+
raise TypeError, "Can't convert #{uri.class} into String."
|
89
|
+
end
|
90
|
+
# Otherwise, convert to a String
|
91
|
+
uri = uri.to_str
|
92
|
+
|
107
93
|
# This Regexp supplied as an example in RFC 3986, and it works great.
|
108
94
|
uri_regex =
|
109
|
-
/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))
|
95
|
+
/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/
|
110
96
|
scan = uri.scan(uri_regex)
|
111
97
|
fragments = scan[0]
|
112
|
-
return nil if fragments.nil?
|
113
98
|
scheme = fragments[1]
|
114
99
|
authority = fragments[3]
|
115
100
|
path = fragments[4]
|
116
101
|
query = fragments[6]
|
117
102
|
fragment = fragments[8]
|
118
|
-
userinfo = nil
|
119
103
|
user = nil
|
120
104
|
password = nil
|
121
105
|
host = nil
|
@@ -186,16 +170,14 @@ module Addressable
|
|
186
170
|
if parsed.scheme =~ /^[^\/?#\.]+\.[^\/?#]+$/
|
187
171
|
parsed = self.parse(hints[:scheme] + "://" + uri)
|
188
172
|
end
|
189
|
-
if parsed.
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
parsed.scheme = hints[:scheme]
|
198
|
-
end
|
173
|
+
if parsed.path.include?(".")
|
174
|
+
new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1]
|
175
|
+
if new_host
|
176
|
+
new_path = parsed.path.gsub(
|
177
|
+
Regexp.new("^" + Regexp.escape(new_host)), "")
|
178
|
+
parsed.host = new_host
|
179
|
+
parsed.path = new_path
|
180
|
+
parsed.scheme = hints[:scheme] unless parsed.scheme
|
199
181
|
end
|
200
182
|
end
|
201
183
|
return parsed
|
@@ -266,643 +248,6 @@ module Addressable
|
|
266
248
|
return uri
|
267
249
|
end
|
268
250
|
|
269
|
-
##
|
270
|
-
# Expands a URI template into a full URI.
|
271
|
-
#
|
272
|
-
# @param [String, #to_str] pattern The URI template pattern.
|
273
|
-
# @param [Hash] mapping The mapping that corresponds to the pattern.
|
274
|
-
# @param [#validate, #transform] processor
|
275
|
-
# An optional processor object may be supplied. The object should
|
276
|
-
# respond to either the <tt>validate</tt> or <tt>transform</tt> messages
|
277
|
-
# or both. Both the <tt>validate</tt> and <tt>transform</tt> methods
|
278
|
-
# should take two parameters: <tt>name</tt> and <tt>value</tt>. The
|
279
|
-
# <tt>validate</tt> method should return <tt>true</tt> or
|
280
|
-
# <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
|
281
|
-
# <tt>false</tt> otherwise. An <tt>InvalidTemplateValueError</tt>
|
282
|
-
# exception will be raised if the value is invalid. The
|
283
|
-
# <tt>transform</tt> method should return the transformed variable
|
284
|
-
# value as a <tt>String</tt>. If a <tt>transform</tt> method is used,
|
285
|
-
# the value will not be percent encoded automatically. Unicode
|
286
|
-
# normalization will be performed both before and after sending the
|
287
|
-
# value to the transform method.
|
288
|
-
#
|
289
|
-
# @return [Addressable::URI] The expanded URI template.
|
290
|
-
#
|
291
|
-
# @example
|
292
|
-
# class ExampleProcessor
|
293
|
-
# def self.validate(name, value)
|
294
|
-
# return !!(value =~ /^[\w ]+$/) if name == "query"
|
295
|
-
# return true
|
296
|
-
# end
|
297
|
-
#
|
298
|
-
# def self.transform(name, value)
|
299
|
-
# return value.gsub(/ /, "+") if name == "query"
|
300
|
-
# return value
|
301
|
-
# end
|
302
|
-
# end
|
303
|
-
#
|
304
|
-
# Addressable::URI.expand_template(
|
305
|
-
# "http://example.com/search/{query}/",
|
306
|
-
# {"query" => "an example search query"},
|
307
|
-
# ExampleProcessor
|
308
|
-
# ).to_s
|
309
|
-
# #=> "http://example.com/search/an+example+search+query/"
|
310
|
-
#
|
311
|
-
# Addressable::URI.expand_template(
|
312
|
-
# "http://example.com/search/{-list|+|query}/",
|
313
|
-
# {"query" => "an example search query".split(" ")}
|
314
|
-
# ).to_s
|
315
|
-
# #=> "http://example.com/search/an+example+search+query/"
|
316
|
-
#
|
317
|
-
# Addressable::URI.expand_template(
|
318
|
-
# "http://example.com/search/{query}/",
|
319
|
-
# {"query" => "bogus!"},
|
320
|
-
# ExampleProcessor
|
321
|
-
# ).to_s
|
322
|
-
# #=> Addressable::URI::InvalidTemplateValueError
|
323
|
-
def self.expand_template(pattern, mapping, processor=nil)
|
324
|
-
|
325
|
-
# FIXME: MUST REFACTOR!!!
|
326
|
-
|
327
|
-
result = pattern.dup
|
328
|
-
|
329
|
-
reserved = Addressable::URI::CharacterClasses::RESERVED
|
330
|
-
unreserved = Addressable::URI::CharacterClasses::UNRESERVED
|
331
|
-
anything = reserved + unreserved
|
332
|
-
operator_expansion =
|
333
|
-
/\{-([a-zA-Z]+)\|([#{anything}]+)\|([#{anything}]+)\}/
|
334
|
-
variable_expansion = /\{([#{anything}]+?)(=([#{anything}]+))?\}/
|
335
|
-
|
336
|
-
transformed_mapping = mapping.inject({}) do |accu, pair|
|
337
|
-
name, value = pair
|
338
|
-
unless value.respond_to?(:to_ary) || value.respond_to?(:to_str)
|
339
|
-
raise TypeError,
|
340
|
-
"Can't convert #{value.class} into String or Array."
|
341
|
-
end
|
342
|
-
|
343
|
-
value =
|
344
|
-
value.respond_to?(:to_ary) ? value.to_ary : value.to_str
|
345
|
-
# Handle unicode normalization
|
346
|
-
if value.kind_of?(Array)
|
347
|
-
value.map! { |val| Addressable::IDNA.unicode_normalize_kc(val) }
|
348
|
-
else
|
349
|
-
value = Addressable::IDNA.unicode_normalize_kc(value)
|
350
|
-
end
|
351
|
-
|
352
|
-
if processor == nil || !processor.respond_to?(:transform)
|
353
|
-
# Handle percent escaping
|
354
|
-
if value.kind_of?(Array)
|
355
|
-
transformed_value = value.map do |val|
|
356
|
-
self.encode_component(
|
357
|
-
val, Addressable::URI::CharacterClasses::UNRESERVED)
|
358
|
-
end
|
359
|
-
else
|
360
|
-
transformed_value = self.encode_component(
|
361
|
-
value, Addressable::URI::CharacterClasses::UNRESERVED)
|
362
|
-
end
|
363
|
-
end
|
364
|
-
|
365
|
-
# Process, if we've got a processor
|
366
|
-
if processor != nil
|
367
|
-
if processor.respond_to?(:validate)
|
368
|
-
if !processor.validate(name, value)
|
369
|
-
display_value = value.kind_of?(Array) ? value.inspect : value
|
370
|
-
raise InvalidTemplateValueError,
|
371
|
-
"#{name}=#{display_value} is an invalid template value."
|
372
|
-
end
|
373
|
-
end
|
374
|
-
if processor.respond_to?(:transform)
|
375
|
-
transformed_value = processor.transform(name, value)
|
376
|
-
if transformed_value.kind_of?(Array)
|
377
|
-
transformed_value.map! do |val|
|
378
|
-
Addressable::IDNA.unicode_normalize_kc(val)
|
379
|
-
end
|
380
|
-
else
|
381
|
-
transformed_value =
|
382
|
-
Addressable::IDNA.unicode_normalize_kc(transformed_value)
|
383
|
-
end
|
384
|
-
end
|
385
|
-
end
|
386
|
-
|
387
|
-
accu[name] = transformed_value
|
388
|
-
accu
|
389
|
-
end
|
390
|
-
result.gsub!(
|
391
|
-
/#{operator_expansion}|#{variable_expansion}/
|
392
|
-
) do |capture|
|
393
|
-
if capture =~ operator_expansion
|
394
|
-
operator, argument, variables, default_mapping =
|
395
|
-
parse_template_expansion(capture, transformed_mapping)
|
396
|
-
expand_method = "expand_#{operator}_operator"
|
397
|
-
if ([expand_method, expand_method.to_sym] & private_methods).empty?
|
398
|
-
raise InvalidTemplateOperatorError,
|
399
|
-
"Invalid template operator: #{operator}"
|
400
|
-
else
|
401
|
-
send(expand_method.to_sym, argument, variables, default_mapping)
|
402
|
-
end
|
403
|
-
else
|
404
|
-
varname, _, vardefault = capture.scan(/^\{(.+?)(=(.*))?\}$/)[0]
|
405
|
-
transformed_mapping[varname] || vardefault
|
406
|
-
end
|
407
|
-
end
|
408
|
-
return Addressable::URI.parse(result)
|
409
|
-
end
|
410
|
-
|
411
|
-
##
|
412
|
-
# Expands a URI Template opt operator.
|
413
|
-
#
|
414
|
-
# @param [String] argument The argument to the operator.
|
415
|
-
# @param [Array] variables The variables the operator is working on.
|
416
|
-
# @param [Hash] mapping The mapping of variables to values.
|
417
|
-
#
|
418
|
-
# @return [String] The expanded result.
|
419
|
-
def self.expand_opt_operator(argument, variables, mapping)
|
420
|
-
if (variables.any? do |variable|
|
421
|
-
mapping[variable] != [] &&
|
422
|
-
mapping[variable]
|
423
|
-
end)
|
424
|
-
argument
|
425
|
-
else
|
426
|
-
""
|
427
|
-
end
|
428
|
-
end
|
429
|
-
class <<self; private :expand_opt_operator; end
|
430
|
-
|
431
|
-
##
|
432
|
-
# Expands a URI Template neg operator.
|
433
|
-
#
|
434
|
-
# @param [String] argument The argument to the operator.
|
435
|
-
# @param [Array] variables The variables the operator is working on.
|
436
|
-
# @param [Hash] mapping The mapping of variables to values.
|
437
|
-
#
|
438
|
-
# @return [String] The expanded result.
|
439
|
-
def self.expand_neg_operator(argument, variables, mapping)
|
440
|
-
if (variables.any? do |variable|
|
441
|
-
mapping[variable] != [] &&
|
442
|
-
mapping[variable]
|
443
|
-
end)
|
444
|
-
""
|
445
|
-
else
|
446
|
-
argument
|
447
|
-
end
|
448
|
-
end
|
449
|
-
class <<self; private :expand_neg_operator; end
|
450
|
-
|
451
|
-
##
|
452
|
-
# Expands a URI Template prefix operator.
|
453
|
-
#
|
454
|
-
# @param [String] argument The argument to the operator.
|
455
|
-
# @param [Array] variables The variables the operator is working on.
|
456
|
-
# @param [Hash] mapping The mapping of variables to values.
|
457
|
-
#
|
458
|
-
# @return [String] The expanded result.
|
459
|
-
def self.expand_prefix_operator(argument, variables, mapping)
|
460
|
-
if variables.size != 1
|
461
|
-
raise InvalidTemplateOperatorError,
|
462
|
-
"Template operator 'prefix' takes exactly one variable."
|
463
|
-
end
|
464
|
-
value = mapping[variables.first]
|
465
|
-
if value.kind_of?(Array)
|
466
|
-
(value.map { |list_value| argument + list_value }).join("")
|
467
|
-
else
|
468
|
-
argument + value.to_s
|
469
|
-
end
|
470
|
-
end
|
471
|
-
class <<self; private :expand_prefix_operator; end
|
472
|
-
|
473
|
-
##
|
474
|
-
# Expands a URI Template suffix operator.
|
475
|
-
#
|
476
|
-
# @param [String] argument The argument to the operator.
|
477
|
-
# @param [Array] variables The variables the operator is working on.
|
478
|
-
# @param [Hash] mapping The mapping of variables to values.
|
479
|
-
#
|
480
|
-
# @return [String] The expanded result.
|
481
|
-
def self.expand_suffix_operator(argument, variables, mapping)
|
482
|
-
if variables.size != 1
|
483
|
-
raise InvalidTemplateOperatorError,
|
484
|
-
"Template operator 'suffix' takes exactly one variable."
|
485
|
-
end
|
486
|
-
value = mapping[variables.first]
|
487
|
-
if value.kind_of?(Array)
|
488
|
-
(value.map { |list_value| list_value + argument }).join("")
|
489
|
-
else
|
490
|
-
value.to_s + argument
|
491
|
-
end
|
492
|
-
end
|
493
|
-
class <<self; private :expand_suffix_operator; end
|
494
|
-
|
495
|
-
##
|
496
|
-
# Expands a URI Template join operator.
|
497
|
-
#
|
498
|
-
# @param [String] argument The argument to the operator.
|
499
|
-
# @param [Array] variables The variables the operator is working on.
|
500
|
-
# @param [Hash] mapping The mapping of variables to values.
|
501
|
-
#
|
502
|
-
# @return [String] The expanded result.
|
503
|
-
def self.expand_join_operator(argument, variables, mapping)
|
504
|
-
variable_values = variables.inject([]) do |accu, variable|
|
505
|
-
if !mapping[variable].kind_of?(Array)
|
506
|
-
if mapping[variable]
|
507
|
-
accu << variable + "=" + (mapping[variable])
|
508
|
-
end
|
509
|
-
else
|
510
|
-
raise InvalidTemplateOperatorError,
|
511
|
-
"Template operator 'join' does not accept Array values."
|
512
|
-
end
|
513
|
-
accu
|
514
|
-
end
|
515
|
-
variable_values.join(argument)
|
516
|
-
end
|
517
|
-
class <<self; private :expand_join_operator; end
|
518
|
-
|
519
|
-
##
|
520
|
-
# Expands a URI Template list operator.
|
521
|
-
#
|
522
|
-
# @param [String] argument The argument to the operator.
|
523
|
-
# @param [Array] variables The variables the operator is working on.
|
524
|
-
# @param [Hash] mapping The mapping of variables to values.
|
525
|
-
#
|
526
|
-
# @return [String] The expanded result.
|
527
|
-
def self.expand_list_operator(argument, variables, mapping)
|
528
|
-
if variables.size != 1
|
529
|
-
raise InvalidTemplateOperatorError,
|
530
|
-
"Template operator 'list' takes exactly one variable."
|
531
|
-
end
|
532
|
-
mapping[variables.first].join(argument)
|
533
|
-
end
|
534
|
-
class <<self; private :expand_list_operator; end
|
535
|
-
|
536
|
-
##
|
537
|
-
# Parses a URI template expansion <tt>String</tt>.
|
538
|
-
#
|
539
|
-
# @param [String] expansion The operator <tt>String</tt>.
|
540
|
-
# @param [Hash] mapping The mapping to merge defaults into.
|
541
|
-
#
|
542
|
-
# @return [Array]
|
543
|
-
# A tuple of the operator, argument, variables, and mapping.
|
544
|
-
def self.parse_template_expansion(capture, mapping)
|
545
|
-
operator, argument, variables = capture[1...-1].split("|")
|
546
|
-
operator.gsub!(/^\-/, "")
|
547
|
-
variables = variables.split(",")
|
548
|
-
mapping = (variables.inject({}) do |accu, var|
|
549
|
-
varname, _, vardefault = var.scan(/^(.+?)(=(.*))?$/)[0]
|
550
|
-
accu[varname] = vardefault
|
551
|
-
accu
|
552
|
-
end).merge(mapping)
|
553
|
-
variables = variables.map { |var| var.gsub(/=.*$/, "") }
|
554
|
-
return operator, argument, variables, mapping
|
555
|
-
end
|
556
|
-
class <<self; private :parse_template_expansion; end
|
557
|
-
|
558
|
-
##
|
559
|
-
# Extracts a mapping from the URI using a URI Template pattern.
|
560
|
-
#
|
561
|
-
# @param [String] pattern
|
562
|
-
# A URI template pattern.
|
563
|
-
# @param [#restore, #match] processor
|
564
|
-
# A template processor object may optionally be supplied.
|
565
|
-
# The object should respond to either the <tt>restore</tt> or
|
566
|
-
# <tt>match</tt> messages or both. The <tt>restore</tt> method should
|
567
|
-
# take two parameters: [String] name and [String] value. The
|
568
|
-
# <tt>restore</tt> method should reverse any transformations that have
|
569
|
-
# been performed on the value to ensure a valid URI. The
|
570
|
-
# <tt>match</tt> method should take a single parameter: [String] name.
|
571
|
-
# The <tt>match</tt> method should return a <tt>String</tt> containing
|
572
|
-
# a regular expression capture group for matching on that particular
|
573
|
-
# variable. The default value is ".*?". The <tt>match</tt> method has
|
574
|
-
# no effect on multivariate operator expansions.
|
575
|
-
# @return [Hash, NilClass]
|
576
|
-
# The <tt>Hash</tt> mapping that was extracted from the URI, or
|
577
|
-
# <tt>nil</tt> if the URI didn't match the template.
|
578
|
-
#
|
579
|
-
# @example
|
580
|
-
# class ExampleProcessor
|
581
|
-
# def self.restore(name, value)
|
582
|
-
# return value.gsub(/\+/, " ") if name == "query"
|
583
|
-
# return value
|
584
|
-
# end
|
585
|
-
#
|
586
|
-
# def self.match(name)
|
587
|
-
# return ".*?" if name == "first"
|
588
|
-
# return ".*"
|
589
|
-
# end
|
590
|
-
# end
|
591
|
-
#
|
592
|
-
# uri = Addressable::URI.parse(
|
593
|
-
# "http://example.com/search/an+example+search+query/"
|
594
|
-
# )
|
595
|
-
# uri.extract_mapping(
|
596
|
-
# "http://example.com/search/{query}/",
|
597
|
-
# ExampleProcessor
|
598
|
-
# )
|
599
|
-
# #=> {"query" => "an example search query"}
|
600
|
-
#
|
601
|
-
# uri = Addressable::URI.parse("http://example.com/a/b/c/")
|
602
|
-
# uri.extract_mapping(
|
603
|
-
# "http://example.com/{first}/{second}/",
|
604
|
-
# ExampleProcessor
|
605
|
-
# )
|
606
|
-
# #=> {"first" => "a", "second" => "b/c"}
|
607
|
-
#
|
608
|
-
# uri = Addressable::URI.parse("http://example.com/a/b/c/")
|
609
|
-
# uri.extract_mapping(
|
610
|
-
# "http://example.com/{first}/{-list|/|second}/"
|
611
|
-
# )
|
612
|
-
# #=> {"first" => "a", "second" => ["b", "c"]}
|
613
|
-
def extract_mapping(pattern, processor=nil)
|
614
|
-
reserved = Addressable::URI::CharacterClasses::RESERVED
|
615
|
-
unreserved = Addressable::URI::CharacterClasses::UNRESERVED
|
616
|
-
anything = reserved + unreserved
|
617
|
-
operator_expansion =
|
618
|
-
/\{-([a-zA-Z]+)\|([#{anything}]+)\|([#{anything}]+)\}/
|
619
|
-
variable_expansion = /\{([#{anything}]+?)(=([#{anything}]+))?\}/
|
620
|
-
|
621
|
-
# First, we need to process the pattern, and extract the values.
|
622
|
-
expansions, expansion_regexp =
|
623
|
-
parse_template_pattern(pattern, processor)
|
624
|
-
unparsed_values = self.to_s.scan(expansion_regexp).flatten
|
625
|
-
|
626
|
-
mapping = {}
|
627
|
-
|
628
|
-
if self.to_s == pattern
|
629
|
-
return mapping
|
630
|
-
elsif expansions.size > 0 && expansions.size == unparsed_values.size
|
631
|
-
expansions.each_with_index do |expansion, index|
|
632
|
-
unparsed_value = unparsed_values[index]
|
633
|
-
if expansion =~ operator_expansion
|
634
|
-
operator, argument, variables =
|
635
|
-
parse_template_expansion(expansion)
|
636
|
-
extract_method = "extract_#{operator}_operator"
|
637
|
-
if ([extract_method, extract_method.to_sym] &
|
638
|
-
private_methods).empty?
|
639
|
-
raise InvalidTemplateOperatorError,
|
640
|
-
"Invalid template operator: #{operator}"
|
641
|
-
else
|
642
|
-
begin
|
643
|
-
send(
|
644
|
-
extract_method.to_sym, unparsed_value, processor,
|
645
|
-
argument, variables, mapping
|
646
|
-
)
|
647
|
-
rescue TemplateOperatorAbortedError
|
648
|
-
return nil
|
649
|
-
end
|
650
|
-
end
|
651
|
-
else
|
652
|
-
name = expansion[variable_expansion, 1]
|
653
|
-
value = unparsed_value
|
654
|
-
if processor != nil && processor.respond_to?(:restore)
|
655
|
-
value = processor.restore(name, value)
|
656
|
-
end
|
657
|
-
mapping[name] = value
|
658
|
-
end
|
659
|
-
end
|
660
|
-
return mapping
|
661
|
-
else
|
662
|
-
return nil
|
663
|
-
end
|
664
|
-
end
|
665
|
-
|
666
|
-
##
|
667
|
-
# Generates the <tt>Regexp</tt> that parses a template pattern.
|
668
|
-
#
|
669
|
-
# @param [String] pattern The URI template pattern.
|
670
|
-
# @param [#match] processor The template processor to use.
|
671
|
-
#
|
672
|
-
# @return [Regexp]
|
673
|
-
# A regular expression which may be used to parse a template pattern.
|
674
|
-
def parse_template_pattern(pattern, processor)
|
675
|
-
reserved = Addressable::URI::CharacterClasses::RESERVED
|
676
|
-
unreserved = Addressable::URI::CharacterClasses::UNRESERVED
|
677
|
-
anything = reserved + unreserved
|
678
|
-
operator_expansion =
|
679
|
-
/\{-[a-zA-Z]+\|[#{anything}]+\|[#{anything}]+\}/
|
680
|
-
variable_expansion = /\{([#{anything}]+?)(=([#{anything}]+))?\}/
|
681
|
-
|
682
|
-
# Escape the pattern. The two gsubs restore the escaped curly braces
|
683
|
-
# back to their original form. Basically, escape everything that isn't
|
684
|
-
# within an expansion.
|
685
|
-
escaped_pattern = Regexp.escape(
|
686
|
-
pattern
|
687
|
-
).gsub(/\\\{(.*?)\\\}/) do |escaped|
|
688
|
-
escaped.gsub(/\\(.)/, "\\1")
|
689
|
-
end
|
690
|
-
|
691
|
-
expansions = []
|
692
|
-
|
693
|
-
# Create a regular expression that captures the values of the
|
694
|
-
# variables in the URI.
|
695
|
-
regexp_string = escaped_pattern.gsub(
|
696
|
-
/#{operator_expansion}|#{variable_expansion}/
|
697
|
-
) do |expansion|
|
698
|
-
expansions << expansion
|
699
|
-
if expansion =~ operator_expansion
|
700
|
-
capture_group = "(.*)"
|
701
|
-
if processor != nil && processor.respond_to?(:match)
|
702
|
-
# We can only lookup the match values for single variable
|
703
|
-
# operator expansions. Besides, ".*" is usually the only
|
704
|
-
# reasonable value for multivariate operators anyways.
|
705
|
-
operator, _, names, _ =
|
706
|
-
parse_template_expansion(expansion)
|
707
|
-
if ["prefix", "suffix", "list"].include?(operator)
|
708
|
-
capture_group = "(#{processor.match(names.first)})"
|
709
|
-
end
|
710
|
-
end
|
711
|
-
capture_group
|
712
|
-
else
|
713
|
-
capture_group = "(.*?)"
|
714
|
-
if processor != nil && processor.respond_to?(:match)
|
715
|
-
name = expansion[/\{([^\}=]+)(=[^\}]+)?\}/, 1]
|
716
|
-
capture_group = "(#{processor.match(name)})"
|
717
|
-
end
|
718
|
-
capture_group
|
719
|
-
end
|
720
|
-
end
|
721
|
-
|
722
|
-
# Ensure that the regular expression matches the whole URI.
|
723
|
-
regexp_string = "^#{regexp_string}$"
|
724
|
-
|
725
|
-
return expansions, Regexp.new(regexp_string)
|
726
|
-
end
|
727
|
-
private :parse_template_pattern
|
728
|
-
|
729
|
-
##
|
730
|
-
# Parses a URI template expansion <tt>String</tt>.
|
731
|
-
#
|
732
|
-
# @param [String] expansion The operator <tt>String</tt>.
|
733
|
-
#
|
734
|
-
# @return [Array]
|
735
|
-
# A tuple of the operator, argument, variables.
|
736
|
-
def parse_template_expansion(capture)
|
737
|
-
operator, argument, variables = capture[1...-1].split("|")
|
738
|
-
operator.gsub!(/^\-/, "")
|
739
|
-
variables = variables.split(",").map { |var| var.gsub(/=.*$/, "") }
|
740
|
-
return operator, argument, variables
|
741
|
-
end
|
742
|
-
private :parse_template_expansion
|
743
|
-
|
744
|
-
|
745
|
-
##
|
746
|
-
# Extracts a URI Template opt operator.
|
747
|
-
#
|
748
|
-
# @param [String] value The unparsed value to extract from.
|
749
|
-
# @param [#restore] processor The processor object.
|
750
|
-
# @param [String] argument The argument to the operator.
|
751
|
-
# @param [Array] variables The variables the operator is working on.
|
752
|
-
# @param [Hash] mapping The mapping of variables to values.
|
753
|
-
#
|
754
|
-
# @return [String] The extracted result.
|
755
|
-
def extract_opt_operator(
|
756
|
-
value, processor, argument, variables, mapping)
|
757
|
-
if value != "" && value != argument
|
758
|
-
raise TemplateOperatorAbortedError,
|
759
|
-
"Value for template operator 'neg' was unexpected."
|
760
|
-
end
|
761
|
-
end
|
762
|
-
private :extract_opt_operator
|
763
|
-
|
764
|
-
##
|
765
|
-
# Extracts a URI Template neg operator.
|
766
|
-
#
|
767
|
-
# @param [String] value The unparsed value to extract from.
|
768
|
-
# @param [#restore] processor The processor object.
|
769
|
-
# @param [String] argument The argument to the operator.
|
770
|
-
# @param [Array] variables The variables the operator is working on.
|
771
|
-
# @param [Hash] mapping The mapping of variables to values.
|
772
|
-
#
|
773
|
-
# @return [String] The extracted result.
|
774
|
-
def extract_neg_operator(
|
775
|
-
value, processor, argument, variables, mapping)
|
776
|
-
if value != "" && value != argument
|
777
|
-
raise TemplateOperatorAbortedError,
|
778
|
-
"Value for template operator 'neg' was unexpected."
|
779
|
-
end
|
780
|
-
end
|
781
|
-
private :extract_neg_operator
|
782
|
-
|
783
|
-
##
|
784
|
-
# Extracts a URI Template prefix operator.
|
785
|
-
#
|
786
|
-
# @param [String] value The unparsed value to extract from.
|
787
|
-
# @param [#restore] processor The processor object.
|
788
|
-
# @param [String] argument The argument to the operator.
|
789
|
-
# @param [Array] variables The variables the operator is working on.
|
790
|
-
# @param [Hash] mapping The mapping of variables to values.
|
791
|
-
#
|
792
|
-
# @return [String] The extracted result.
|
793
|
-
def extract_prefix_operator(
|
794
|
-
value, processor, argument, variables, mapping)
|
795
|
-
if variables.size != 1
|
796
|
-
raise InvalidTemplateOperatorError,
|
797
|
-
"Template operator 'suffix' takes exactly one variable."
|
798
|
-
end
|
799
|
-
if value[0...argument.size] != argument
|
800
|
-
raise TemplateOperatorAbortedError,
|
801
|
-
"Value for template operator 'prefix' missing expected prefix."
|
802
|
-
end
|
803
|
-
values = value.split(argument)
|
804
|
-
# Compensate for the crappy result from split.
|
805
|
-
if value[-argument.size..-1] == argument
|
806
|
-
values << ""
|
807
|
-
end
|
808
|
-
if values[0] == ""
|
809
|
-
values.shift
|
810
|
-
end
|
811
|
-
if processor && processor.respond_to?(:restore)
|
812
|
-
values.map! { |value| processor.restore(variables.first, value) }
|
813
|
-
end
|
814
|
-
mapping[variables.first] = values
|
815
|
-
end
|
816
|
-
private :extract_prefix_operator
|
817
|
-
|
818
|
-
##
|
819
|
-
# Extracts a URI Template suffix operator.
|
820
|
-
#
|
821
|
-
# @param [String] value The unparsed value to extract from.
|
822
|
-
# @param [#restore] processor The processor object.
|
823
|
-
# @param [String] argument The argument to the operator.
|
824
|
-
# @param [Array] variables The variables the operator is working on.
|
825
|
-
# @param [Hash] mapping The mapping of variables to values.
|
826
|
-
#
|
827
|
-
# @return [String] The extracted result.
|
828
|
-
def extract_suffix_operator(
|
829
|
-
value, processor, argument, variables, mapping)
|
830
|
-
if variables.size != 1
|
831
|
-
raise InvalidTemplateOperatorError,
|
832
|
-
"Template operator 'suffix' takes exactly one variable."
|
833
|
-
end
|
834
|
-
if value[-argument.size..-1] != argument
|
835
|
-
raise TemplateOperatorAbortedError,
|
836
|
-
"Value for template operator 'suffix' missing expected suffix."
|
837
|
-
end
|
838
|
-
values = value.split(argument)
|
839
|
-
# Compensate for the crappy result from split.
|
840
|
-
if value[-argument.size..-1] == argument
|
841
|
-
values << ""
|
842
|
-
end
|
843
|
-
if values[-1] == ""
|
844
|
-
values.pop
|
845
|
-
end
|
846
|
-
if processor && processor.respond_to?(:restore)
|
847
|
-
values.map! { |value| processor.restore(variables.first, value) }
|
848
|
-
end
|
849
|
-
mapping[variables.first] = values
|
850
|
-
end
|
851
|
-
private :extract_suffix_operator
|
852
|
-
|
853
|
-
##
|
854
|
-
# Extracts a URI Template join operator.
|
855
|
-
#
|
856
|
-
# @param [String] value The unparsed value to extract from.
|
857
|
-
# @param [#restore] processor The processor object.
|
858
|
-
# @param [String] argument The argument to the operator.
|
859
|
-
# @param [Array] variables The variables the operator is working on.
|
860
|
-
# @param [Hash] mapping The mapping of variables to values.
|
861
|
-
#
|
862
|
-
# @return [String] The extracted result.
|
863
|
-
def extract_join_operator(value, processor, argument, variables, mapping)
|
864
|
-
unparsed_values = value.split(argument)
|
865
|
-
parsed_variables = []
|
866
|
-
for unparsed_value in unparsed_values
|
867
|
-
name = unparsed_value[/^(.+?)=(.+)$/, 1]
|
868
|
-
parsed_variables << name
|
869
|
-
parsed_value = unparsed_value[/^(.+?)=(.+)$/, 2]
|
870
|
-
if processor && processor.respond_to?(:restore)
|
871
|
-
parsed_value = processor.restore(name, parsed_value)
|
872
|
-
end
|
873
|
-
mapping[name] = parsed_value
|
874
|
-
end
|
875
|
-
if (parsed_variables & variables) != parsed_variables
|
876
|
-
raise TemplateOperatorAbortedError,
|
877
|
-
"Template operator 'join' variable mismatch: " +
|
878
|
-
"#{parsed_variables.inspect}, #{variables.inspect}"
|
879
|
-
end
|
880
|
-
end
|
881
|
-
private :extract_join_operator
|
882
|
-
|
883
|
-
##
|
884
|
-
# Extracts a URI Template list operator.
|
885
|
-
#
|
886
|
-
# @param [String] value The unparsed value to extract from.
|
887
|
-
# @param [#restore] processor The processor object.
|
888
|
-
# @param [String] argument The argument to the operator.
|
889
|
-
# @param [Array] variables The variables the operator is working on.
|
890
|
-
# @param [Hash] mapping The mapping of variables to values.
|
891
|
-
#
|
892
|
-
# @return [String] The extracted result.
|
893
|
-
def extract_list_operator(value, processor, argument, variables, mapping)
|
894
|
-
if variables.size != 1
|
895
|
-
raise InvalidTemplateOperatorError,
|
896
|
-
"Template operator 'list' takes exactly one variable."
|
897
|
-
end
|
898
|
-
values = value.split(argument)
|
899
|
-
if processor && processor.respond_to?(:restore)
|
900
|
-
values.map! { |value| processor.restore(variables.first, value) }
|
901
|
-
end
|
902
|
-
mapping[variables.first] = values
|
903
|
-
end
|
904
|
-
private :extract_list_operator
|
905
|
-
|
906
251
|
##
|
907
252
|
# Joins several URIs together.
|
908
253
|
#
|
@@ -973,6 +318,12 @@ module Addressable
|
|
973
318
|
if character_class.kind_of?(String)
|
974
319
|
character_class = /[^#{character_class}]/
|
975
320
|
end
|
321
|
+
if component.respond_to?(:force_encoding)
|
322
|
+
# We can't perform regexps on invalid UTF sequences, but
|
323
|
+
# here we need to, so switch to ASCII.
|
324
|
+
component = component.dup
|
325
|
+
component.force_encoding(Encoding::ASCII_8BIT)
|
326
|
+
end
|
976
327
|
return component.gsub(character_class) do |sequence|
|
977
328
|
(sequence.unpack('C*').map { |c| "%" + ("%02x" % c).upcase }).join("")
|
978
329
|
end
|
@@ -1111,7 +462,13 @@ module Addressable
|
|
1111
462
|
}
|
1112
463
|
components.each do |key, value|
|
1113
464
|
if value != nil
|
1114
|
-
|
465
|
+
begin
|
466
|
+
components[key] =
|
467
|
+
Addressable::IDNA.unicode_normalize_kc(value.to_str)
|
468
|
+
rescue ArgumentError
|
469
|
+
# Likely a malformed UTF-8 character, skip unicode normalization
|
470
|
+
components[key] = value.to_str
|
471
|
+
end
|
1115
472
|
end
|
1116
473
|
end
|
1117
474
|
encoded_uri = Addressable::URI.new(
|
@@ -1137,67 +494,6 @@ module Addressable
|
|
1137
494
|
end
|
1138
495
|
end
|
1139
496
|
|
1140
|
-
##
|
1141
|
-
# Extracts uris from an arbitrary body of text.
|
1142
|
-
#
|
1143
|
-
# @param [String, #to_str] text
|
1144
|
-
# The body of text to extract URIs from.
|
1145
|
-
#
|
1146
|
-
# @option [String, Addressable::URI, #to_str] base
|
1147
|
-
# Causes any relative URIs to be resolved against the base URI.
|
1148
|
-
#
|
1149
|
-
# @option [TrueClass, FalseClass] parse
|
1150
|
-
# If parse is true, all extracted URIs will be parsed. If parse is
|
1151
|
-
# false, the return value with be an <tt>Array</tt> of <tt>Strings</aa>.
|
1152
|
-
# Defaults to false.
|
1153
|
-
#
|
1154
|
-
# @return [Array] The extracted URIs.
|
1155
|
-
def self.extract(text, options={})
|
1156
|
-
defaults = {:base => nil, :parse => false}
|
1157
|
-
options = defaults.merge(options)
|
1158
|
-
raise InvalidOptionError unless (options.keys - defaults.keys).empty?
|
1159
|
-
# This regular expression needs to be less forgiving or else it would
|
1160
|
-
# match virtually all text. Which isn't exactly what we're going for.
|
1161
|
-
extract_regex = /((([a-z\+]+):)[^ \n\<\>\"\\]+[\w\/])/
|
1162
|
-
extracted_uris =
|
1163
|
-
text.scan(extract_regex).collect { |match| match[0] }
|
1164
|
-
sgml_extract_regex = /<[^>]+href=\"([^\"]+?)\"[^>]*>/
|
1165
|
-
sgml_extracted_uris =
|
1166
|
-
text.scan(sgml_extract_regex).collect { |match| match[0] }
|
1167
|
-
extracted_uris.concat(sgml_extracted_uris - extracted_uris)
|
1168
|
-
textile_extract_regex = /\".+?\":([^ ]+\/[^ ]+)[ \,\.\;\:\?\!\<\>\"]/i
|
1169
|
-
textile_extracted_uris =
|
1170
|
-
text.scan(textile_extract_regex).collect { |match| match[0] }
|
1171
|
-
extracted_uris.concat(textile_extracted_uris - extracted_uris)
|
1172
|
-
parsed_uris = []
|
1173
|
-
base_uri = nil
|
1174
|
-
if options[:base] != nil
|
1175
|
-
base_uri = options[:base] if options[:base].kind_of?(self)
|
1176
|
-
base_uri = self.parse(options[:base].to_s) if base_uri == nil
|
1177
|
-
end
|
1178
|
-
for uri_string in extracted_uris
|
1179
|
-
begin
|
1180
|
-
if base_uri == nil
|
1181
|
-
parsed_uris << self.parse(uri_string)
|
1182
|
-
else
|
1183
|
-
parsed_uris << (base_uri + self.parse(uri_string))
|
1184
|
-
end
|
1185
|
-
rescue Exception
|
1186
|
-
nil
|
1187
|
-
end
|
1188
|
-
end
|
1189
|
-
parsed_uris = parsed_uris.select do |uri|
|
1190
|
-
(self.ip_based_schemes | [
|
1191
|
-
"file", "git", "svn", "mailto", "tel"
|
1192
|
-
]).include?(uri.normalized_scheme)
|
1193
|
-
end
|
1194
|
-
if options[:parse]
|
1195
|
-
return parsed_uris
|
1196
|
-
else
|
1197
|
-
return parsed_uris.collect { |uri| uri.to_s }
|
1198
|
-
end
|
1199
|
-
end
|
1200
|
-
|
1201
497
|
##
|
1202
498
|
# Creates a new uri object from component parts.
|
1203
499
|
#
|
@@ -1282,11 +578,18 @@ module Addressable
|
|
1282
578
|
#
|
1283
579
|
# @param [String, #to_str] new_scheme The new scheme component.
|
1284
580
|
def scheme=(new_scheme)
|
581
|
+
# Check for frozenness
|
582
|
+
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
583
|
+
|
1285
584
|
@scheme = new_scheme ? new_scheme.to_str : nil
|
1286
585
|
@scheme = nil if @scheme.to_s.strip == ""
|
1287
586
|
|
1288
587
|
# Reset dependant values
|
1289
588
|
@normalized_scheme = nil
|
589
|
+
@uri_string = nil
|
590
|
+
|
591
|
+
# Ensure we haven't created an invalid URI
|
592
|
+
validate()
|
1290
593
|
end
|
1291
594
|
|
1292
595
|
##
|
@@ -1325,9 +628,13 @@ module Addressable
|
|
1325
628
|
#
|
1326
629
|
# @param [String, #to_str] new_user The new user component.
|
1327
630
|
def user=(new_user)
|
631
|
+
# Check for frozenness
|
632
|
+
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
633
|
+
|
1328
634
|
@user = new_user ? new_user.to_str : nil
|
1329
635
|
|
1330
636
|
# You can't have a nil user with a non-nil password
|
637
|
+
@password ||= nil
|
1331
638
|
if @password != nil
|
1332
639
|
@user = "" if @user.nil?
|
1333
640
|
end
|
@@ -1337,6 +644,7 @@ module Addressable
|
|
1337
644
|
@normalized_userinfo = nil
|
1338
645
|
@authority = nil
|
1339
646
|
@normalized_user = nil
|
647
|
+
@uri_string = nil
|
1340
648
|
|
1341
649
|
# Ensure we haven't created an invalid URI
|
1342
650
|
validate()
|
@@ -1378,9 +686,14 @@ module Addressable
|
|
1378
686
|
#
|
1379
687
|
# @param [String, #to_str] new_password The new password component.
|
1380
688
|
def password=(new_password)
|
689
|
+
# Check for frozenness
|
690
|
+
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
691
|
+
|
1381
692
|
@password = new_password ? new_password.to_str : nil
|
1382
693
|
|
1383
694
|
# You can't have a nil user with a non-nil password
|
695
|
+
@password ||= nil
|
696
|
+
@user ||= nil
|
1384
697
|
if @password != nil
|
1385
698
|
@user = "" if @user.nil?
|
1386
699
|
end
|
@@ -1390,6 +703,7 @@ module Addressable
|
|
1390
703
|
@normalized_userinfo = nil
|
1391
704
|
@authority = nil
|
1392
705
|
@normalized_password = nil
|
706
|
+
@uri_string = nil
|
1393
707
|
|
1394
708
|
# Ensure we haven't created an invalid URI
|
1395
709
|
validate()
|
@@ -1437,6 +751,9 @@ module Addressable
|
|
1437
751
|
#
|
1438
752
|
# @param [String, #to_str] new_userinfo The new userinfo component.
|
1439
753
|
def userinfo=(new_userinfo)
|
754
|
+
# Check for frozenness
|
755
|
+
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
756
|
+
|
1440
757
|
new_user, new_password = if new_userinfo
|
1441
758
|
[
|
1442
759
|
new_userinfo.to_str.strip[/^(.*):/, 1],
|
@@ -1452,6 +769,7 @@ module Addressable
|
|
1452
769
|
|
1453
770
|
# Reset dependant values
|
1454
771
|
@authority = nil
|
772
|
+
@uri_string = nil
|
1455
773
|
|
1456
774
|
# Ensure we haven't created an invalid URI
|
1457
775
|
validate()
|
@@ -1495,11 +813,15 @@ module Addressable
|
|
1495
813
|
#
|
1496
814
|
# @param [String, #to_str] new_host The new host component.
|
1497
815
|
def host=(new_host)
|
816
|
+
# Check for frozenness
|
817
|
+
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
818
|
+
|
1498
819
|
@host = new_host ? new_host.to_str : nil
|
1499
820
|
|
1500
821
|
# Reset dependant values
|
1501
822
|
@authority = nil
|
1502
823
|
@normalized_host = nil
|
824
|
+
@uri_string = nil
|
1503
825
|
|
1504
826
|
# Ensure we haven't created an invalid URI
|
1505
827
|
validate()
|
@@ -1555,6 +877,9 @@ module Addressable
|
|
1555
877
|
#
|
1556
878
|
# @param [String, #to_str] new_authority The new authority component.
|
1557
879
|
def authority=(new_authority)
|
880
|
+
# Check for frozenness
|
881
|
+
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
882
|
+
|
1558
883
|
if new_authority
|
1559
884
|
new_authority = new_authority.to_str
|
1560
885
|
new_userinfo = new_authority[/^([^\[\]]*)@/, 1]
|
@@ -1578,6 +903,7 @@ module Addressable
|
|
1578
903
|
@inferred_port = nil
|
1579
904
|
@userinfo = nil
|
1580
905
|
@normalized_userinfo = nil
|
906
|
+
@uri_string = nil
|
1581
907
|
|
1582
908
|
# Ensure we haven't created an invalid URI
|
1583
909
|
validate()
|
@@ -1618,7 +944,7 @@ module Addressable
|
|
1618
944
|
#
|
1619
945
|
# @return [Integer] The port component.
|
1620
946
|
def port
|
1621
|
-
return @port
|
947
|
+
return @port ||= nil
|
1622
948
|
end
|
1623
949
|
|
1624
950
|
##
|
@@ -1640,6 +966,9 @@ module Addressable
|
|
1640
966
|
#
|
1641
967
|
# @param [String, Integer, #to_s] new_port The new port component.
|
1642
968
|
def port=(new_port)
|
969
|
+
# Check for frozenness
|
970
|
+
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
971
|
+
|
1643
972
|
if new_port != nil && new_port.respond_to?(:to_str)
|
1644
973
|
new_port = Addressable::URI.unencode_component(new_port.to_str)
|
1645
974
|
end
|
@@ -1655,6 +984,7 @@ module Addressable
|
|
1655
984
|
@authority = nil
|
1656
985
|
@inferred_port = nil
|
1657
986
|
@normalized_port = nil
|
987
|
+
@uri_string = nil
|
1658
988
|
|
1659
989
|
# Ensure we haven't created an invalid URI
|
1660
990
|
validate()
|
@@ -1694,11 +1024,19 @@ module Addressable
|
|
1694
1024
|
# @return [String] The path component, normalized.
|
1695
1025
|
def normalized_path
|
1696
1026
|
@normalized_path ||= (begin
|
1697
|
-
|
1698
|
-
Addressable::
|
1699
|
-
Addressable::
|
1700
|
-
|
1701
|
-
|
1027
|
+
begin
|
1028
|
+
result = Addressable::URI.encode_component(
|
1029
|
+
Addressable::IDNA.unicode_normalize_kc(
|
1030
|
+
Addressable::URI.unencode_component(self.path.strip)),
|
1031
|
+
Addressable::URI::CharacterClasses::PATH
|
1032
|
+
)
|
1033
|
+
rescue ArgumentError
|
1034
|
+
# Likely a malformed UTF-8 character, skip unicode normalization
|
1035
|
+
result = Addressable::URI.encode_component(
|
1036
|
+
Addressable::URI.unencode_component(self.path.strip),
|
1037
|
+
Addressable::URI::CharacterClasses::PATH
|
1038
|
+
)
|
1039
|
+
end
|
1702
1040
|
result = self.class.normalize_path(result)
|
1703
1041
|
if result == "" &&
|
1704
1042
|
["http", "https", "ftp", "tftp"].include?(self.normalized_scheme)
|
@@ -1713,6 +1051,9 @@ module Addressable
|
|
1713
1051
|
#
|
1714
1052
|
# @param [String, #to_str] new_path The new path component.
|
1715
1053
|
def path=(new_path)
|
1054
|
+
# Check for frozenness
|
1055
|
+
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
1056
|
+
|
1716
1057
|
@path = (new_path || "").to_str
|
1717
1058
|
if @path != "" && @path[0..0] != "/" && host != nil
|
1718
1059
|
@path = "/#{@path}"
|
@@ -1720,6 +1061,7 @@ module Addressable
|
|
1720
1061
|
|
1721
1062
|
# Reset dependant values
|
1722
1063
|
@normalized_path = nil
|
1064
|
+
@uri_string = nil
|
1723
1065
|
end
|
1724
1066
|
|
1725
1067
|
##
|
@@ -1772,10 +1114,14 @@ module Addressable
|
|
1772
1114
|
#
|
1773
1115
|
# @param [String, #to_str] new_query The new query component.
|
1774
1116
|
def query=(new_query)
|
1117
|
+
# Check for frozenness
|
1118
|
+
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
1119
|
+
|
1775
1120
|
@query = new_query ? new_query.to_str : nil
|
1776
1121
|
|
1777
1122
|
# Reset dependant values
|
1778
1123
|
@normalized_query = nil
|
1124
|
+
@uri_string = nil
|
1779
1125
|
end
|
1780
1126
|
|
1781
1127
|
##
|
@@ -1816,11 +1162,24 @@ module Addressable
|
|
1816
1162
|
raise ArgumentError,
|
1817
1163
|
"Invalid notation. Must be one of: [:flat, :dot, :subscript]."
|
1818
1164
|
end
|
1165
|
+
dehash = lambda do |hash|
|
1166
|
+
hash.each do |(key, value)|
|
1167
|
+
if value.kind_of?(Hash)
|
1168
|
+
hash[key] = dehash.call(value)
|
1169
|
+
end
|
1170
|
+
end
|
1171
|
+
if hash != {} && hash.keys.all? { |key| key =~ /^\d+$/ }
|
1172
|
+
hash.sort.inject([]) do |accu, (key, value)|
|
1173
|
+
accu << value; accu
|
1174
|
+
end
|
1175
|
+
else
|
1176
|
+
hash
|
1177
|
+
end
|
1178
|
+
end
|
1819
1179
|
return nil if self.query == nil
|
1820
|
-
return (self.query.split("&").map do |pair|
|
1180
|
+
return ((self.query.split("&").map do |pair|
|
1821
1181
|
pair.split("=")
|
1822
|
-
end).inject({}) do |accumulator,
|
1823
|
-
key, value = pair
|
1182
|
+
end).inject({}) do |accumulator, (key, value)|
|
1824
1183
|
value = true if value.nil?
|
1825
1184
|
key = self.class.unencode_component(key)
|
1826
1185
|
if value != true
|
@@ -1853,28 +1212,62 @@ module Addressable
|
|
1853
1212
|
end
|
1854
1213
|
end
|
1855
1214
|
accumulator
|
1215
|
+
end).inject({}) do |accumulator, (key, value)|
|
1216
|
+
accumulator[key] = value.kind_of?(Hash) ? dehash.call(value) : value
|
1217
|
+
accumulator
|
1856
1218
|
end
|
1857
1219
|
end
|
1858
1220
|
|
1859
1221
|
##
|
1860
1222
|
# Sets the query component for this URI from a Hash object.
|
1223
|
+
# This method produces a query string using the :subscript notation.
|
1861
1224
|
#
|
1862
1225
|
# @param [Hash, #to_hash] new_query_values The new query values.
|
1863
1226
|
def query_values=(new_query_values)
|
1864
|
-
|
1865
|
-
|
1866
|
-
|
1867
|
-
|
1868
|
-
|
1227
|
+
# Check for frozenness
|
1228
|
+
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
1229
|
+
if !new_query_values.respond_to?(:to_hash)
|
1230
|
+
raise TypeError, "Can't convert #{new_query_values.class} into Hash."
|
1231
|
+
end
|
1232
|
+
new_query_values = new_query_values.to_hash
|
1233
|
+
|
1234
|
+
# Algorithm shamelessly stolen from Julien Genestoux, slightly modified
|
1235
|
+
buffer = ""
|
1236
|
+
stack = []
|
1237
|
+
e = lambda do |component|
|
1238
|
+
component = component.to_s if component.kind_of?(Symbol)
|
1239
|
+
self.class.encode_component(component, CharacterClasses::UNRESERVED)
|
1240
|
+
end
|
1241
|
+
new_query_values.each do |key, value|
|
1242
|
+
if value.kind_of?(Hash)
|
1243
|
+
stack << [key, value]
|
1244
|
+
elsif value.kind_of?(Array)
|
1245
|
+
stack << [
|
1246
|
+
key,
|
1247
|
+
value.inject({}) { |accu, x| accu[accu.size.to_s] = x; accu }
|
1248
|
+
]
|
1249
|
+
elsif value == true
|
1250
|
+
buffer << "#{e.call(key)}&"
|
1869
1251
|
else
|
1870
|
-
|
1871
|
-
|
1872
|
-
|
1252
|
+
buffer << "#{e.call(key)}=#{e.call(value)}&"
|
1253
|
+
end
|
1254
|
+
end
|
1255
|
+
stack.each do |(parent, hash)|
|
1256
|
+
(hash.sort_by { |key| key.to_s }).each do |(key, value)|
|
1257
|
+
if value.kind_of?(Hash)
|
1258
|
+
stack << ["#{parent}[#{key}]", value]
|
1259
|
+
elsif value == true
|
1260
|
+
buffer << "#{parent}[#{e.call(key)}]&"
|
1261
|
+
else
|
1262
|
+
buffer << "#{parent}[#{e.call(key)}]=#{e.call(value)}&"
|
1263
|
+
end
|
1873
1264
|
end
|
1874
|
-
end
|
1265
|
+
end
|
1266
|
+
@query = buffer.chop
|
1875
1267
|
|
1876
1268
|
# Reset dependant values
|
1877
1269
|
@normalized_query = nil
|
1270
|
+
@uri_string = nil
|
1878
1271
|
end
|
1879
1272
|
|
1880
1273
|
##
|
@@ -1908,10 +1301,17 @@ module Addressable
|
|
1908
1301
|
#
|
1909
1302
|
# @param [String, #to_str] new_fragment The new fragment component.
|
1910
1303
|
def fragment=(new_fragment)
|
1304
|
+
# Check for frozenness
|
1305
|
+
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
1306
|
+
|
1911
1307
|
@fragment = new_fragment ? new_fragment.to_str : nil
|
1912
1308
|
|
1913
1309
|
# Reset dependant values
|
1914
1310
|
@normalized_fragment = nil
|
1311
|
+
@uri_string = nil
|
1312
|
+
|
1313
|
+
# Ensure we haven't created an invalid URI
|
1314
|
+
validate()
|
1915
1315
|
end
|
1916
1316
|
|
1917
1317
|
##
|
@@ -2259,8 +1659,7 @@ module Addressable
|
|
2259
1659
|
# @return [Addressable::URI] A URI suitable for display purposes.
|
2260
1660
|
def display_uri
|
2261
1661
|
display_uri = self.normalize
|
2262
|
-
display_uri.
|
2263
|
-
::Addressable::IDNA.to_unicode(display_uri.host))
|
1662
|
+
display_uri.host = ::Addressable::IDNA.to_unicode(display_uri.host)
|
2264
1663
|
return display_uri
|
2265
1664
|
end
|
2266
1665
|
|
@@ -2318,7 +1717,7 @@ module Addressable
|
|
2318
1717
|
#
|
2319
1718
|
# @return [Integer] A hash of the URI.
|
2320
1719
|
def hash
|
2321
|
-
return (self.
|
1720
|
+
return @hash ||= (self.to_s.hash * -1)
|
2322
1721
|
end
|
2323
1722
|
|
2324
1723
|
##
|
@@ -2339,6 +1738,28 @@ module Addressable
|
|
2339
1738
|
return duplicated_uri
|
2340
1739
|
end
|
2341
1740
|
|
1741
|
+
##
|
1742
|
+
# Freezes the URI object.
|
1743
|
+
#
|
1744
|
+
# @return [Addressable::URI] The frozen URI.
|
1745
|
+
def freeze
|
1746
|
+
# Unfortunately, because of the memoized implementation of many of the
|
1747
|
+
# URI methods, the default freeze method will cause unexpected errors.
|
1748
|
+
# As an alternative, we freeze the string representation of the URI
|
1749
|
+
# instead. This should generally produce the desired effect.
|
1750
|
+
self.to_s.freeze
|
1751
|
+
return self
|
1752
|
+
end
|
1753
|
+
|
1754
|
+
##
|
1755
|
+
# Determines if the URI is frozen.
|
1756
|
+
#
|
1757
|
+
# @return [TrueClass, FalseClass]
|
1758
|
+
# True if the URI is frozen, false otherwise.
|
1759
|
+
def frozen?
|
1760
|
+
self.to_s.frozen?
|
1761
|
+
end
|
1762
|
+
|
2342
1763
|
##
|
2343
1764
|
# Omits components from a URI.
|
2344
1765
|
#
|
@@ -2365,6 +1786,7 @@ module Addressable
|
|
2365
1786
|
components.each do |component|
|
2366
1787
|
duplicated_uri.send((component.to_s + "=").to_sym, nil)
|
2367
1788
|
end
|
1789
|
+
duplicated_uri.user = duplicated_uri.normalized_user
|
2368
1790
|
duplicated_uri.validation_deferred = false
|
2369
1791
|
duplicated_uri
|
2370
1792
|
end
|
@@ -2386,16 +1808,18 @@ module Addressable
|
|
2386
1808
|
#
|
2387
1809
|
# @return [String] The URI's <tt>String</tt> representation.
|
2388
1810
|
def to_s
|
2389
|
-
uri_string
|
2390
|
-
|
2391
|
-
|
2392
|
-
|
2393
|
-
|
2394
|
-
|
2395
|
-
|
2396
|
-
uri_string.force_encoding
|
2397
|
-
|
2398
|
-
|
1811
|
+
@uri_string ||= (begin
|
1812
|
+
uri_string = ""
|
1813
|
+
uri_string << "#{self.scheme}:" if self.scheme != nil
|
1814
|
+
uri_string << "//#{self.authority}" if self.authority != nil
|
1815
|
+
uri_string << self.path.to_s
|
1816
|
+
uri_string << "?#{self.query}" if self.query != nil
|
1817
|
+
uri_string << "##{self.fragment}" if self.fragment != nil
|
1818
|
+
if uri_string.respond_to?(:force_encoding)
|
1819
|
+
uri_string.force_encoding(Encoding::UTF_8)
|
1820
|
+
end
|
1821
|
+
uri_string
|
1822
|
+
end)
|
2399
1823
|
end
|
2400
1824
|
|
2401
1825
|
##
|
@@ -2434,7 +1858,7 @@ module Addressable
|
|
2434
1858
|
# <tt>true</tt> if validation has been deferred,
|
2435
1859
|
# <tt>false</tt> otherwise.
|
2436
1860
|
def validation_deferred
|
2437
|
-
|
1861
|
+
!!@validation_deferred
|
2438
1862
|
end
|
2439
1863
|
|
2440
1864
|
##
|
@@ -2444,6 +1868,9 @@ module Addressable
|
|
2444
1868
|
# <tt>true</tt> if validation will be deferred,
|
2445
1869
|
# <tt>false</tt> otherwise.
|
2446
1870
|
def validation_deferred=(new_validation_deferred)
|
1871
|
+
# Check for frozenness
|
1872
|
+
raise TypeError, "Can't modify frozen URI." if self.frozen?
|
1873
|
+
|
2447
1874
|
@validation_deferred = new_validation_deferred
|
2448
1875
|
validate unless @validation_deferred
|
2449
1876
|
end
|