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.
@@ -1,4 +1,4 @@
1
- # coding:utf-8
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.authority == nil
190
- if parsed.path =~ /^[^\/]+\./
191
- new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1]
192
- if new_host
193
- new_path = parsed.path.gsub(
194
- Regexp.new("^" + Regexp.escape(new_host)), "")
195
- parsed.host = new_host
196
- parsed.path = new_path
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
- components[key] = Addressable::IDNA.unicode_normalize_kc(value.to_s)
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
- result = Addressable::URI.encode_component(
1698
- Addressable::IDNA.unicode_normalize_kc(
1699
- Addressable::URI.unencode_component(self.path.strip)),
1700
- Addressable::URI::CharacterClasses::PATH
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, pair|
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
- @query = (new_query_values.to_hash.inject([]) do |accumulator, pair|
1865
- key, value = pair
1866
- key = self.class.encode_component(key, CharacterClasses::UNRESERVED)
1867
- if value == true
1868
- accumulator << "#{key}"
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
- value = self.class.encode_component(
1871
- value, CharacterClasses::UNRESERVED)
1872
- accumulator << "#{key}=#{value}"
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).join("&")
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.instance_variable_set("@host",
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.normalize.to_s.hash * -1)
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
- uri_string << "#{self.scheme}:" if self.scheme != nil
2391
- uri_string << "//#{self.authority}" if self.authority != nil
2392
- uri_string << self.path.to_s
2393
- uri_string << "?#{self.query}" if self.query != nil
2394
- uri_string << "##{self.fragment}" if self.fragment != nil
2395
- if uri_string.respond_to?(:force_encoding)
2396
- uri_string.force_encoding(Encoding::UTF_8)
2397
- end
2398
- return uri_string
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
- @validation_deferred ||= false
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