addressable 2.2.8 → 2.3.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of addressable might be problematic. Click here for more details.
- data/CHANGELOG.md +14 -0
- data/README.md +7 -3
- data/Rakefile +2 -2
- data/data/unicode.data +0 -0
- data/lib/addressable/idna/pure.rb +7 -4235
- data/lib/addressable/template.rb +334 -543
- data/lib/addressable/uri.rb +117 -160
- data/lib/addressable/version.rb +2 -2
- data/spec/addressable/idna_spec.rb +15 -0
- data/spec/addressable/template_spec.rb +920 -2063
- data/spec/addressable/uri_spec.rb +265 -129
- metadata +5 -27
- data/Gemfile.lock +0 -35
data/lib/addressable/template.rb
CHANGED
@@ -22,15 +22,57 @@ require "addressable/uri"
|
|
22
22
|
module Addressable
|
23
23
|
##
|
24
24
|
# This is an implementation of a URI template based on
|
25
|
-
#
|
25
|
+
# RFC 6570 (http://tools.ietf.org/html/rfc6570).
|
26
26
|
class Template
|
27
27
|
# Constants used throughout the template code.
|
28
28
|
anything =
|
29
29
|
Addressable::URI::CharacterClasses::RESERVED +
|
30
30
|
Addressable::URI::CharacterClasses::UNRESERVED
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
|
32
|
+
|
33
|
+
variable_char_class =
|
34
|
+
Addressable::URI::CharacterClasses::ALPHA +
|
35
|
+
Addressable::URI::CharacterClasses::DIGIT + '_'
|
36
|
+
|
37
|
+
var_char =
|
38
|
+
"(?:(?:[#{variable_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)"
|
39
|
+
RESERVED =
|
40
|
+
"(?:[#{anything}]|%[a-fA-F0-9][a-fA-F0-9])"
|
41
|
+
UNRESERVED =
|
42
|
+
"(?:[#{
|
43
|
+
Addressable::URI::CharacterClasses::UNRESERVED
|
44
|
+
}]|%[a-fA-F0-9][a-fA-F0-9])"
|
45
|
+
variable =
|
46
|
+
"(?:#{var_char}(?:\\.?#{var_char})*)"
|
47
|
+
varspec =
|
48
|
+
"(?:(#{variable})(\\*|:\\d+)?)"
|
49
|
+
VARNAME =
|
50
|
+
/^#{variable}$/
|
51
|
+
VARSPEC =
|
52
|
+
/^#{varspec}$/
|
53
|
+
VARIABLE_LIST =
|
54
|
+
/^#{varspec}(?:,#{varspec})*$/
|
55
|
+
operator =
|
56
|
+
"+#./;?&=,!@|"
|
57
|
+
EXPRESSION =
|
58
|
+
/\{([#{operator}])?(#{varspec}(?:,#{varspec})*)\}/
|
59
|
+
|
60
|
+
|
61
|
+
LEADERS = {
|
62
|
+
'?' => '?',
|
63
|
+
'/' => '/',
|
64
|
+
'#' => '#',
|
65
|
+
'.' => '.',
|
66
|
+
';' => ';',
|
67
|
+
'&' => '&'
|
68
|
+
}
|
69
|
+
JOINERS = {
|
70
|
+
'?' => '&',
|
71
|
+
'.' => '.',
|
72
|
+
';' => ';',
|
73
|
+
'&' => '&',
|
74
|
+
'/' => '/'
|
75
|
+
}
|
34
76
|
|
35
77
|
##
|
36
78
|
# Raised if an invalid template value is supplied.
|
@@ -148,7 +190,7 @@ module Addressable
|
|
148
190
|
#
|
149
191
|
# @param [#restore, #match] processor
|
150
192
|
# A template processor object may optionally be supplied.
|
151
|
-
#
|
193
|
+
#
|
152
194
|
# The object should respond to either the <tt>restore</tt> or
|
153
195
|
# <tt>match</tt> messages or both. The <tt>restore</tt> method should
|
154
196
|
# take two parameters: `[String] name` and `[String] value`.
|
@@ -210,7 +252,7 @@ module Addressable
|
|
210
252
|
#
|
211
253
|
# @param [#restore, #match] processor
|
212
254
|
# A template processor object may optionally be supplied.
|
213
|
-
#
|
255
|
+
#
|
214
256
|
# The object should respond to either the <tt>restore</tt> or
|
215
257
|
# <tt>match</tt> messages or both. The <tt>restore</tt> method should
|
216
258
|
# take two parameters: `[String] name` and `[String] value`.
|
@@ -253,7 +295,7 @@ module Addressable
|
|
253
295
|
#
|
254
296
|
# uri = Addressable::URI.parse("http://example.com/a/b/c/")
|
255
297
|
# match = Addressable::Template.new(
|
256
|
-
# "http://example.com/{first}/{second}/"
|
298
|
+
# "http://example.com/{first}/{+second}/"
|
257
299
|
# ).match(uri, ExampleProcessor)
|
258
300
|
# match.variables
|
259
301
|
# #=> ["first", "second"]
|
@@ -262,7 +304,7 @@ module Addressable
|
|
262
304
|
#
|
263
305
|
# uri = Addressable::URI.parse("http://example.com/a/b/c/")
|
264
306
|
# match = Addressable::Template.new(
|
265
|
-
# "http://example.com/{first}/
|
307
|
+
# "http://example.com/{first}{/second*}/"
|
266
308
|
# ).match(uri)
|
267
309
|
# match.variables
|
268
310
|
# #=> ["first", "second"]
|
@@ -279,38 +321,56 @@ module Addressable
|
|
279
321
|
|
280
322
|
if uri.to_str == pattern
|
281
323
|
return Addressable::Template::MatchData.new(uri, self, mapping)
|
282
|
-
elsif expansions.size > 0
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
324
|
+
elsif expansions.size > 0
|
325
|
+
index = 0
|
326
|
+
expansions.each do |expansion|
|
327
|
+
_, operator, varlist = *expansion.match(EXPRESSION)
|
328
|
+
varlist.split(',').each do |varspec|
|
329
|
+
_, name, modifier = *varspec.match(VARSPEC)
|
330
|
+
case operator
|
331
|
+
when nil, '+', '#', '/', '.'
|
332
|
+
unparsed_value = unparsed_values[index]
|
333
|
+
name = varspec[VARSPEC, 1]
|
334
|
+
value = unparsed_value
|
335
|
+
value = value.split(JOINERS[operator]) if value && modifier == '*'
|
336
|
+
when ';', '?', '&'
|
337
|
+
if modifier == '*'
|
338
|
+
value = unparsed_values[index].split(JOINERS[operator])
|
339
|
+
value = value.inject({}) do |acc, v|
|
340
|
+
key, val = v.split('=')
|
341
|
+
val = "" if val.nil?
|
342
|
+
acc[key] = val
|
343
|
+
acc
|
344
|
+
end
|
345
|
+
else
|
346
|
+
if (unparsed_values[index])
|
347
|
+
name, value = unparsed_values[index].split('=')
|
348
|
+
value = "" if value.nil?
|
349
|
+
end
|
301
350
|
end
|
302
351
|
end
|
303
|
-
else
|
304
|
-
name = expansion[VARIABLE_EXPANSION, 1]
|
305
|
-
value = unparsed_value
|
306
352
|
if processor != nil && processor.respond_to?(:restore)
|
307
353
|
value = processor.restore(name, value)
|
308
354
|
end
|
355
|
+
if processor == nil
|
356
|
+
if value.is_a?(Hash)
|
357
|
+
value = value.inject({}){|acc, (k, v)|
|
358
|
+
acc[Addressable::URI.unencode_component(k)] =
|
359
|
+
Addressable::URI.unencode_component(v)
|
360
|
+
acc
|
361
|
+
}
|
362
|
+
elsif value.is_a?(Array)
|
363
|
+
value = value.map{|v| Addressable::URI.unencode_component(v) }
|
364
|
+
else
|
365
|
+
value = Addressable::URI.unencode_component(value)
|
366
|
+
end
|
367
|
+
end
|
309
368
|
if mapping[name] == nil || mapping[name] == value
|
310
369
|
mapping[name] = value
|
311
370
|
else
|
312
371
|
return nil
|
313
372
|
end
|
373
|
+
index = index + 1
|
314
374
|
end
|
315
375
|
end
|
316
376
|
return Addressable::Template::MatchData.new(uri, self, mapping)
|
@@ -324,7 +384,7 @@ module Addressable
|
|
324
384
|
#
|
325
385
|
# @param [Hash] mapping The mapping that corresponds to the pattern.
|
326
386
|
# @param [#validate, #transform] processor
|
327
|
-
# An optional processor object may be supplied.
|
387
|
+
# An optional processor object may be supplied.
|
328
388
|
#
|
329
389
|
# The object should respond to either the <tt>validate</tt> or
|
330
390
|
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
|
@@ -347,50 +407,18 @@ module Addressable
|
|
347
407
|
# #=> "http://example.com/1/{two}/"
|
348
408
|
#
|
349
409
|
# Addressable::Template.new(
|
350
|
-
# "http://example.com/
|
351
|
-
# ).partial_expand(
|
352
|
-
# {"query" => "an example search query".split(" ")}
|
353
|
-
# ).pattern
|
354
|
-
# #=> "http://example.com/search/an+example+search+query/"
|
355
|
-
#
|
356
|
-
# Addressable::Template.new(
|
357
|
-
# "http://example.com/{-join|&|one,two}/"
|
410
|
+
# "http://example.com/{?one,two}/"
|
358
411
|
# ).partial_expand({"one" => "1"}).pattern
|
359
|
-
# #=> "http://example.com/?one=1{
|
412
|
+
# #=> "http://example.com/?one=1{&two}/"
|
360
413
|
#
|
361
414
|
# Addressable::Template.new(
|
362
|
-
# "http://example.com/{
|
415
|
+
# "http://example.com/{?one,two,three}/"
|
363
416
|
# ).partial_expand({"one" => "1", "three" => 3}).pattern
|
364
|
-
# #=> "http://example.com/?one=1{
|
417
|
+
# #=> "http://example.com/?one=1{&two}&three=3"
|
365
418
|
def partial_expand(mapping, processor=nil)
|
366
419
|
result = self.pattern.dup
|
367
|
-
|
368
|
-
|
369
|
-
/#{OPERATOR_EXPANSION}|#{VARIABLE_EXPANSION}/
|
370
|
-
) do |capture|
|
371
|
-
if capture =~ OPERATOR_EXPANSION
|
372
|
-
operator, argument, variables, default_mapping =
|
373
|
-
parse_template_expansion(capture, transformed_mapping)
|
374
|
-
expand_method = "expand_#{operator}_operator"
|
375
|
-
if ([expand_method, expand_method.to_sym] & private_methods).empty?
|
376
|
-
raise InvalidTemplateOperatorError,
|
377
|
-
"Invalid template operator: #{operator}"
|
378
|
-
else
|
379
|
-
send(
|
380
|
-
expand_method.to_sym, argument, variables,
|
381
|
-
default_mapping, true
|
382
|
-
)
|
383
|
-
end
|
384
|
-
else
|
385
|
-
varname, _, vardefault = capture.scan(/^\{(.+?)(=(.*))?\}$/)[0]
|
386
|
-
if transformed_mapping[varname]
|
387
|
-
transformed_mapping[varname]
|
388
|
-
elsif vardefault
|
389
|
-
"{#{varname}=#{vardefault}}"
|
390
|
-
else
|
391
|
-
"{#{varname}}"
|
392
|
-
end
|
393
|
-
end
|
420
|
+
result.gsub!( EXPRESSION ) do |capture|
|
421
|
+
transform_partial_capture(mapping, capture, processor)
|
394
422
|
end
|
395
423
|
return Addressable::Template.new(result)
|
396
424
|
end
|
@@ -438,11 +466,11 @@ module Addressable
|
|
438
466
|
# #=> "http://example.com/search/an+example+search+query/"
|
439
467
|
#
|
440
468
|
# Addressable::Template.new(
|
441
|
-
# "http://example.com/search/{
|
469
|
+
# "http://example.com/search/{query}/"
|
442
470
|
# ).expand(
|
443
|
-
# {"query" => "an example search query"
|
471
|
+
# {"query" => "an example search query"}
|
444
472
|
# ).to_str
|
445
|
-
# #=> "http://example.com/search/an
|
473
|
+
# #=> "http://example.com/search/an%20example%20search%20query/"
|
446
474
|
#
|
447
475
|
# Addressable::Template.new(
|
448
476
|
# "http://example.com/search/{query}/"
|
@@ -453,24 +481,9 @@ module Addressable
|
|
453
481
|
# #=> Addressable::Template::InvalidTemplateValueError
|
454
482
|
def expand(mapping, processor=nil)
|
455
483
|
result = self.pattern.dup
|
456
|
-
|
457
|
-
result.gsub!(
|
458
|
-
|
459
|
-
) do |capture|
|
460
|
-
if capture =~ OPERATOR_EXPANSION
|
461
|
-
operator, argument, variables, default_mapping =
|
462
|
-
parse_template_expansion(capture, transformed_mapping)
|
463
|
-
expand_method = "expand_#{operator}_operator"
|
464
|
-
if ([expand_method, expand_method.to_sym] & private_methods).empty?
|
465
|
-
raise InvalidTemplateOperatorError,
|
466
|
-
"Invalid template operator: #{operator}"
|
467
|
-
else
|
468
|
-
send(expand_method.to_sym, argument, variables, default_mapping)
|
469
|
-
end
|
470
|
-
else
|
471
|
-
varname, _, vardefault = capture.scan(/^\{(.+?)(=(.*))?\}$/)[0]
|
472
|
-
transformed_mapping[varname] || vardefault
|
473
|
-
end
|
484
|
+
mapping = normalize_keys(mapping)
|
485
|
+
result.gsub!( EXPRESSION ) do |capture|
|
486
|
+
transform_capture(mapping, capture, processor)
|
474
487
|
end
|
475
488
|
return Addressable::URI.parse(result)
|
476
489
|
end
|
@@ -499,27 +512,25 @@ module Addressable
|
|
499
512
|
|
500
513
|
private
|
501
514
|
def ordered_variable_defaults
|
502
|
-
@ordered_variable_defaults ||= (
|
515
|
+
@ordered_variable_defaults ||= (
|
503
516
|
expansions, expansion_regexp = parse_template_pattern(pattern)
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
_, _, variables, mapping = parse_template_expansion(expansion)
|
509
|
-
result.concat variables.map { |var| [var, mapping[var]] }
|
510
|
-
when VARIABLE_EXPANSION
|
511
|
-
result << [$1, $2]
|
517
|
+
expansions.map do |capture|
|
518
|
+
_, operator, varlist = *capture.match(EXPRESSION)
|
519
|
+
varlist.split(',').map do |varspec|
|
520
|
+
name = varspec[VARSPEC, 1]
|
512
521
|
end
|
513
|
-
|
514
|
-
|
515
|
-
end)
|
522
|
+
end.flatten
|
523
|
+
)
|
516
524
|
end
|
517
525
|
|
526
|
+
|
518
527
|
##
|
519
|
-
#
|
520
|
-
# template.
|
528
|
+
# Loops through each capture and expands any values available in mapping
|
521
529
|
#
|
522
|
-
# @param [Hash] mapping
|
530
|
+
# @param [Hash] mapping
|
531
|
+
# Set of keys to expand
|
532
|
+
# @param [String] capture
|
533
|
+
# The expression to expand
|
523
534
|
# @param [#validate, #transform] processor
|
524
535
|
# An optional processor object may be supplied.
|
525
536
|
#
|
@@ -535,279 +546,227 @@ module Addressable
|
|
535
546
|
# automatically. Unicode normalization will be performed both before and
|
536
547
|
# after sending the value to the transform method.
|
537
548
|
#
|
538
|
-
# @return [
|
539
|
-
def
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
if Symbol === name
|
550
|
-
name = name.to_s
|
551
|
-
elsif name.respond_to?(:to_str)
|
552
|
-
name = name.to_str
|
553
|
-
else
|
554
|
-
raise TypeError,
|
555
|
-
"Can't convert #{name.class} into String."
|
556
|
-
end
|
557
|
-
value = value.respond_to?(:to_ary) ? value.to_ary : value.to_str
|
558
|
-
|
559
|
-
# Handle unicode normalization
|
560
|
-
if value.kind_of?(Array)
|
561
|
-
value.map! { |val| Addressable::IDNA.unicode_normalize_kc(val) }
|
549
|
+
# @return [String] The expanded expression
|
550
|
+
def transform_partial_capture(mapping, capture, processor = nil)
|
551
|
+
_, operator, varlist = *capture.match(EXPRESSION)
|
552
|
+
is_first = true
|
553
|
+
varlist.split(',').inject('') do |acc, varspec|
|
554
|
+
_, name, modifier = *varspec.match(VARSPEC)
|
555
|
+
value = mapping[name]
|
556
|
+
if value
|
557
|
+
operator = '&' if !is_first && operator == '?'
|
558
|
+
acc << transform_capture(mapping, "{#{operator}#{varspec}}", processor)
|
562
559
|
else
|
563
|
-
|
560
|
+
operator = '&' if !is_first && operator == '?'
|
561
|
+
acc << "{#{operator}#{varspec}}"
|
564
562
|
end
|
565
|
-
|
566
|
-
|
567
|
-
# Handle percent escaping
|
568
|
-
if value.kind_of?(Array)
|
569
|
-
transformed_value = value.map do |val|
|
570
|
-
Addressable::URI.encode_component(
|
571
|
-
val, Addressable::URI::CharacterClasses::UNRESERVED)
|
572
|
-
end
|
573
|
-
else
|
574
|
-
transformed_value = Addressable::URI.encode_component(
|
575
|
-
value, Addressable::URI::CharacterClasses::UNRESERVED)
|
576
|
-
end
|
577
|
-
end
|
578
|
-
|
579
|
-
# Process, if we've got a processor
|
580
|
-
if processor != nil
|
581
|
-
if processor.respond_to?(:validate)
|
582
|
-
if !processor.validate(name, value)
|
583
|
-
display_value = value.kind_of?(Array) ? value.inspect : value
|
584
|
-
raise InvalidTemplateValueError,
|
585
|
-
"#{name}=#{display_value} is an invalid template value."
|
586
|
-
end
|
587
|
-
end
|
588
|
-
if processor.respond_to?(:transform)
|
589
|
-
transformed_value = processor.transform(name, value)
|
590
|
-
if transformed_value.kind_of?(Array)
|
591
|
-
transformed_value.map! do |val|
|
592
|
-
Addressable::IDNA.unicode_normalize_kc(val)
|
593
|
-
end
|
594
|
-
else
|
595
|
-
transformed_value =
|
596
|
-
Addressable::IDNA.unicode_normalize_kc(transformed_value)
|
597
|
-
end
|
598
|
-
end
|
599
|
-
end
|
600
|
-
|
601
|
-
accu[name] = transformed_value
|
602
|
-
accu
|
563
|
+
is_first = false
|
564
|
+
acc
|
603
565
|
end
|
604
566
|
end
|
605
567
|
|
606
568
|
##
|
607
|
-
#
|
608
|
-
#
|
609
|
-
# @param [String] argument The argument to the operator.
|
610
|
-
# @param [Array] variables The variables the operator is working on.
|
611
|
-
# @param [Hash] mapping The mapping of variables to values.
|
612
|
-
#
|
613
|
-
# @return [String] The expanded result.
|
614
|
-
def expand_opt_operator(argument, variables, mapping, partial=false)
|
615
|
-
variables_present = variables.any? do |variable|
|
616
|
-
mapping[variable] != [] &&
|
617
|
-
mapping[variable]
|
618
|
-
end
|
619
|
-
if partial && !variables_present
|
620
|
-
"{-opt|#{argument}|#{variables.join(",")}}"
|
621
|
-
elsif variables_present
|
622
|
-
argument
|
623
|
-
else
|
624
|
-
""
|
625
|
-
end
|
626
|
-
end
|
627
|
-
|
628
|
-
##
|
629
|
-
# Expands a URI Template neg operator.
|
630
|
-
#
|
631
|
-
# @param [String] argument The argument to the operator.
|
632
|
-
# @param [Array] variables The variables the operator is working on.
|
633
|
-
# @param [Hash] mapping The mapping of variables to values.
|
569
|
+
# Transforms a mapped value so that values can be substituted into the
|
570
|
+
# template.
|
634
571
|
#
|
635
|
-
# @
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
end
|
641
|
-
if partial && !variables_present
|
642
|
-
"{-neg|#{argument}|#{variables.join(",")}}"
|
643
|
-
elsif variables_present
|
644
|
-
""
|
645
|
-
else
|
646
|
-
argument
|
647
|
-
end
|
648
|
-
end
|
649
|
-
|
650
|
-
##
|
651
|
-
# Expands a URI Template prefix operator.
|
572
|
+
# @param [Hash] mapping The mapping to replace captures
|
573
|
+
# @param [String] capture
|
574
|
+
# The expression to replace
|
575
|
+
# @param [#validate, #transform] processor
|
576
|
+
# An optional processor object may be supplied.
|
652
577
|
#
|
653
|
-
#
|
654
|
-
#
|
655
|
-
#
|
578
|
+
# The object should respond to either the <tt>validate</tt> or
|
579
|
+
# <tt>transform</tt> messages or both. Both the <tt>validate</tt> and
|
580
|
+
# <tt>transform</tt> methods should take two parameters: <tt>name</tt> and
|
581
|
+
# <tt>value</tt>. The <tt>validate</tt> method should return <tt>true</tt>
|
582
|
+
# or <tt>false</tt>; <tt>true</tt> if the value of the variable is valid,
|
583
|
+
# <tt>false</tt> otherwise. An <tt>InvalidTemplateValueError</tt> exception
|
584
|
+
# will be raised if the value is invalid. The <tt>transform</tt> method
|
585
|
+
# should return the transformed variable value as a <tt>String</tt>. If a
|
586
|
+
# <tt>transform</tt> method is used, the value will not be percent encoded
|
587
|
+
# automatically. Unicode normalization will be performed both before and
|
588
|
+
# after sending the value to the transform method.
|
656
589
|
#
|
657
|
-
# @return [String] The expanded
|
658
|
-
def
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
(
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
end
|
590
|
+
# @return [String] The expanded expression
|
591
|
+
def transform_capture(mapping, capture, processor=nil)
|
592
|
+
_, operator, varlist = *capture.match(EXPRESSION)
|
593
|
+
return_value = varlist.split(',').inject([]) do |acc, varspec|
|
594
|
+
_, name, modifier = *varspec.match(VARSPEC)
|
595
|
+
value = mapping[name]
|
596
|
+
unless value == nil || value == {}
|
597
|
+
allow_reserved = %w(+ #).include?(operator)
|
598
|
+
value = value.to_s if Numeric === value || Symbol === value
|
599
|
+
length = modifier.gsub(':', '').to_i if modifier =~ /^:\d+/
|
600
|
+
|
601
|
+
unless (Hash === value) ||
|
602
|
+
value.respond_to?(:to_ary) || value.respond_to?(:to_str)
|
603
|
+
raise TypeError,
|
604
|
+
"Can't convert #{value.class} into String or Array."
|
605
|
+
end
|
674
606
|
|
675
|
-
|
676
|
-
# Expands a URI Template suffix operator.
|
677
|
-
#
|
678
|
-
# @param [String] argument The argument to the operator.
|
679
|
-
# @param [Array] variables The variables the operator is working on.
|
680
|
-
# @param [Hash] mapping The mapping of variables to values.
|
681
|
-
#
|
682
|
-
# @return [String] The expanded result.
|
683
|
-
def expand_suffix_operator(argument, variables, mapping, partial=false)
|
684
|
-
if variables.size != 1
|
685
|
-
raise InvalidTemplateOperatorError,
|
686
|
-
"Template operator 'suffix' takes exactly one variable."
|
687
|
-
end
|
688
|
-
value = mapping[variables.first]
|
689
|
-
if !partial || value
|
690
|
-
if value.kind_of?(Array)
|
691
|
-
(value.map { |list_value| list_value + argument }).join("")
|
692
|
-
elsif value
|
693
|
-
value.to_s + argument
|
694
|
-
end
|
695
|
-
else
|
696
|
-
"{-suffix|#{argument}|#{variables.first}}"
|
697
|
-
end
|
698
|
-
end
|
607
|
+
value = normalize_value(value)
|
699
608
|
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
def expand_join_operator(argument, variables, mapping, partial=false)
|
709
|
-
if !partial
|
710
|
-
variable_values = variables.inject([]) do |accu, variable|
|
711
|
-
if !mapping[variable].kind_of?(Array)
|
712
|
-
if mapping[variable]
|
713
|
-
accu << variable + "=" + (mapping[variable])
|
609
|
+
if processor == nil || !processor.respond_to?(:transform)
|
610
|
+
# Handle percent escaping
|
611
|
+
if allow_reserved
|
612
|
+
encode_map =
|
613
|
+
Addressable::URI::CharacterClasses::RESERVED +
|
614
|
+
Addressable::URI::CharacterClasses::UNRESERVED
|
615
|
+
else
|
616
|
+
encode_map = Addressable::URI::CharacterClasses::UNRESERVED
|
714
617
|
end
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
else
|
723
|
-
buffer = ""
|
724
|
-
state = :suffix
|
725
|
-
variables.each_with_index do |variable, index|
|
726
|
-
if !mapping[variable].kind_of?(Array)
|
727
|
-
if mapping[variable]
|
728
|
-
if buffer.empty? || buffer[-1..-1] == "}"
|
729
|
-
buffer << (variable + "=" + (mapping[variable]))
|
730
|
-
elsif state == :suffix
|
731
|
-
buffer << argument
|
732
|
-
buffer << (variable + "=" + (mapping[variable]))
|
733
|
-
else
|
734
|
-
buffer << (variable + "=" + (mapping[variable]))
|
618
|
+
if value.kind_of?(Array)
|
619
|
+
transformed_value = value.map do |val|
|
620
|
+
if length
|
621
|
+
Addressable::URI.encode_component(val[0...length], encode_map)
|
622
|
+
else
|
623
|
+
Addressable::URI.encode_component(val, encode_map)
|
624
|
+
end
|
735
625
|
end
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
626
|
+
unless modifier == "*"
|
627
|
+
transformed_value = transformed_value.join(',')
|
628
|
+
end
|
629
|
+
elsif value.kind_of?(Hash)
|
630
|
+
transformed_value = value.map do |key, val|
|
631
|
+
if modifier == "*"
|
632
|
+
"#{
|
633
|
+
Addressable::URI.encode_component( key, encode_map)
|
634
|
+
}=#{
|
635
|
+
Addressable::URI.encode_component( val, encode_map)
|
636
|
+
}"
|
637
|
+
else
|
638
|
+
"#{
|
639
|
+
Addressable::URI.encode_component( key, encode_map)
|
640
|
+
},#{
|
641
|
+
Addressable::URI.encode_component( val, encode_map)
|
642
|
+
}"
|
643
|
+
end
|
740
644
|
end
|
741
|
-
|
742
|
-
|
743
|
-
|
645
|
+
unless modifier == "*"
|
646
|
+
transformed_value = transformed_value.join(',')
|
647
|
+
end
|
648
|
+
else
|
649
|
+
if length
|
650
|
+
transformed_value = Addressable::URI.encode_component(
|
651
|
+
value[0...length], encode_map)
|
744
652
|
else
|
745
|
-
|
653
|
+
transformed_value = Addressable::URI.encode_component(
|
654
|
+
value, encode_map)
|
746
655
|
end
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
656
|
+
end
|
657
|
+
end
|
658
|
+
|
659
|
+
# Process, if we've got a processor
|
660
|
+
if processor != nil
|
661
|
+
if processor.respond_to?(:validate)
|
662
|
+
if !processor.validate(name, value)
|
663
|
+
display_value = value.kind_of?(Array) ? value.inspect : value
|
664
|
+
raise InvalidTemplateValueError,
|
665
|
+
"#{name}=#{display_value} is an invalid template value."
|
753
666
|
end
|
754
667
|
end
|
755
|
-
|
756
|
-
|
757
|
-
|
668
|
+
if processor.respond_to?(:transform)
|
669
|
+
transformed_value = processor.transform(name, value)
|
670
|
+
transformed_value = normalize_value(transformed_value)
|
671
|
+
end
|
758
672
|
end
|
673
|
+
acc << [name, transformed_value]
|
759
674
|
end
|
760
|
-
|
675
|
+
acc
|
676
|
+
end
|
677
|
+
return "" if return_value.empty?
|
678
|
+
join_values(operator, return_value)
|
679
|
+
end
|
680
|
+
|
681
|
+
##
|
682
|
+
# Takes a set of values, and joins them together based on the
|
683
|
+
# operator.
|
684
|
+
#
|
685
|
+
# @param [String, Nil] operator One of the operators from the set
|
686
|
+
# (?,&,+,#,;,/,.), or nil if there wasn't one.
|
687
|
+
# @param [Array] return_value
|
688
|
+
# The set of return values (as [variable_name, value] tuples) that will
|
689
|
+
# be joined together.
|
690
|
+
#
|
691
|
+
# @return [String] The transformed mapped value
|
692
|
+
def join_values(operator, return_value)
|
693
|
+
leader = LEADERS.fetch(operator, '')
|
694
|
+
joiner = JOINERS.fetch(operator, ',')
|
695
|
+
case operator
|
696
|
+
when '&', '?'
|
697
|
+
leader + return_value.map{|k,v|
|
698
|
+
if v.is_a?(Array) && v.first =~ /=/
|
699
|
+
v.join(joiner)
|
700
|
+
elsif v.is_a?(Array)
|
701
|
+
v.map{|v| "#{k}=#{v}"}.join(joiner)
|
702
|
+
else
|
703
|
+
"#{k}=#{v}"
|
704
|
+
end
|
705
|
+
}.join(joiner)
|
706
|
+
when ';'
|
707
|
+
return_value.map{|k,v|
|
708
|
+
if v.is_a?(Array) && v.first =~ /=/
|
709
|
+
';' + v.join(";")
|
710
|
+
elsif v.is_a?(Array)
|
711
|
+
';' + v.map{|v| "#{k}=#{v}"}.join(";")
|
712
|
+
else
|
713
|
+
v && v != '' ? ";#{k}=#{v}" : ";#{k}"
|
714
|
+
end
|
715
|
+
}.join
|
716
|
+
else
|
717
|
+
leader + return_value.map{|k,v| v}.join(joiner)
|
761
718
|
end
|
762
719
|
end
|
763
720
|
|
764
721
|
##
|
765
|
-
#
|
722
|
+
# Takes a set of values, and joins them together based on the
|
723
|
+
# operator.
|
766
724
|
#
|
767
|
-
# @param [String]
|
768
|
-
#
|
769
|
-
# @param [Hash] mapping The mapping of variables to values.
|
725
|
+
# @param [Hash, Array, String] value
|
726
|
+
# Normalizes keys and values with IDNA#unicode_normalize_kc
|
770
727
|
#
|
771
|
-
# @return [String] The
|
772
|
-
def
|
773
|
-
|
774
|
-
|
775
|
-
"Template operator 'list' takes exactly one variable."
|
728
|
+
# @return [Hash, Array, String] The normalized values
|
729
|
+
def normalize_value(value)
|
730
|
+
unless value.is_a?(Hash)
|
731
|
+
value = value.respond_to?(:to_ary) ? value.to_ary : value.to_str
|
776
732
|
end
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
733
|
+
|
734
|
+
# Handle unicode normalization
|
735
|
+
if value.kind_of?(Array)
|
736
|
+
value.map! { |val| Addressable::IDNA.unicode_normalize_kc(val) }
|
737
|
+
elsif value.kind_of?(Hash)
|
738
|
+
value = value.inject({}) { |acc, (k, v)|
|
739
|
+
acc[Addressable::IDNA.unicode_normalize_kc(k)] =
|
740
|
+
Addressable::IDNA.unicode_normalize_kc(v)
|
741
|
+
acc
|
742
|
+
}
|
787
743
|
else
|
788
|
-
|
744
|
+
value = Addressable::IDNA.unicode_normalize_kc(value)
|
789
745
|
end
|
746
|
+
value
|
790
747
|
end
|
791
748
|
|
792
749
|
##
|
793
|
-
#
|
750
|
+
# Generates a hash with string keys
|
794
751
|
#
|
795
|
-
# @param [
|
796
|
-
# @param [Hash] mapping An optional mapping to merge defaults into.
|
752
|
+
# @param [Hash] mapping A mapping hash to normalize
|
797
753
|
#
|
798
|
-
# @return [
|
799
|
-
# A
|
800
|
-
def
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
754
|
+
# @return [Hash]
|
755
|
+
# A hash with stringified keys
|
756
|
+
def normalize_keys(mapping)
|
757
|
+
return mapping.inject({}) do |accu, pair|
|
758
|
+
name, value = pair
|
759
|
+
if Symbol === name
|
760
|
+
name = name.to_s
|
761
|
+
elsif name.respond_to?(:to_str)
|
762
|
+
name = name.to_str
|
763
|
+
else
|
764
|
+
raise TypeError,
|
765
|
+
"Can't convert #{name.class} into String."
|
766
|
+
end
|
767
|
+
accu[name] = value
|
807
768
|
accu
|
808
|
-
end
|
809
|
-
variables = variables.map { |var| var.gsub(/=.*$/, "") }
|
810
|
-
return operator, argument, variables, mapping
|
769
|
+
end
|
811
770
|
end
|
812
771
|
|
813
772
|
##
|
@@ -832,216 +791,48 @@ module Addressable
|
|
832
791
|
|
833
792
|
# Create a regular expression that captures the values of the
|
834
793
|
# variables in the URI.
|
835
|
-
regexp_string = escaped_pattern.gsub(
|
836
|
-
|
837
|
-
) do |expansion|
|
794
|
+
regexp_string = escaped_pattern.gsub( EXPRESSION ) do |expansion|
|
795
|
+
|
838
796
|
expansions << expansion
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
797
|
+
_, operator, varlist = *expansion.match(EXPRESSION)
|
798
|
+
leader = Regexp.escape(LEADERS.fetch(operator, ''))
|
799
|
+
joiner = Regexp.escape(JOINERS.fetch(operator, ','))
|
800
|
+
leader + varlist.split(',').map do |varspec|
|
801
|
+
_, name, modifier = *varspec.match(VARSPEC)
|
843
802
|
if processor != nil && processor.respond_to?(:match)
|
844
|
-
#
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
803
|
+
"(#{ processor.match(name) })"
|
804
|
+
else
|
805
|
+
group = case operator
|
806
|
+
when '+'
|
807
|
+
"#{ RESERVED }*?"
|
808
|
+
when '#'
|
809
|
+
"#{ RESERVED }*?"
|
810
|
+
when '/'
|
811
|
+
"#{ UNRESERVED }*?"
|
812
|
+
when '.'
|
813
|
+
"#{ UNRESERVED.gsub('\.', '') }*?"
|
814
|
+
when ';'
|
815
|
+
"#{ UNRESERVED }*=?#{ UNRESERVED }*?"
|
816
|
+
when '?'
|
817
|
+
"#{ UNRESERVED }*=#{ UNRESERVED }*?"
|
818
|
+
when '&'
|
819
|
+
"#{ UNRESERVED }*=#{ UNRESERVED }*?"
|
820
|
+
else
|
821
|
+
"#{ UNRESERVED }*?"
|
822
|
+
end
|
823
|
+
if modifier == '*'
|
824
|
+
"(#{group}(?:#{joiner}?#{group})*)?"
|
825
|
+
else
|
826
|
+
"(#{group})?"
|
849
827
|
end
|
850
|
-
elsif operator == "prefix"
|
851
|
-
capture_group = "(#{Regexp.escape(argument)}.*?)"
|
852
|
-
elsif operator == "suffix"
|
853
|
-
capture_group = "(.*?#{Regexp.escape(argument)})"
|
854
|
-
end
|
855
|
-
capture_group
|
856
|
-
else
|
857
|
-
capture_group = "(.*?)"
|
858
|
-
if processor != nil && processor.respond_to?(:match)
|
859
|
-
name = expansion[/\{([^\}=]+)(=[^\}]+)?\}/, 1]
|
860
|
-
capture_group = "(#{processor.match(name)})"
|
861
828
|
end
|
862
|
-
|
863
|
-
end
|
829
|
+
end.join("#{joiner}?")
|
864
830
|
end
|
865
831
|
|
866
832
|
# Ensure that the regular expression matches the whole URI.
|
867
833
|
regexp_string = "^#{regexp_string}$"
|
868
|
-
|
869
834
|
return expansions, Regexp.new(regexp_string)
|
870
835
|
end
|
871
836
|
|
872
|
-
##
|
873
|
-
# Extracts a URI Template opt operator.
|
874
|
-
#
|
875
|
-
# @param [String] value The unparsed value to extract from.
|
876
|
-
# @param [#restore] processor The processor object.
|
877
|
-
# @param [String] argument The argument to the operator.
|
878
|
-
# @param [Array] variables The variables the operator is working on.
|
879
|
-
# @param [Hash] mapping The mapping of variables to values.
|
880
|
-
#
|
881
|
-
# @return [String] The extracted result.
|
882
|
-
def extract_opt_operator(
|
883
|
-
value, processor, argument, variables, mapping)
|
884
|
-
if value != "" && value != argument
|
885
|
-
raise TemplateOperatorAbortedError,
|
886
|
-
"Value for template operator 'opt' was unexpected."
|
887
|
-
end
|
888
|
-
end
|
889
|
-
|
890
|
-
##
|
891
|
-
# Extracts a URI Template neg operator.
|
892
|
-
#
|
893
|
-
# @param [String] value The unparsed value to extract from.
|
894
|
-
# @param [#restore] processor The processor object.
|
895
|
-
# @param [String] argument The argument to the operator.
|
896
|
-
# @param [Array] variables The variables the operator is working on.
|
897
|
-
# @param [Hash] mapping The mapping of variables to values.
|
898
|
-
#
|
899
|
-
# @return [String] The extracted result.
|
900
|
-
def extract_neg_operator(
|
901
|
-
value, processor, argument, variables, mapping)
|
902
|
-
if value != "" && value != argument
|
903
|
-
raise TemplateOperatorAbortedError,
|
904
|
-
"Value for template operator 'neg' was unexpected."
|
905
|
-
end
|
906
|
-
end
|
907
|
-
|
908
|
-
##
|
909
|
-
# Extracts a URI Template prefix operator.
|
910
|
-
#
|
911
|
-
# @param [String] value The unparsed value to extract from.
|
912
|
-
# @param [#restore] processor The processor object.
|
913
|
-
# @param [String] argument The argument to the operator.
|
914
|
-
# @param [Array] variables The variables the operator is working on.
|
915
|
-
# @param [Hash] mapping The mapping of variables to values.
|
916
|
-
#
|
917
|
-
# @return [String] The extracted result.
|
918
|
-
def extract_prefix_operator(
|
919
|
-
value, processor, argument, variables, mapping)
|
920
|
-
if variables.size != 1
|
921
|
-
raise InvalidTemplateOperatorError,
|
922
|
-
"Template operator 'prefix' takes exactly one variable."
|
923
|
-
end
|
924
|
-
if value[0...argument.size] != argument
|
925
|
-
raise TemplateOperatorAbortedError,
|
926
|
-
"Value for template operator 'prefix' missing expected prefix."
|
927
|
-
end
|
928
|
-
values = value.split(argument, -1)
|
929
|
-
values << "" if value[-argument.size..-1] == argument
|
930
|
-
values.shift if values[0] == ""
|
931
|
-
values.pop if values[-1] == ""
|
932
|
-
|
933
|
-
if processor && processor.respond_to?(:restore)
|
934
|
-
values.map! { |val| processor.restore(variables.first, val) }
|
935
|
-
end
|
936
|
-
values = values.first if values.size == 1
|
937
|
-
if mapping[variables.first] == nil || mapping[variables.first] == values
|
938
|
-
mapping[variables.first] = values
|
939
|
-
else
|
940
|
-
raise TemplateOperatorAbortedError,
|
941
|
-
"Value mismatch for repeated variable."
|
942
|
-
end
|
943
|
-
end
|
944
|
-
|
945
|
-
##
|
946
|
-
# Extracts a URI Template suffix operator.
|
947
|
-
#
|
948
|
-
# @param [String] value The unparsed value to extract from.
|
949
|
-
# @param [#restore] processor The processor object.
|
950
|
-
# @param [String] argument The argument to the operator.
|
951
|
-
# @param [Array] variables The variables the operator is working on.
|
952
|
-
# @param [Hash] mapping The mapping of variables to values.
|
953
|
-
#
|
954
|
-
# @return [String] The extracted result.
|
955
|
-
def extract_suffix_operator(
|
956
|
-
value, processor, argument, variables, mapping)
|
957
|
-
if variables.size != 1
|
958
|
-
raise InvalidTemplateOperatorError,
|
959
|
-
"Template operator 'suffix' takes exactly one variable."
|
960
|
-
end
|
961
|
-
if value[-argument.size..-1] != argument
|
962
|
-
raise TemplateOperatorAbortedError,
|
963
|
-
"Value for template operator 'suffix' missing expected suffix."
|
964
|
-
end
|
965
|
-
values = value.split(argument, -1)
|
966
|
-
values.pop if values[-1] == ""
|
967
|
-
if processor && processor.respond_to?(:restore)
|
968
|
-
values.map! { |val| processor.restore(variables.first, val) }
|
969
|
-
end
|
970
|
-
values = values.first if values.size == 1
|
971
|
-
if mapping[variables.first] == nil || mapping[variables.first] == values
|
972
|
-
mapping[variables.first] = values
|
973
|
-
else
|
974
|
-
raise TemplateOperatorAbortedError,
|
975
|
-
"Value mismatch for repeated variable."
|
976
|
-
end
|
977
|
-
end
|
978
|
-
|
979
|
-
##
|
980
|
-
# Extracts a URI Template join operator.
|
981
|
-
#
|
982
|
-
# @param [String] value The unparsed value to extract from.
|
983
|
-
# @param [#restore] processor The processor object.
|
984
|
-
# @param [String] argument The argument to the operator.
|
985
|
-
# @param [Array] variables The variables the operator is working on.
|
986
|
-
# @param [Hash] mapping The mapping of variables to values.
|
987
|
-
#
|
988
|
-
# @return [String] The extracted result.
|
989
|
-
def extract_join_operator(value, processor, argument, variables, mapping)
|
990
|
-
unparsed_values = value.split(argument)
|
991
|
-
parsed_variables = []
|
992
|
-
for unparsed_value in unparsed_values
|
993
|
-
name = unparsed_value[/^(.+?)=(.+)$/, 1]
|
994
|
-
parsed_variables << name
|
995
|
-
parsed_value = unparsed_value[/^(.+?)=(.+)$/, 2]
|
996
|
-
if processor && processor.respond_to?(:restore)
|
997
|
-
parsed_value = processor.restore(name, parsed_value)
|
998
|
-
end
|
999
|
-
if mapping[name] == nil || mapping[name] == parsed_value
|
1000
|
-
mapping[name] = parsed_value
|
1001
|
-
else
|
1002
|
-
raise TemplateOperatorAbortedError,
|
1003
|
-
"Value mismatch for repeated variable."
|
1004
|
-
end
|
1005
|
-
end
|
1006
|
-
for variable in variables
|
1007
|
-
if !parsed_variables.include?(variable) && mapping[variable] != nil
|
1008
|
-
raise TemplateOperatorAbortedError,
|
1009
|
-
"Value mismatch for repeated variable."
|
1010
|
-
end
|
1011
|
-
end
|
1012
|
-
if (parsed_variables & variables) != parsed_variables
|
1013
|
-
raise TemplateOperatorAbortedError,
|
1014
|
-
"Template operator 'join' variable mismatch: " +
|
1015
|
-
"#{parsed_variables.inspect}, #{variables.inspect}"
|
1016
|
-
end
|
1017
|
-
end
|
1018
|
-
|
1019
|
-
##
|
1020
|
-
# Extracts a URI Template list operator.
|
1021
|
-
#
|
1022
|
-
# @param [String] value The unparsed value to extract from.
|
1023
|
-
# @param [#restore] processor The processor object.
|
1024
|
-
# @param [String] argument The argument to the operator.
|
1025
|
-
# @param [Array] variables The variables the operator is working on.
|
1026
|
-
# @param [Hash] mapping The mapping of variables to values.
|
1027
|
-
#
|
1028
|
-
# @return [String] The extracted result.
|
1029
|
-
def extract_list_operator(value, processor, argument, variables, mapping)
|
1030
|
-
if variables.size != 1
|
1031
|
-
raise InvalidTemplateOperatorError,
|
1032
|
-
"Template operator 'list' takes exactly one variable."
|
1033
|
-
end
|
1034
|
-
values = value.split(argument, -1)
|
1035
|
-
values.pop if values[-1] == ""
|
1036
|
-
if processor && processor.respond_to?(:restore)
|
1037
|
-
values.map! { |val| processor.restore(variables.first, val) }
|
1038
|
-
end
|
1039
|
-
if mapping[variables.first] == nil || mapping[variables.first] == values
|
1040
|
-
mapping[variables.first] = values
|
1041
|
-
else
|
1042
|
-
raise TemplateOperatorAbortedError,
|
1043
|
-
"Value mismatch for repeated variable."
|
1044
|
-
end
|
1045
|
-
end
|
1046
837
|
end
|
1047
838
|
end
|