mack-data_mapper 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. data/lib/gems/addressable-2.0.0/lib/addressable/idna.rb +4867 -0
  2. data/lib/gems/addressable-2.0.0/lib/addressable/uri.rb +2469 -0
  3. data/lib/gems/addressable-2.0.0/lib/addressable/version.rb +35 -0
  4. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/adapters/data_objects_adapter.rb +85 -0
  5. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/aggregate_functions.rb +201 -0
  6. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/collection.rb +11 -0
  7. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/model.rb +11 -0
  8. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/repository.rb +7 -0
  9. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/support/symbol.rb +21 -0
  10. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates/version.rb +7 -0
  11. data/lib/gems/dm-aggregates-0.9.7/lib/dm-aggregates.rb +15 -0
  12. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters/abstract_adapter.rb +209 -0
  13. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters/data_objects_adapter.rb +709 -0
  14. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters/in_memory_adapter.rb +87 -0
  15. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters/mysql_adapter.rb +136 -0
  16. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters/postgres_adapter.rb +188 -0
  17. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
  18. data/lib/gems/dm-core-0.9.7/lib/dm-core/adapters.rb +22 -0
  19. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations/many_to_many.rb +147 -0
  20. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations/many_to_one.rb +107 -0
  21. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations/one_to_many.rb +318 -0
  22. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations/one_to_one.rb +61 -0
  23. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations/relationship.rb +223 -0
  24. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations/relationship_chain.rb +81 -0
  25. data/lib/gems/dm-core-0.9.7/lib/dm-core/associations.rb +200 -0
  26. data/lib/gems/dm-core-0.9.7/lib/dm-core/auto_migrations.rb +105 -0
  27. data/lib/gems/dm-core-0.9.7/lib/dm-core/collection.rb +642 -0
  28. data/lib/gems/dm-core-0.9.7/lib/dm-core/dependency_queue.rb +32 -0
  29. data/lib/gems/dm-core-0.9.7/lib/dm-core/hook.rb +11 -0
  30. data/lib/gems/dm-core-0.9.7/lib/dm-core/identity_map.rb +42 -0
  31. data/lib/gems/dm-core-0.9.7/lib/dm-core/is.rb +16 -0
  32. data/lib/gems/dm-core-0.9.7/lib/dm-core/logger.rb +232 -0
  33. data/lib/gems/dm-core-0.9.7/lib/dm-core/migrations/destructive_migrations.rb +17 -0
  34. data/lib/gems/dm-core-0.9.7/lib/dm-core/migrator.rb +29 -0
  35. data/lib/gems/dm-core-0.9.7/lib/dm-core/model.rb +488 -0
  36. data/lib/gems/dm-core-0.9.7/lib/dm-core/naming_conventions.rb +84 -0
  37. data/lib/gems/dm-core-0.9.7/lib/dm-core/property.rb +663 -0
  38. data/lib/gems/dm-core-0.9.7/lib/dm-core/property_set.rb +169 -0
  39. data/lib/gems/dm-core-0.9.7/lib/dm-core/query.rb +628 -0
  40. data/lib/gems/dm-core-0.9.7/lib/dm-core/repository.rb +159 -0
  41. data/lib/gems/dm-core-0.9.7/lib/dm-core/resource.rb +637 -0
  42. data/lib/gems/dm-core-0.9.7/lib/dm-core/scope.rb +58 -0
  43. data/lib/gems/dm-core-0.9.7/lib/dm-core/support/array.rb +13 -0
  44. data/lib/gems/dm-core-0.9.7/lib/dm-core/support/assertions.rb +8 -0
  45. data/lib/gems/dm-core-0.9.7/lib/dm-core/support/errors.rb +23 -0
  46. data/lib/gems/dm-core-0.9.7/lib/dm-core/support/kernel.rb +11 -0
  47. data/lib/gems/dm-core-0.9.7/lib/dm-core/support/symbol.rb +41 -0
  48. data/lib/gems/dm-core-0.9.7/lib/dm-core/support.rb +7 -0
  49. data/lib/gems/dm-core-0.9.7/lib/dm-core/transaction.rb +267 -0
  50. data/lib/gems/dm-core-0.9.7/lib/dm-core/type.rb +160 -0
  51. data/lib/gems/dm-core-0.9.7/lib/dm-core/type_map.rb +80 -0
  52. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/boolean.rb +7 -0
  53. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/discriminator.rb +34 -0
  54. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/object.rb +24 -0
  55. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/paranoid_boolean.rb +34 -0
  56. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/paranoid_datetime.rb +33 -0
  57. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/serial.rb +9 -0
  58. data/lib/gems/dm-core-0.9.7/lib/dm-core/types/text.rb +10 -0
  59. data/lib/gems/dm-core-0.9.7/lib/dm-core/types.rb +19 -0
  60. data/lib/gems/dm-core-0.9.7/lib/dm-core/version.rb +3 -0
  61. data/lib/gems/dm-core-0.9.7/lib/dm-core.rb +217 -0
  62. data/lib/gems/dm-core-0.9.7/script/all +5 -0
  63. data/lib/gems/dm-core-0.9.7/script/performance.rb +284 -0
  64. data/lib/gems/dm-core-0.9.7/script/profile.rb +87 -0
  65. data/lib/gems/dm-migrations-0.9.7/lib/dm-migrations/version.rb +5 -0
  66. data/lib/gems/dm-migrations-0.9.7/lib/dm-migrations.rb +1 -0
  67. data/lib/gems/dm-migrations-0.9.7/lib/migration.rb +215 -0
  68. data/lib/gems/dm-migrations-0.9.7/lib/migration_runner.rb +88 -0
  69. data/lib/gems/dm-migrations-0.9.7/lib/spec/example/migration_example_group.rb +73 -0
  70. data/lib/gems/dm-migrations-0.9.7/lib/spec/matchers/migration_matchers.rb +107 -0
  71. data/lib/gems/dm-migrations-0.9.7/lib/sql/column.rb +9 -0
  72. data/lib/gems/dm-migrations-0.9.7/lib/sql/mysql.rb +52 -0
  73. data/lib/gems/dm-migrations-0.9.7/lib/sql/postgresql.rb +78 -0
  74. data/lib/gems/dm-migrations-0.9.7/lib/sql/sqlite3.rb +43 -0
  75. data/lib/gems/dm-migrations-0.9.7/lib/sql/table.rb +19 -0
  76. data/lib/gems/dm-migrations-0.9.7/lib/sql/table_creator.rb +81 -0
  77. data/lib/gems/dm-migrations-0.9.7/lib/sql/table_modifier.rb +53 -0
  78. data/lib/gems/dm-migrations-0.9.7/lib/sql.rb +10 -0
  79. data/lib/gems/dm-observer-0.9.7/lib/dm-observer/version.rb +5 -0
  80. data/lib/gems/dm-observer-0.9.7/lib/dm-observer.rb +91 -0
  81. data/lib/gems/dm-serializer-0.9.7/lib/dm-serializer/version.rb +5 -0
  82. data/lib/gems/dm-serializer-0.9.7/lib/dm-serializer.rb +183 -0
  83. data/lib/gems/dm-timestamps-0.9.7/lib/dm-timestamps/version.rb +5 -0
  84. data/lib/gems/dm-timestamps-0.9.7/lib/dm-timestamps.rb +57 -0
  85. data/lib/gems/dm-types-0.9.7/lib/dm-types/bcrypt_hash.rb +31 -0
  86. data/lib/gems/dm-types-0.9.7/lib/dm-types/csv.rb +28 -0
  87. data/lib/gems/dm-types-0.9.7/lib/dm-types/enum.rb +70 -0
  88. data/lib/gems/dm-types-0.9.7/lib/dm-types/epoch_time.rb +27 -0
  89. data/lib/gems/dm-types-0.9.7/lib/dm-types/file_path.rb +27 -0
  90. data/lib/gems/dm-types-0.9.7/lib/dm-types/flag.rb +61 -0
  91. data/lib/gems/dm-types-0.9.7/lib/dm-types/ip_address.rb +30 -0
  92. data/lib/gems/dm-types-0.9.7/lib/dm-types/json.rb +40 -0
  93. data/lib/gems/dm-types-0.9.7/lib/dm-types/regexp.rb +20 -0
  94. data/lib/gems/dm-types-0.9.7/lib/dm-types/serial.rb +8 -0
  95. data/lib/gems/dm-types-0.9.7/lib/dm-types/slug.rb +37 -0
  96. data/lib/gems/dm-types-0.9.7/lib/dm-types/uri.rb +29 -0
  97. data/lib/gems/dm-types-0.9.7/lib/dm-types/uuid.rb +64 -0
  98. data/lib/gems/dm-types-0.9.7/lib/dm-types/version.rb +5 -0
  99. data/lib/gems/dm-types-0.9.7/lib/dm-types/yaml.rb +36 -0
  100. data/lib/gems/dm-types-0.9.7/lib/dm-types.rb +28 -0
  101. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/absent_field_validator.rb +60 -0
  102. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/acceptance_validator.rb +76 -0
  103. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/auto_validate.rb +153 -0
  104. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/block_validator.rb +60 -0
  105. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/confirmation_validator.rb +80 -0
  106. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/contextual_validators.rb +56 -0
  107. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/custom_validator.rb +72 -0
  108. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/format_validator.rb +97 -0
  109. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/formats/email.rb +40 -0
  110. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/formats/url.rb +20 -0
  111. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/generic_validator.rb +100 -0
  112. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/length_validator.rb +113 -0
  113. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/method_validator.rb +68 -0
  114. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/numeric_validator.rb +83 -0
  115. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/primitive_validator.rb +60 -0
  116. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/required_field_validator.rb +88 -0
  117. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/support/object.rb +5 -0
  118. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/uniqueness_validator.rb +64 -0
  119. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/validation_errors.rb +63 -0
  120. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/version.rb +5 -0
  121. data/lib/gems/dm-validations-0.9.7/lib/dm-validations/within_validator.rb +53 -0
  122. data/lib/gems/dm-validations-0.9.7/lib/dm-validations.rb +234 -0
  123. data/lib/gems/json_pure-1.1.3/GPL +340 -0
  124. data/lib/gems/json_pure-1.1.3/VERSION +1 -0
  125. data/lib/gems/json_pure-1.1.3/bin/edit_json.rb +10 -0
  126. data/lib/gems/json_pure-1.1.3/bin/prettify_json.rb +76 -0
  127. data/lib/gems/json_pure-1.1.3/lib/json/Array.xpm +21 -0
  128. data/lib/gems/json_pure-1.1.3/lib/json/FalseClass.xpm +21 -0
  129. data/lib/gems/json_pure-1.1.3/lib/json/Hash.xpm +21 -0
  130. data/lib/gems/json_pure-1.1.3/lib/json/Key.xpm +73 -0
  131. data/lib/gems/json_pure-1.1.3/lib/json/NilClass.xpm +21 -0
  132. data/lib/gems/json_pure-1.1.3/lib/json/Numeric.xpm +28 -0
  133. data/lib/gems/json_pure-1.1.3/lib/json/String.xpm +96 -0
  134. data/lib/gems/json_pure-1.1.3/lib/json/TrueClass.xpm +21 -0
  135. data/lib/gems/json_pure-1.1.3/lib/json/add/core.rb +135 -0
  136. data/lib/gems/json_pure-1.1.3/lib/json/add/rails.rb +58 -0
  137. data/lib/gems/json_pure-1.1.3/lib/json/common.rb +354 -0
  138. data/lib/gems/json_pure-1.1.3/lib/json/editor.rb +1362 -0
  139. data/lib/gems/json_pure-1.1.3/lib/json/ext.rb +13 -0
  140. data/lib/gems/json_pure-1.1.3/lib/json/json.xpm +1499 -0
  141. data/lib/gems/json_pure-1.1.3/lib/json/pure/generator.rb +394 -0
  142. data/lib/gems/json_pure-1.1.3/lib/json/pure/parser.rb +259 -0
  143. data/lib/gems/json_pure-1.1.3/lib/json/pure.rb +75 -0
  144. data/lib/gems/json_pure-1.1.3/lib/json/version.rb +9 -0
  145. data/lib/gems/json_pure-1.1.3/lib/json.rb +235 -0
  146. data/lib/gems/launchy-0.3.2/bin/launchy +12 -0
  147. data/lib/gems/launchy-0.3.2/lib/launchy/application.rb +163 -0
  148. data/lib/gems/launchy-0.3.2/lib/launchy/browser.rb +85 -0
  149. data/lib/gems/launchy-0.3.2/lib/launchy/command_line.rb +48 -0
  150. data/lib/gems/launchy-0.3.2/lib/launchy/gemspec.rb +53 -0
  151. data/lib/gems/launchy-0.3.2/lib/launchy/specification.rb +133 -0
  152. data/lib/gems/launchy-0.3.2/lib/launchy/version.rb +18 -0
  153. data/lib/gems/launchy-0.3.2/lib/launchy.rb +58 -0
  154. data/lib/gems/uuidtools-1.0.3/lib/uuidtools/version.rb +32 -0
  155. data/lib/gems/uuidtools-1.0.3/lib/uuidtools.rb +648 -0
  156. data/lib/gems.rb +13 -0
  157. data/lib/mack-data_mapper/migration_generator/migration_generator.rb +5 -0
  158. data/lib/mack-data_mapper/migration_generator/templates/db/migrations/%=@migration_name%.rb.template +1 -1
  159. data/lib/mack-data_mapper/model_generator/manifest.yml +3 -3
  160. data/lib/mack-data_mapper/model_generator/model_generator.rb +8 -1
  161. data/lib/mack-data_mapper/model_generator/templates/model.rb.template +1 -1
  162. data/lib/mack-data_mapper/model_generator/templates/rspec.rb.template +1 -1
  163. data/lib/mack-data_mapper/model_generator/templates/test_case.rb.template +1 -1
  164. data/lib/mack-data_mapper.rb +3 -2
  165. data/lib/mack-data_mapper_tasks.rb +7 -0
  166. metadata +235 -86
@@ -0,0 +1,2469 @@
1
+ # coding:utf-8
2
+ #--
3
+ # Addressable, Copyright (c) 2006-2008 Bob Aman
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ #++
24
+
25
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '/..')))
26
+ $:.uniq!
27
+
28
+ require "addressable/version"
29
+ require "addressable/idna"
30
+
31
+ module Addressable
32
+ ##
33
+ # This is an implementation of a URI parser based on
34
+ # <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>,
35
+ # <a href="http://www.ietf.org/rfc/rfc3987.txt">RFC 3987</a>.
36
+ class URI
37
+ ##
38
+ # Raised if something other than a uri is supplied.
39
+ class InvalidURIError < StandardError
40
+ end
41
+
42
+ ##
43
+ # Raised if an invalid method option is supplied.
44
+ class InvalidOptionError < StandardError
45
+ end
46
+
47
+ ##
48
+ # Raised if an invalid template value is supplied.
49
+ class InvalidTemplateValueError < StandardError
50
+ end
51
+
52
+ ##
53
+ # Raised if an invalid template operator is used in a pattern.
54
+ class InvalidTemplateOperatorError < StandardError
55
+ end
56
+
57
+ ##
58
+ # Raised if an invalid template operator is used in a pattern.
59
+ class TemplateOperatorAbortedError < StandardError
60
+ end
61
+
62
+ ##
63
+ # Container for the character classes specified in
64
+ # <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
65
+ module CharacterClasses
66
+ ALPHA = "a-zA-Z"
67
+ DIGIT = "0-9"
68
+ GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@"
69
+ SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\="
70
+ RESERVED = GEN_DELIMS + SUB_DELIMS
71
+ UNRESERVED = ALPHA + DIGIT + "\\-\\.\\_\\~"
72
+ PCHAR = UNRESERVED + SUB_DELIMS + "\\:\\@"
73
+ SCHEME = ALPHA + DIGIT + "\\-\\+\\."
74
+ AUTHORITY = PCHAR
75
+ PATH = PCHAR + "\\/"
76
+ QUERY = PCHAR + "\\/\\?"
77
+ FRAGMENT = PCHAR + "\\/\\?"
78
+ end
79
+
80
+ ##
81
+ # Returns a URI object based on the parsed string.
82
+ #
83
+ # @param [String, Addressable::URI, #to_str] uri
84
+ # The URI string to parse. No parsing is performed if the object is
85
+ # already an <tt>Addressable::URI</tt>.
86
+ #
87
+ # @return [Addressable::URI] The parsed URI.
88
+ def self.parse(uri)
89
+ # If we were given nil, return nil.
90
+ return nil unless uri
91
+ # If a URI object is passed, just return itself.
92
+ 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
+
99
+ # If a URI object of the Ruby standard library variety is passed,
100
+ # convert it to a string, then parse the string.
101
+ # We do the check this way because we don't want to accidentally
102
+ # cause a missing constant exception to be thrown.
103
+ if uri.class.name =~ /^URI\b/
104
+ uri = uri.to_s
105
+ end
106
+
107
+ # This Regexp supplied as an example in RFC 3986, and it works great.
108
+ uri_regex =
109
+ /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
110
+ scan = uri.scan(uri_regex)
111
+ fragments = scan[0]
112
+ return nil if fragments.nil?
113
+ scheme = fragments[1]
114
+ authority = fragments[3]
115
+ path = fragments[4]
116
+ query = fragments[6]
117
+ fragment = fragments[8]
118
+ userinfo = nil
119
+ user = nil
120
+ password = nil
121
+ host = nil
122
+ port = nil
123
+ if authority != nil
124
+ # The Regexp above doesn't split apart the authority.
125
+ userinfo = authority[/^([^\[\]]*)@/, 1]
126
+ if userinfo != nil
127
+ user = userinfo.strip[/^([^:]*):?/, 1]
128
+ password = userinfo.strip[/:(.*)$/, 1]
129
+ end
130
+ host = authority.gsub(/^([^\[\]]*)@/, "").gsub(/:([^:@\[\]]*?)$/, "")
131
+ port = authority[/:([^:@\[\]]*?)$/, 1]
132
+ end
133
+ if port == ""
134
+ port = nil
135
+ end
136
+
137
+ return Addressable::URI.new(
138
+ :scheme => scheme,
139
+ :user => user,
140
+ :password => password,
141
+ :host => host,
142
+ :port => port,
143
+ :path => path,
144
+ :query => query,
145
+ :fragment => fragment
146
+ )
147
+ end
148
+
149
+ ##
150
+ # Converts an input to a URI. The input does not have to be a valid
151
+ # URI — the method will use heuristics to guess what URI was intended.
152
+ # This is not standards-compliant, merely user-friendly.
153
+ #
154
+ # @param [String, Addressable::URI, #to_str] uri
155
+ # The URI string to parse. No parsing is performed if the object is
156
+ # already an <tt>Addressable::URI</tt>.
157
+ # @param [Hash] hints
158
+ # A <tt>Hash</tt> of hints to the heuristic parser. Defaults to
159
+ # <tt>{:scheme => "http"}</tt>.
160
+ #
161
+ # @return [Addressable::URI] The parsed URI.
162
+ def self.heuristic_parse(uri, hints={})
163
+ # If we were given nil, return nil.
164
+ return nil unless uri
165
+ # If a URI object is passed, just return itself.
166
+ return uri if uri.kind_of?(self)
167
+ if !uri.respond_to?(:to_str)
168
+ raise TypeError, "Can't convert #{uri.class} into String."
169
+ end
170
+ # Otherwise, convert to a String
171
+ uri = uri.to_str.dup
172
+ hints = {
173
+ :scheme => "http"
174
+ }.merge(hints)
175
+ case uri
176
+ when /^http:\/+/
177
+ uri.gsub!(/^http:\/+/, "http://")
178
+ when /^feed:\/+http:\/+/
179
+ uri.gsub!(/^feed:\/+http:\/+/, "feed:http://")
180
+ when /^feed:\/+/
181
+ uri.gsub!(/^feed:\/+/, "feed://")
182
+ when /^file:\/+/
183
+ uri.gsub!(/^file:\/+/, "file:///")
184
+ end
185
+ parsed = self.parse(uri)
186
+ if parsed.scheme =~ /^[^\/?#\.]+\.[^\/?#]+$/
187
+ parsed = self.parse(hints[:scheme] + "://" + uri)
188
+ 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
199
+ end
200
+ end
201
+ return parsed
202
+ end
203
+
204
+ ##
205
+ # Converts a path to a file scheme URI. If the path supplied is
206
+ # relative, it will be returned as a relative URI. If the path supplied
207
+ # is actually a non-file URI, it will parse the URI as if it had been
208
+ # parsed with <tt>Addressable::URI.parse</tt>. Handles all of the
209
+ # various Microsoft-specific formats for specifying paths.
210
+ #
211
+ # @param [String, Addressable::URI, #to_str] path
212
+ # Typically a <tt>String</tt> path to a file or directory, but
213
+ # will return a sensible return value if an absolute URI is supplied
214
+ # instead.
215
+ #
216
+ # @return [Addressable::URI]
217
+ # The parsed file scheme URI or the original URI if some other URI
218
+ # scheme was provided.
219
+ #
220
+ # @example
221
+ # base = Addressable::URI.convert_path("/absolute/path/")
222
+ # uri = Addressable::URI.convert_path("relative/path")
223
+ # (base + uri).to_s
224
+ # #=> "file:///absolute/path/relative/path"
225
+ #
226
+ # Addressable::URI.convert_path(
227
+ # "c:\\windows\\My Documents 100%20\\foo.txt"
228
+ # ).to_s
229
+ # #=> "file:///c:/windows/My%20Documents%20100%20/foo.txt"
230
+ #
231
+ # Addressable::URI.convert_path("http://example.com/").to_s
232
+ # #=> "http://example.com/"
233
+ def self.convert_path(path)
234
+ # If we were given nil, return nil.
235
+ return nil unless path
236
+ # If a URI object is passed, just return itself.
237
+ return path if path.kind_of?(self)
238
+ if !path.respond_to?(:to_str)
239
+ raise TypeError, "Can't convert #{path.class} into String."
240
+ end
241
+ # Otherwise, convert to a String
242
+ path = path.to_str.strip
243
+
244
+ path.gsub!(/^file:\/?\/?/, "") if path =~ /^file:\/?\/?/
245
+ path = "/" + path if path =~ /^([a-zA-Z])(\||:)/
246
+ uri = self.parse(path)
247
+
248
+ if uri.scheme == nil
249
+ # Adjust windows-style uris
250
+ uri.path.gsub!(/^\/?([a-zA-Z])\|(\\|\/)/, "/\\1:/")
251
+ uri.path.gsub!(/\\/, "/")
252
+ if File.exists?(uri.path) &&
253
+ File.stat(uri.path).directory?
254
+ uri.path.gsub!(/\/$/, "")
255
+ uri.path = uri.path + '/'
256
+ end
257
+
258
+ # If the path is absolute, set the scheme and host.
259
+ if uri.path =~ /^\//
260
+ uri.scheme = "file"
261
+ uri.host = ""
262
+ end
263
+ uri.normalize!
264
+ end
265
+
266
+ return uri
267
+ end
268
+
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>.
285
+ #
286
+ # @return [Addressable::URI] The expanded URI template.
287
+ #
288
+ # @example
289
+ # class ExampleProcessor
290
+ # def self.validate(name, value)
291
+ # return !!(value =~ /^[\w ]+$/) if name == "query"
292
+ # return true
293
+ # end
294
+ #
295
+ # def self.transform(name, value)
296
+ # return value.gsub(/ /, "+") if name == "query"
297
+ # return value
298
+ # end
299
+ # end
300
+ #
301
+ # Addressable::URI.expand_template(
302
+ # "http://example.com/search/{query}/",
303
+ # {"query" => "an example search query"},
304
+ # ExampleProcessor
305
+ # ).to_s
306
+ # #=> "http://example.com/search/an+example+search+query/"
307
+ #
308
+ # Addressable::URI.expand_template(
309
+ # "http://example.com/search/{-list|+|query}/",
310
+ # {"query" => "an example search query".split(" ")}
311
+ # ).to_s
312
+ # #=> "http://example.com/search/an+example+search+query/"
313
+ #
314
+ # Addressable::URI.expand_template(
315
+ # "http://example.com/search/{query}/",
316
+ # {"query" => "bogus!"},
317
+ # ExampleProcessor
318
+ # ).to_s
319
+ # #=> Addressable::URI::InvalidTemplateValueError
320
+ def self.expand_template(pattern, mapping, processor=nil)
321
+
322
+ # FIXME: MUST REFACTOR!!!
323
+
324
+ result = pattern.dup
325
+
326
+ reserved = Addressable::URI::CharacterClasses::RESERVED
327
+ unreserved = Addressable::URI::CharacterClasses::UNRESERVED
328
+ anything = reserved + unreserved
329
+ operator_expansion =
330
+ /\{-([a-zA-Z]+)\|([#{anything}]+)\|([#{anything}]+)\}/
331
+ variable_expansion = /\{([#{anything}]+?)(=([#{anything}]+))?\}/
332
+
333
+ transformed_mapping = mapping.inject({}) do |accu, pair|
334
+ name, value = pair
335
+ unless value.respond_to?(:to_ary) || value.respond_to?(:to_str)
336
+ raise TypeError,
337
+ "Can't convert #{value.class} into String or Array."
338
+ end
339
+ transformed_value =
340
+ value.respond_to?(:to_ary) ? value.to_ary : value.to_str
341
+
342
+ # Handle percent escaping, and unicode normalization
343
+ if transformed_value.kind_of?(Array)
344
+ transformed_value.map! do |value|
345
+ self.encode_component(
346
+ Addressable::IDNA.unicode_normalize_kc(value),
347
+ Addressable::URI::CharacterClasses::UNRESERVED
348
+ )
349
+ end
350
+ else
351
+ transformed_value = self.encode_component(
352
+ Addressable::IDNA.unicode_normalize_kc(transformed_value),
353
+ Addressable::URI::CharacterClasses::UNRESERVED
354
+ )
355
+ end
356
+
357
+ # Process, if we've got a processor
358
+ if processor != nil
359
+ if processor.respond_to?(:validate)
360
+ if !processor.validate(name, value)
361
+ display_value = value.kind_of?(Array) ? value.inspect : value
362
+ raise InvalidTemplateValueError,
363
+ "#{name}=#{display_value} is an invalid template value."
364
+ end
365
+ end
366
+ if processor.respond_to?(:transform)
367
+ transformed_value = processor.transform(name, value)
368
+ end
369
+ end
370
+
371
+ accu[name] = transformed_value
372
+ accu
373
+ end
374
+ result.gsub!(
375
+ /#{operator_expansion}|#{variable_expansion}/
376
+ ) do |capture|
377
+ if capture =~ operator_expansion
378
+ operator, argument, variables, default_mapping =
379
+ parse_template_expansion(capture, transformed_mapping)
380
+ expand_method = "expand_#{operator}_operator"
381
+ if ([expand_method, expand_method.to_sym] & private_methods).empty?
382
+ raise InvalidTemplateOperatorError,
383
+ "Invalid template operator: #{operator}"
384
+ else
385
+ send(expand_method.to_sym, argument, variables, default_mapping)
386
+ end
387
+ else
388
+ varname, _, vardefault = capture.scan(/^\{(.+?)(=(.*))?\}$/)[0]
389
+ transformed_mapping[varname] || vardefault
390
+ end
391
+ end
392
+ return Addressable::URI.parse(result)
393
+ end
394
+
395
+ ##
396
+ # Expands a URI Template opt operator.
397
+ #
398
+ # @param [String] argument The argument to the operator.
399
+ # @param [Array] variables The variables the operator is working on.
400
+ # @param [Hash] mapping The mapping of variables to values.
401
+ #
402
+ # @return [String] The expanded result.
403
+ def self.expand_opt_operator(argument, variables, mapping)
404
+ if (variables.any? do |variable|
405
+ mapping[variable] != [] &&
406
+ mapping[variable]
407
+ end)
408
+ argument
409
+ else
410
+ ""
411
+ end
412
+ end
413
+ class <<self; private :expand_opt_operator; end
414
+
415
+ ##
416
+ # Expands a URI Template neg operator.
417
+ #
418
+ # @param [String] argument The argument to the operator.
419
+ # @param [Array] variables The variables the operator is working on.
420
+ # @param [Hash] mapping The mapping of variables to values.
421
+ #
422
+ # @return [String] The expanded result.
423
+ def self.expand_neg_operator(argument, variables, mapping)
424
+ if (variables.any? do |variable|
425
+ mapping[variable] != [] &&
426
+ mapping[variable]
427
+ end)
428
+ ""
429
+ else
430
+ argument
431
+ end
432
+ end
433
+ class <<self; private :expand_neg_operator; end
434
+
435
+ ##
436
+ # Expands a URI Template prefix operator.
437
+ #
438
+ # @param [String] argument The argument to the operator.
439
+ # @param [Array] variables The variables the operator is working on.
440
+ # @param [Hash] mapping The mapping of variables to values.
441
+ #
442
+ # @return [String] The expanded result.
443
+ def self.expand_prefix_operator(argument, variables, mapping)
444
+ if variables.size != 1
445
+ raise InvalidTemplateOperatorError,
446
+ "Template operator 'prefix' takes exactly one variable."
447
+ end
448
+ value = mapping[variables.first]
449
+ if value.kind_of?(Array)
450
+ (value.map { |list_value| argument + list_value }).join("")
451
+ else
452
+ argument + value.to_s
453
+ end
454
+ end
455
+ class <<self; private :expand_prefix_operator; end
456
+
457
+ ##
458
+ # Expands a URI Template suffix operator.
459
+ #
460
+ # @param [String] argument The argument to the operator.
461
+ # @param [Array] variables The variables the operator is working on.
462
+ # @param [Hash] mapping The mapping of variables to values.
463
+ #
464
+ # @return [String] The expanded result.
465
+ def self.expand_suffix_operator(argument, variables, mapping)
466
+ if variables.size != 1
467
+ raise InvalidTemplateOperatorError,
468
+ "Template operator 'suffix' takes exactly one variable."
469
+ end
470
+ value = mapping[variables.first]
471
+ if value.kind_of?(Array)
472
+ (value.map { |list_value| list_value + argument }).join("")
473
+ else
474
+ value.to_s + argument
475
+ end
476
+ end
477
+ class <<self; private :expand_suffix_operator; end
478
+
479
+ ##
480
+ # Expands a URI Template join operator.
481
+ #
482
+ # @param [String] argument The argument to the operator.
483
+ # @param [Array] variables The variables the operator is working on.
484
+ # @param [Hash] mapping The mapping of variables to values.
485
+ #
486
+ # @return [String] The expanded result.
487
+ def self.expand_join_operator(argument, variables, mapping)
488
+ variable_values = variables.inject([]) do |accu, variable|
489
+ if !mapping[variable].kind_of?(Array)
490
+ if mapping[variable]
491
+ accu << variable + "=" + (mapping[variable])
492
+ end
493
+ else
494
+ raise InvalidTemplateOperatorError,
495
+ "Template operator 'join' does not accept Array values."
496
+ end
497
+ accu
498
+ end
499
+ variable_values.join(argument)
500
+ end
501
+ class <<self; private :expand_join_operator; end
502
+
503
+ ##
504
+ # Expands a URI Template list operator.
505
+ #
506
+ # @param [String] argument The argument to the operator.
507
+ # @param [Array] variables The variables the operator is working on.
508
+ # @param [Hash] mapping The mapping of variables to values.
509
+ #
510
+ # @return [String] The expanded result.
511
+ def self.expand_list_operator(argument, variables, mapping)
512
+ if variables.size != 1
513
+ raise InvalidTemplateOperatorError,
514
+ "Template operator 'list' takes exactly one variable."
515
+ end
516
+ mapping[variables.first].join(argument)
517
+ end
518
+ class <<self; private :expand_list_operator; end
519
+
520
+ ##
521
+ # Parses a URI template expansion <tt>String</tt>.
522
+ #
523
+ # @param [String] expansion The operator <tt>String</tt>.
524
+ # @param [Hash] mapping The mapping to merge defaults into.
525
+ #
526
+ # @return [Array]
527
+ # A tuple of the operator, argument, variables, and mapping.
528
+ def self.parse_template_expansion(capture, mapping)
529
+ operator, argument, variables = capture[1...-1].split("|")
530
+ operator.gsub!(/^\-/, "")
531
+ variables = variables.split(",")
532
+ mapping = (variables.inject({}) do |accu, var|
533
+ varname, _, vardefault = var.scan(/^(.+?)(=(.*))?$/)[0]
534
+ accu[varname] = vardefault
535
+ accu
536
+ end).merge(mapping)
537
+ variables = variables.map { |var| var.gsub(/=.*$/, "") }
538
+ return operator, argument, variables, mapping
539
+ end
540
+ class <<self; private :parse_template_expansion; end
541
+
542
+ ##
543
+ # Extracts a mapping from the URI using a URI Template pattern.
544
+ #
545
+ # @param [String] pattern
546
+ # A URI template pattern.
547
+ # @param [#restore, #match] processor
548
+ # A template processor object may optionally be supplied.
549
+ # The object should respond to either the <tt>restore</tt> or
550
+ # <tt>match</tt> messages or both. The <tt>restore</tt> method should
551
+ # take two parameters: [String] name and [String] value. The
552
+ # <tt>restore</tt> method should reverse any transformations that have
553
+ # been performed on the value to ensure a valid URI. The
554
+ # <tt>match</tt> method should take a single parameter: [String] name.
555
+ # The <tt>match</tt> method should return a <tt>String</tt> containing
556
+ # a regular expression capture group for matching on that particular
557
+ # variable. The default value is ".*?". The <tt>match</tt> method has
558
+ # no effect on multivariate operator expansions.
559
+ # @return [Hash, NilClass]
560
+ # The <tt>Hash</tt> mapping that was extracted from the URI, or
561
+ # <tt>nil</tt> if the URI didn't match the template.
562
+ #
563
+ # @example
564
+ # class ExampleProcessor
565
+ # def self.restore(name, value)
566
+ # return value.gsub(/\+/, " ") if name == "query"
567
+ # return value
568
+ # end
569
+ #
570
+ # def self.match(name)
571
+ # return ".*?" if name == "first"
572
+ # return ".*"
573
+ # end
574
+ # end
575
+ #
576
+ # uri = Addressable::URI.parse(
577
+ # "http://example.com/search/an+example+search+query/"
578
+ # )
579
+ # uri.extract_mapping(
580
+ # "http://example.com/search/{query}/",
581
+ # ExampleProcessor
582
+ # )
583
+ # #=> {"query" => "an example search query"}
584
+ #
585
+ # uri = Addressable::URI.parse("http://example.com/a/b/c/")
586
+ # uri.extract_mapping(
587
+ # "http://example.com/{first}/{second}/",
588
+ # ExampleProcessor
589
+ # )
590
+ # #=> {"first" => "a", "second" => "b/c"}
591
+ #
592
+ # uri = Addressable::URI.parse("http://example.com/a/b/c/")
593
+ # uri.extract_mapping(
594
+ # "http://example.com/{first}/{-list|/|second}/"
595
+ # )
596
+ # #=> {"first" => "a", "second" => ["b", "c"]}
597
+ def extract_mapping(pattern, processor=nil)
598
+ reserved = Addressable::URI::CharacterClasses::RESERVED
599
+ unreserved = Addressable::URI::CharacterClasses::UNRESERVED
600
+ anything = reserved + unreserved
601
+ operator_expansion =
602
+ /\{-([a-zA-Z]+)\|([#{anything}]+)\|([#{anything}]+)\}/
603
+ variable_expansion = /\{([#{anything}]+?)(=([#{anything}]+))?\}/
604
+
605
+ # First, we need to process the pattern, and extract the values.
606
+ expansions, expansion_regexp =
607
+ parse_template_pattern(pattern, processor)
608
+ unparsed_values = self.to_s.scan(expansion_regexp).flatten
609
+
610
+ mapping = {}
611
+
612
+ if self.to_s == pattern
613
+ return mapping
614
+ elsif expansions.size > 0 && expansions.size == unparsed_values.size
615
+ expansions.each_with_index do |expansion, index|
616
+ unparsed_value = unparsed_values[index]
617
+ if expansion =~ operator_expansion
618
+ operator, argument, variables =
619
+ parse_template_expansion(expansion)
620
+ extract_method = "extract_#{operator}_operator"
621
+ if ([extract_method, extract_method.to_sym] &
622
+ private_methods).empty?
623
+ raise InvalidTemplateOperatorError,
624
+ "Invalid template operator: #{operator}"
625
+ else
626
+ begin
627
+ send(
628
+ extract_method.to_sym, unparsed_value, processor,
629
+ argument, variables, mapping
630
+ )
631
+ rescue TemplateOperatorAbortedError
632
+ return nil
633
+ end
634
+ end
635
+ else
636
+ name = expansion[variable_expansion, 1]
637
+ value = unparsed_value
638
+ if processor != nil && processor.respond_to?(:restore)
639
+ value = processor.restore(name, value)
640
+ end
641
+ mapping[name] = value
642
+ end
643
+ end
644
+ return mapping
645
+ else
646
+ return nil
647
+ end
648
+ end
649
+
650
+ ##
651
+ # Generates the <tt>Regexp</tt> that parses a template pattern.
652
+ #
653
+ # @param [String] pattern The URI template pattern.
654
+ # @param [#match] processor The template processor to use.
655
+ #
656
+ # @return [Regexp]
657
+ # A regular expression which may be used to parse a template pattern.
658
+ def parse_template_pattern(pattern, processor)
659
+ reserved = Addressable::URI::CharacterClasses::RESERVED
660
+ unreserved = Addressable::URI::CharacterClasses::UNRESERVED
661
+ anything = reserved + unreserved
662
+ operator_expansion =
663
+ /\{-[a-zA-Z]+\|[#{anything}]+\|[#{anything}]+\}/
664
+ variable_expansion = /\{([#{anything}]+?)(=([#{anything}]+))?\}/
665
+
666
+ # Escape the pattern. The two gsubs restore the escaped curly braces
667
+ # back to their original form. Basically, escape everything that isn't
668
+ # within an expansion.
669
+ escaped_pattern = Regexp.escape(
670
+ pattern
671
+ ).gsub(/\\\{(.*?)\\\}/) do |escaped|
672
+ escaped.gsub(/\\(.)/, "\\1")
673
+ end
674
+
675
+ expansions = []
676
+
677
+ # Create a regular expression that captures the values of the
678
+ # variables in the URI.
679
+ regexp_string = escaped_pattern.gsub(
680
+ /#{operator_expansion}|#{variable_expansion}/
681
+ ) do |expansion|
682
+ expansions << expansion
683
+ if expansion =~ operator_expansion
684
+ capture_group = "(.*)"
685
+ if processor != nil && processor.respond_to?(:match)
686
+ # We can only lookup the match values for single variable
687
+ # operator expansions. Besides, ".*" is usually the only
688
+ # reasonable value for multivariate operators anyways.
689
+ operator, _, names, _ =
690
+ parse_template_expansion(expansion)
691
+ if ["prefix", "suffix", "list"].include?(operator)
692
+ capture_group = "(#{processor.match(names.first)})"
693
+ end
694
+ end
695
+ capture_group
696
+ else
697
+ capture_group = "(.*?)"
698
+ if processor != nil && processor.respond_to?(:match)
699
+ name = expansion[/\{([^\}=]+)(=[^\}]+)?\}/, 1]
700
+ capture_group = "(#{processor.match(name)})"
701
+ end
702
+ capture_group
703
+ end
704
+ end
705
+
706
+ # Ensure that the regular expression matches the whole URI.
707
+ regexp_string = "^#{regexp_string}$"
708
+
709
+ return expansions, Regexp.new(regexp_string)
710
+ end
711
+ private :parse_template_pattern
712
+
713
+ ##
714
+ # Parses a URI template expansion <tt>String</tt>.
715
+ #
716
+ # @param [String] expansion The operator <tt>String</tt>.
717
+ #
718
+ # @return [Array]
719
+ # A tuple of the operator, argument, variables.
720
+ def parse_template_expansion(capture)
721
+ operator, argument, variables = capture[1...-1].split("|")
722
+ operator.gsub!(/^\-/, "")
723
+ variables = variables.split(",").map { |var| var.gsub(/=.*$/, "") }
724
+ return operator, argument, variables
725
+ end
726
+ private :parse_template_expansion
727
+
728
+
729
+ ##
730
+ # Extracts a URI Template opt operator.
731
+ #
732
+ # @param [String] value The unparsed value to extract from.
733
+ # @param [#restore] processor The processor object.
734
+ # @param [String] argument The argument to the operator.
735
+ # @param [Array] variables The variables the operator is working on.
736
+ # @param [Hash] mapping The mapping of variables to values.
737
+ #
738
+ # @return [String] The extracted result.
739
+ def extract_opt_operator(
740
+ value, processor, argument, variables, mapping)
741
+ if value != "" && value != argument
742
+ raise TemplateOperatorAbortedError,
743
+ "Value for template operator 'neg' was unexpected."
744
+ end
745
+ end
746
+ private :extract_opt_operator
747
+
748
+ ##
749
+ # Extracts a URI Template neg operator.
750
+ #
751
+ # @param [String] value The unparsed value to extract from.
752
+ # @param [#restore] processor The processor object.
753
+ # @param [String] argument The argument to the operator.
754
+ # @param [Array] variables The variables the operator is working on.
755
+ # @param [Hash] mapping The mapping of variables to values.
756
+ #
757
+ # @return [String] The extracted result.
758
+ def extract_neg_operator(
759
+ value, processor, argument, variables, mapping)
760
+ if value != "" && value != argument
761
+ raise TemplateOperatorAbortedError,
762
+ "Value for template operator 'neg' was unexpected."
763
+ end
764
+ end
765
+ private :extract_neg_operator
766
+
767
+ ##
768
+ # Extracts a URI Template prefix operator.
769
+ #
770
+ # @param [String] value The unparsed value to extract from.
771
+ # @param [#restore] processor The processor object.
772
+ # @param [String] argument The argument to the operator.
773
+ # @param [Array] variables The variables the operator is working on.
774
+ # @param [Hash] mapping The mapping of variables to values.
775
+ #
776
+ # @return [String] The extracted result.
777
+ def extract_prefix_operator(
778
+ value, processor, argument, variables, mapping)
779
+ if variables.size != 1
780
+ raise InvalidTemplateOperatorError,
781
+ "Template operator 'suffix' takes exactly one variable."
782
+ end
783
+ if value[0...argument.size] != argument
784
+ raise TemplateOperatorAbortedError,
785
+ "Value for template operator 'prefix' missing expected prefix."
786
+ end
787
+ values = value.split(argument)
788
+ # Compensate for the crappy result from split.
789
+ if value[-argument.size..-1] == argument
790
+ values << ""
791
+ end
792
+ if values[0] == ""
793
+ values.shift
794
+ end
795
+ if processor && processor.respond_to?(:restore)
796
+ values.map! { |value| processor.restore(variables.first, value) }
797
+ end
798
+ mapping[variables.first] = values
799
+ end
800
+ private :extract_prefix_operator
801
+
802
+ ##
803
+ # Extracts a URI Template suffix operator.
804
+ #
805
+ # @param [String] value The unparsed value to extract from.
806
+ # @param [#restore] processor The processor object.
807
+ # @param [String] argument The argument to the operator.
808
+ # @param [Array] variables The variables the operator is working on.
809
+ # @param [Hash] mapping The mapping of variables to values.
810
+ #
811
+ # @return [String] The extracted result.
812
+ def extract_suffix_operator(
813
+ value, processor, argument, variables, mapping)
814
+ if variables.size != 1
815
+ raise InvalidTemplateOperatorError,
816
+ "Template operator 'suffix' takes exactly one variable."
817
+ end
818
+ if value[-argument.size..-1] != argument
819
+ raise TemplateOperatorAbortedError,
820
+ "Value for template operator 'suffix' missing expected suffix."
821
+ end
822
+ values = value.split(argument)
823
+ # Compensate for the crappy result from split.
824
+ if value[-argument.size..-1] == argument
825
+ values << ""
826
+ end
827
+ if values[-1] == ""
828
+ values.pop
829
+ end
830
+ if processor && processor.respond_to?(:restore)
831
+ values.map! { |value| processor.restore(variables.first, value) }
832
+ end
833
+ mapping[variables.first] = values
834
+ end
835
+ private :extract_suffix_operator
836
+
837
+ ##
838
+ # Extracts a URI Template join operator.
839
+ #
840
+ # @param [String] value The unparsed value to extract from.
841
+ # @param [#restore] processor The processor object.
842
+ # @param [String] argument The argument to the operator.
843
+ # @param [Array] variables The variables the operator is working on.
844
+ # @param [Hash] mapping The mapping of variables to values.
845
+ #
846
+ # @return [String] The extracted result.
847
+ def extract_join_operator(value, processor, argument, variables, mapping)
848
+ unparsed_values = value.split(argument)
849
+ parsed_variables = []
850
+ for unparsed_value in unparsed_values
851
+ name = unparsed_value[/^(.+?)=(.+)$/, 1]
852
+ parsed_variables << name
853
+ parsed_value = unparsed_value[/^(.+?)=(.+)$/, 2]
854
+ if processor && processor.respond_to?(:restore)
855
+ parsed_value = processor.restore(name, parsed_value)
856
+ end
857
+ mapping[name] = parsed_value
858
+ end
859
+ if (parsed_variables & variables) != parsed_variables
860
+ raise TemplateOperatorAbortedError,
861
+ "Template operator 'join' variable mismatch: " +
862
+ "#{parsed_variables.inspect}, #{variables.inspect}"
863
+ end
864
+ end
865
+ private :extract_join_operator
866
+
867
+ ##
868
+ # Extracts a URI Template list operator.
869
+ #
870
+ # @param [String] value The unparsed value to extract from.
871
+ # @param [#restore] processor The processor object.
872
+ # @param [String] argument The argument to the operator.
873
+ # @param [Array] variables The variables the operator is working on.
874
+ # @param [Hash] mapping The mapping of variables to values.
875
+ #
876
+ # @return [String] The extracted result.
877
+ def extract_list_operator(value, processor, argument, variables, mapping)
878
+ if variables.size != 1
879
+ raise InvalidTemplateOperatorError,
880
+ "Template operator 'list' takes exactly one variable."
881
+ end
882
+ values = value.split(argument)
883
+ if processor && processor.respond_to?(:restore)
884
+ values.map! { |value| processor.restore(variables.first, value) }
885
+ end
886
+ mapping[variables.first] = values
887
+ end
888
+ private :extract_list_operator
889
+
890
+ ##
891
+ # Joins several URIs together.
892
+ #
893
+ # @param [String, Addressable::URI, #to_str] *uris
894
+ # The URIs to join.
895
+ #
896
+ # @return [Addressable::URI] The joined URI.
897
+ #
898
+ # @example
899
+ # base = "http://example.com/"
900
+ # uri = Addressable::URI.parse("relative/path")
901
+ # Addressable::URI.join(base, uri)
902
+ # #=> #<Addressable::URI:0xcab390 URI:http://example.com/relative/path>
903
+ def self.join(*uris)
904
+ uri_objects = uris.collect do |uri|
905
+ if !uri.respond_to?(:to_str)
906
+ raise TypeError, "Can't convert #{uri.class} into String."
907
+ end
908
+ uri.kind_of?(self) ? uri : self.parse(uri.to_str)
909
+ end
910
+ result = uri_objects.shift.dup
911
+ for uri in uri_objects
912
+ result.join!(uri)
913
+ end
914
+ return result
915
+ end
916
+
917
+ ##
918
+ # Percent encodes a URI component.
919
+ #
920
+ # @param [String, #to_str] component The URI component to encode.
921
+ #
922
+ # @param [String, Regexp] character_class
923
+ # The characters which are not percent encoded. If a <tt>String</tt>
924
+ # is passed, the <tt>String</tt> must be formatted as a regular
925
+ # expression character class. (Do not include the surrounding square
926
+ # brackets.) For example, <tt>"b-zB-Z0-9"</tt> would cause everything
927
+ # but the letters 'b' through 'z' and the numbers '0' through '9' to be
928
+ # percent encoded. If a <tt>Regexp</tt> is passed, the value
929
+ # <tt>/[^b-zB-Z0-9]/</tt> would have the same effect.
930
+ # A set of useful <tt>String</tt> values may be found in the
931
+ # <tt>Addressable::URI::CharacterClasses</tt> module. The default value
932
+ # is the reserved plus unreserved character classes specified in
933
+ # <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>.
934
+ #
935
+ # @return [String] The encoded component.
936
+ #
937
+ # @example
938
+ # Addressable::URI.encode_component("simple/example", "b-zB-Z0-9")
939
+ # => "simple%2Fex%61mple"
940
+ # Addressable::URI.encode_component("simple/example", /[^b-zB-Z0-9]/)
941
+ # => "simple%2Fex%61mple"
942
+ # Addressable::URI.encode_component(
943
+ # "simple/example", Addressable::URI::CharacterClasses::UNRESERVED
944
+ # )
945
+ # => "simple%2Fexample"
946
+ def self.encode_component(component, character_class=
947
+ CharacterClasses::RESERVED + CharacterClasses::UNRESERVED)
948
+ return nil if component.nil?
949
+ if !component.respond_to?(:to_str)
950
+ raise TypeError, "Can't convert #{component.class} into String."
951
+ end
952
+ component = component.to_str
953
+ if ![String, Regexp].include?(character_class.class)
954
+ raise TypeError,
955
+ "Expected String or Regexp, got #{character_class.inspect}"
956
+ end
957
+ if character_class.kind_of?(String)
958
+ character_class = /[^#{character_class}]/
959
+ end
960
+ return component.gsub(character_class) do |sequence|
961
+ (sequence.unpack('C*').map { |c| "%#{c.to_s(16).upcase}" }).join("")
962
+ end
963
+ end
964
+
965
+ class << self
966
+ alias_method :encode_component, :encode_component
967
+ end
968
+
969
+ ##
970
+ # Unencodes any percent encoded characters within a URI component.
971
+ # This method may be used for unencoding either components or full URIs,
972
+ # however, it is recommended to use the <tt>unencode_component</tt> alias
973
+ # when unencoding components.
974
+ #
975
+ # @param [String, Addressable::URI, #to_str] uri
976
+ # The URI or component to unencode.
977
+ #
978
+ # @param [Class] returning
979
+ # The type of object to return. This value may only be set to
980
+ # <tt>String</tt> or <tt>Addressable::URI</tt>. All other values
981
+ # are invalid. Defaults to <tt>String</tt>.
982
+ #
983
+ # @return [String, Addressable::URI]
984
+ # The unencoded component or URI. The return type is determined by
985
+ # the <tt>returning</tt> parameter.
986
+ def self.unencode(uri, returning=String)
987
+ return nil if uri.nil?
988
+ if !uri.respond_to?(:to_str)
989
+ raise TypeError, "Can't convert #{uri.class} into String."
990
+ end
991
+ if ![String, ::Addressable::URI].include?(returning)
992
+ raise TypeError,
993
+ "Expected String or Addressable::URI, got #{returning.inspect}"
994
+ end
995
+ result = uri.to_str.gsub(/%[0-9a-f]{2}/i) do |sequence|
996
+ sequence[1..3].to_i(16).chr
997
+ end
998
+ result.force_encoding("utf-8") if result.respond_to?(:force_encoding)
999
+ if returning == String
1000
+ return result
1001
+ elsif returning == ::Addressable::URI
1002
+ return ::Addressable::URI.parse(result)
1003
+ end
1004
+ end
1005
+
1006
+ class << self
1007
+ alias_method :unescape, :unencode
1008
+ alias_method :unencode_component, :unencode
1009
+ alias_method :unescape_component, :unencode
1010
+ end
1011
+
1012
+ ##
1013
+ # Percent encodes any special characters in the URI.
1014
+ #
1015
+ # @param [String, Addressable::URI, #to_str] uri
1016
+ # The URI to encode.
1017
+ #
1018
+ # @param [Class] returning
1019
+ # The type of object to return. This value may only be set to
1020
+ # <tt>String</tt> or <tt>Addressable::URI</tt>. All other values
1021
+ # are invalid. Defaults to <tt>String</tt>.
1022
+ #
1023
+ # @return [String, Addressable::URI]
1024
+ # The encoded URI. The return type is determined by
1025
+ # the <tt>returning</tt> parameter.
1026
+ def self.encode(uri, returning=String)
1027
+ return nil if uri.nil?
1028
+ if !uri.respond_to?(:to_str)
1029
+ raise TypeError, "Can't convert #{uri.class} into String."
1030
+ end
1031
+ if ![String, ::Addressable::URI].include?(returning)
1032
+ raise TypeError,
1033
+ "Expected String or Addressable::URI, got #{returning.inspect}"
1034
+ end
1035
+ uri_object = uri.kind_of?(self) ? uri : self.parse(uri.to_str)
1036
+ encoded_uri = Addressable::URI.new(
1037
+ :scheme => self.encode_component(uri_object.scheme,
1038
+ Addressable::URI::CharacterClasses::SCHEME),
1039
+ :authority => self.encode_component(uri_object.authority,
1040
+ Addressable::URI::CharacterClasses::AUTHORITY),
1041
+ :path => self.encode_component(uri_object.path,
1042
+ Addressable::URI::CharacterClasses::PATH),
1043
+ :query => self.encode_component(uri_object.query,
1044
+ Addressable::URI::CharacterClasses::QUERY),
1045
+ :fragment => self.encode_component(uri_object.fragment,
1046
+ Addressable::URI::CharacterClasses::FRAGMENT)
1047
+ )
1048
+ if returning == String
1049
+ return encoded_uri.to_s
1050
+ elsif returning == ::Addressable::URI
1051
+ return encoded_uri
1052
+ end
1053
+ end
1054
+
1055
+ class << self
1056
+ alias_method :escape, :encode
1057
+ end
1058
+
1059
+ ##
1060
+ # Normalizes the encoding of a URI. Characters within a hostname are
1061
+ # not percent encoded to allow for internationalized domain names.
1062
+ #
1063
+ # @param [String, Addressable::URI, #to_str] uri
1064
+ # The URI to encode.
1065
+ #
1066
+ # @param [Class] returning
1067
+ # The type of object to return. This value may only be set to
1068
+ # <tt>String</tt> or <tt>Addressable::URI</tt>. All other values
1069
+ # are invalid. Defaults to <tt>String</tt>.
1070
+ #
1071
+ # @return [String, Addressable::URI]
1072
+ # The encoded URI. The return type is determined by
1073
+ # the <tt>returning</tt> parameter.
1074
+ def self.normalized_encode(uri, returning=String)
1075
+ if !uri.respond_to?(:to_str)
1076
+ raise TypeError, "Can't convert #{uri.class} into String."
1077
+ end
1078
+ if ![String, ::Addressable::URI].include?(returning)
1079
+ raise TypeError,
1080
+ "Expected String or Addressable::URI, got #{returning.inspect}"
1081
+ end
1082
+ uri_object = uri.kind_of?(self) ? uri : self.parse(uri.to_str)
1083
+ components = {
1084
+ :scheme => self.unencode_component(uri_object.scheme),
1085
+ :user => self.unencode_component(uri_object.user),
1086
+ :password => self.unencode_component(uri_object.password),
1087
+ :host => self.unencode_component(uri_object.host),
1088
+ :port => uri_object.port,
1089
+ :path => self.unencode_component(uri_object.path),
1090
+ :query => self.unencode_component(uri_object.query),
1091
+ :fragment => self.unencode_component(uri_object.fragment)
1092
+ }
1093
+ components.each do |key, value|
1094
+ if value != nil
1095
+ components[key] = Addressable::IDNA.unicode_normalize_kc(value.to_s)
1096
+ end
1097
+ end
1098
+ encoded_uri = Addressable::URI.new(
1099
+ :scheme => self.encode_component(components[:scheme],
1100
+ Addressable::URI::CharacterClasses::SCHEME),
1101
+ :user => self.encode_component(components[:user],
1102
+ Addressable::URI::CharacterClasses::AUTHORITY),
1103
+ :password => self.encode_component(components[:password],
1104
+ Addressable::URI::CharacterClasses::AUTHORITY),
1105
+ :host => components[:host],
1106
+ :port => components[:port],
1107
+ :path => self.encode_component(components[:path],
1108
+ Addressable::URI::CharacterClasses::PATH),
1109
+ :query => self.encode_component(components[:query],
1110
+ Addressable::URI::CharacterClasses::QUERY),
1111
+ :fragment => self.encode_component(components[:fragment],
1112
+ Addressable::URI::CharacterClasses::FRAGMENT)
1113
+ )
1114
+ if returning == String
1115
+ return encoded_uri.to_s
1116
+ elsif returning == ::Addressable::URI
1117
+ return encoded_uri
1118
+ end
1119
+ end
1120
+
1121
+ ##
1122
+ # Extracts uris from an arbitrary body of text.
1123
+ #
1124
+ # @param [String, #to_str] text
1125
+ # The body of text to extract URIs from.
1126
+ #
1127
+ # @option [String, Addressable::URI, #to_str] base
1128
+ # Causes any relative URIs to be resolved against the base URI.
1129
+ #
1130
+ # @option [TrueClass, FalseClass] parse
1131
+ # If parse is true, all extracted URIs will be parsed. If parse is
1132
+ # false, the return value with be an <tt>Array</tt> of <tt>Strings</aa>.
1133
+ # Defaults to false.
1134
+ #
1135
+ # @return [Array] The extracted URIs.
1136
+ def self.extract(text, options={})
1137
+ defaults = {:base => nil, :parse => false}
1138
+ options = defaults.merge(options)
1139
+ raise InvalidOptionError unless (options.keys - defaults.keys).empty?
1140
+ # This regular expression needs to be less forgiving or else it would
1141
+ # match virtually all text. Which isn't exactly what we're going for.
1142
+ extract_regex = /((([a-z\+]+):)[^ \n\<\>\"\\]+[\w\/])/
1143
+ extracted_uris =
1144
+ text.scan(extract_regex).collect { |match| match[0] }
1145
+ sgml_extract_regex = /<[^>]+href=\"([^\"]+?)\"[^>]*>/
1146
+ sgml_extracted_uris =
1147
+ text.scan(sgml_extract_regex).collect { |match| match[0] }
1148
+ extracted_uris.concat(sgml_extracted_uris - extracted_uris)
1149
+ textile_extract_regex = /\".+?\":([^ ]+\/[^ ]+)[ \,\.\;\:\?\!\<\>\"]/i
1150
+ textile_extracted_uris =
1151
+ text.scan(textile_extract_regex).collect { |match| match[0] }
1152
+ extracted_uris.concat(textile_extracted_uris - extracted_uris)
1153
+ parsed_uris = []
1154
+ base_uri = nil
1155
+ if options[:base] != nil
1156
+ base_uri = options[:base] if options[:base].kind_of?(self)
1157
+ base_uri = self.parse(options[:base].to_s) if base_uri == nil
1158
+ end
1159
+ for uri_string in extracted_uris
1160
+ begin
1161
+ if base_uri == nil
1162
+ parsed_uris << self.parse(uri_string)
1163
+ else
1164
+ parsed_uris << (base_uri + self.parse(uri_string))
1165
+ end
1166
+ rescue Exception
1167
+ nil
1168
+ end
1169
+ end
1170
+ parsed_uris = parsed_uris.select do |uri|
1171
+ (self.ip_based_schemes | [
1172
+ "file", "git", "svn", "mailto", "tel"
1173
+ ]).include?(uri.normalized_scheme)
1174
+ end
1175
+ if options[:parse]
1176
+ return parsed_uris
1177
+ else
1178
+ return parsed_uris.collect { |uri| uri.to_s }
1179
+ end
1180
+ end
1181
+
1182
+ ##
1183
+ # Creates a new uri object from component parts.
1184
+ #
1185
+ # @option [String, #to_str] scheme The scheme component.
1186
+ # @option [String, #to_str] user The user component.
1187
+ # @option [String, #to_str] password The password component.
1188
+ # @option [String, #to_str] userinfo
1189
+ # The userinfo component. If this is supplied, the user and password
1190
+ # components must be omitted.
1191
+ # @option [String, #to_str] host The host component.
1192
+ # @option [String, #to_str] port The port component.
1193
+ # @option [String, #to_str] authority
1194
+ # The authority component. If this is supplied, the user, password,
1195
+ # userinfo, host, and port components must be omitted.
1196
+ # @option [String, #to_str] path The path component.
1197
+ # @option [String, #to_str] query The query component.
1198
+ # @option [String, #to_str] fragment The fragment component.
1199
+ #
1200
+ # @return [Addressable::URI] The constructed URI object.
1201
+ def initialize(options={})
1202
+ if options.has_key?(:authority)
1203
+ if (options.keys & [:userinfo, :user, :password, :host, :port]).any?
1204
+ raise ArgumentError,
1205
+ "Cannot specify both an authority and any of the components " +
1206
+ "within the authority."
1207
+ end
1208
+ end
1209
+ if options.has_key?(:userinfo)
1210
+ if (options.keys & [:user, :password]).any?
1211
+ raise ArgumentError,
1212
+ "Cannot specify both a userinfo and either the user or password."
1213
+ end
1214
+ end
1215
+
1216
+ self.validation_deferred = true
1217
+ self.scheme = options[:scheme] if options[:scheme]
1218
+ self.user = options[:user] if options[:user]
1219
+ self.password = options[:password] if options[:password]
1220
+ self.userinfo = options[:userinfo] if options[:userinfo]
1221
+ self.host = options[:host] if options[:host]
1222
+ self.port = options[:port] if options[:port]
1223
+ self.authority = options[:authority] if options[:authority]
1224
+ self.path = options[:path] if options[:path]
1225
+ self.query = options[:query] if options[:query]
1226
+ self.fragment = options[:fragment] if options[:fragment]
1227
+ self.validation_deferred = false
1228
+ end
1229
+
1230
+ ##
1231
+ # The scheme component for this URI.
1232
+ #
1233
+ # @return [String] The scheme component.
1234
+ def scheme
1235
+ return @scheme
1236
+ end
1237
+
1238
+ ##
1239
+ # The scheme component for this URI, normalized.
1240
+ #
1241
+ # @return [String] The scheme component, normalized.
1242
+ def normalized_scheme
1243
+ @normalized_scheme ||= (begin
1244
+ if self.scheme != nil
1245
+ if self.scheme =~ /^\s*ssh\+svn\s*$/i
1246
+ "svn+ssh"
1247
+ else
1248
+ self.scheme.strip.downcase
1249
+ end
1250
+ else
1251
+ nil
1252
+ end
1253
+ end)
1254
+ end
1255
+
1256
+ ##
1257
+ # Sets the scheme component for this URI.
1258
+ #
1259
+ # @param [String, #to_str] new_scheme The new scheme component.
1260
+ def scheme=(new_scheme)
1261
+ @scheme = new_scheme ? new_scheme.to_str : nil
1262
+ @scheme = nil if @scheme.to_s.strip == ""
1263
+
1264
+ # Reset dependant values
1265
+ @normalized_scheme = nil
1266
+ end
1267
+
1268
+ ##
1269
+ # The user component for this URI.
1270
+ #
1271
+ # @return [String] The user component.
1272
+ def user
1273
+ return @user
1274
+ end
1275
+
1276
+ ##
1277
+ # The user component for this URI, normalized.
1278
+ #
1279
+ # @return [String] The user component, normalized.
1280
+ def normalized_user
1281
+ @normalized_user ||= (begin
1282
+ if self.user
1283
+ if normalized_scheme =~ /https?/ && self.user.strip == "" &&
1284
+ (!self.password || self.password.strip == "")
1285
+ nil
1286
+ else
1287
+ self.user.strip
1288
+ end
1289
+ else
1290
+ nil
1291
+ end
1292
+ end)
1293
+ end
1294
+
1295
+ ##
1296
+ # Sets the user component for this URI.
1297
+ #
1298
+ # @param [String, #to_str] new_user The new user component.
1299
+ def user=(new_user)
1300
+ @user = new_user ? new_user.to_str : nil
1301
+
1302
+ # You can't have a nil user with a non-nil password
1303
+ if @password != nil
1304
+ @user = "" if @user.nil?
1305
+ end
1306
+
1307
+ # Reset dependant values
1308
+ @userinfo = nil
1309
+ @normalized_userinfo = nil
1310
+ @authority = nil
1311
+ @normalized_user = nil
1312
+
1313
+ # Ensure we haven't created an invalid URI
1314
+ validate()
1315
+ end
1316
+
1317
+ ##
1318
+ # The password component for this URI.
1319
+ #
1320
+ # @return [String] The password component.
1321
+ def password
1322
+ return @password
1323
+ end
1324
+
1325
+ ##
1326
+ # The password component for this URI, normalized.
1327
+ #
1328
+ # @return [String] The password component, normalized.
1329
+ def normalized_password
1330
+ @normalized_password ||= (begin
1331
+ if self.password
1332
+ if normalized_scheme =~ /https?/ && self.password.strip == "" &&
1333
+ (!self.user || self.user.strip == "")
1334
+ nil
1335
+ else
1336
+ self.password.strip
1337
+ end
1338
+ else
1339
+ nil
1340
+ end
1341
+ end)
1342
+ end
1343
+
1344
+ ##
1345
+ # Sets the password component for this URI.
1346
+ #
1347
+ # @param [String, #to_str] new_password The new password component.
1348
+ def password=(new_password)
1349
+ @password = new_password ? new_password.to_str : nil
1350
+
1351
+ # You can't have a nil user with a non-nil password
1352
+ if @password != nil
1353
+ @user = "" if @user.nil?
1354
+ end
1355
+
1356
+ # Reset dependant values
1357
+ @userinfo = nil
1358
+ @normalized_userinfo = nil
1359
+ @authority = nil
1360
+ @normalized_password = nil
1361
+
1362
+ # Ensure we haven't created an invalid URI
1363
+ validate()
1364
+ end
1365
+
1366
+ ##
1367
+ # The userinfo component for this URI.
1368
+ # Combines the user and password components.
1369
+ #
1370
+ # @return [String] The userinfo component.
1371
+ def userinfo
1372
+ @userinfo ||= (begin
1373
+ current_user = self.user
1374
+ current_password = self.password
1375
+ if !current_user && !current_password
1376
+ nil
1377
+ elsif current_user && current_password
1378
+ "#{current_user}:#{current_password}"
1379
+ elsif current_user && !current_password
1380
+ "#{current_user}"
1381
+ end
1382
+ end)
1383
+ end
1384
+
1385
+ ##
1386
+ # The userinfo component for this URI, normalized.
1387
+ #
1388
+ # @return [String] The userinfo component, normalized.
1389
+ def normalized_userinfo
1390
+ @normalized_userinfo ||= (begin
1391
+ current_user = self.normalized_user
1392
+ current_password = self.normalized_password
1393
+ if !current_user && !current_password
1394
+ nil
1395
+ elsif current_user && current_password
1396
+ "#{current_user}:#{current_password}"
1397
+ elsif current_user && !current_password
1398
+ "#{current_user}"
1399
+ end
1400
+ end)
1401
+ end
1402
+
1403
+ ##
1404
+ # Sets the userinfo component for this URI.
1405
+ #
1406
+ # @param [String, #to_str] new_userinfo The new userinfo component.
1407
+ def userinfo=(new_userinfo)
1408
+ new_user, new_password = if new_userinfo
1409
+ [
1410
+ new_userinfo.to_str.strip[/^(.*):/, 1],
1411
+ new_userinfo.to_str.strip[/:(.*)$/, 1]
1412
+ ]
1413
+ else
1414
+ [nil, nil]
1415
+ end
1416
+
1417
+ # Password assigned first to ensure validity in case of nil
1418
+ self.password = new_password
1419
+ self.user = new_user
1420
+
1421
+ # Reset dependant values
1422
+ @authority = nil
1423
+
1424
+ # Ensure we haven't created an invalid URI
1425
+ validate()
1426
+ end
1427
+
1428
+ ##
1429
+ # The host component for this URI.
1430
+ #
1431
+ # @return [String] The host component.
1432
+ def host
1433
+ return @host
1434
+ end
1435
+
1436
+ ##
1437
+ # The host component for this URI, normalized.
1438
+ #
1439
+ # @return [String] The host component, normalized.
1440
+ def normalized_host
1441
+ @normalized_host ||= (begin
1442
+ if self.host != nil
1443
+ if self.host.strip != ""
1444
+ result = ::Addressable::IDNA.to_ascii(
1445
+ self.class.unencode_component(self.host.strip.downcase)
1446
+ )
1447
+ if result[-1..-1] == "."
1448
+ # Trailing dots are unnecessary
1449
+ result = result[0...-1]
1450
+ end
1451
+ result
1452
+ else
1453
+ ""
1454
+ end
1455
+ else
1456
+ nil
1457
+ end
1458
+ end)
1459
+ end
1460
+
1461
+ ##
1462
+ # Sets the host component for this URI.
1463
+ #
1464
+ # @param [String, #to_str] new_host The new host component.
1465
+ def host=(new_host)
1466
+ @host = new_host ? new_host.to_str : nil
1467
+
1468
+ # Reset dependant values
1469
+ @authority = nil
1470
+ @normalized_host = nil
1471
+
1472
+ # Ensure we haven't created an invalid URI
1473
+ validate()
1474
+ end
1475
+
1476
+ ##
1477
+ # The authority component for this URI.
1478
+ # Combines the user, password, host, and port components.
1479
+ #
1480
+ # @return [String] The authority component.
1481
+ def authority
1482
+ @authority ||= (begin
1483
+ if self.host.nil?
1484
+ nil
1485
+ else
1486
+ authority = ""
1487
+ if self.userinfo != nil
1488
+ authority << "#{self.userinfo}@"
1489
+ end
1490
+ authority << self.host
1491
+ if self.port != nil
1492
+ authority << ":#{self.port}"
1493
+ end
1494
+ authority
1495
+ end
1496
+ end)
1497
+ end
1498
+
1499
+ ##
1500
+ # The authority component for this URI, normalized.
1501
+ #
1502
+ # @return [String] The authority component, normalized.
1503
+ def normalized_authority
1504
+ @normalized_authority ||= (begin
1505
+ if self.normalized_host.nil?
1506
+ nil
1507
+ else
1508
+ authority = ""
1509
+ if self.normalized_userinfo != nil
1510
+ authority << "#{self.normalized_userinfo}@"
1511
+ end
1512
+ authority << self.normalized_host
1513
+ if self.normalized_port != nil
1514
+ authority << ":#{self.normalized_port}"
1515
+ end
1516
+ authority
1517
+ end
1518
+ end)
1519
+ end
1520
+
1521
+ ##
1522
+ # Sets the authority component for this URI.
1523
+ #
1524
+ # @param [String, #to_str] new_authority The new authority component.
1525
+ def authority=(new_authority)
1526
+ if new_authority
1527
+ new_authority = new_authority.to_str
1528
+ new_userinfo = new_authority[/^([^\[\]]*)@/, 1]
1529
+ if new_userinfo
1530
+ new_user = new_userinfo.strip[/^([^:]*):?/, 1]
1531
+ new_password = new_userinfo.strip[/:(.*)$/, 1]
1532
+ end
1533
+ new_host =
1534
+ new_authority.gsub(/^([^\[\]]*)@/, "").gsub(/:([^:@\[\]]*?)$/, "")
1535
+ new_port =
1536
+ new_authority[/:([^:@\[\]]*?)$/, 1]
1537
+ end
1538
+
1539
+ # Password assigned first to ensure validity in case of nil
1540
+ self.password = new_password
1541
+ self.user = new_user
1542
+ self.host = new_host
1543
+ self.port = new_port
1544
+
1545
+ # Reset dependant values
1546
+ @inferred_port = nil
1547
+ @userinfo = nil
1548
+ @normalized_userinfo = nil
1549
+
1550
+ # Ensure we haven't created an invalid URI
1551
+ validate()
1552
+ end
1553
+
1554
+ # Returns an array of known ip-based schemes. These schemes typically
1555
+ # use a similar URI form:
1556
+ # //<user>:<password>@<host>:<port>/<url-path>
1557
+ def self.ip_based_schemes
1558
+ return self.port_mapping.keys
1559
+ end
1560
+
1561
+ # Returns a hash of common IP-based schemes and their default port
1562
+ # numbers. Adding new schemes to this hash, as necessary, will allow
1563
+ # for better URI normalization.
1564
+ def self.port_mapping
1565
+ @port_mapping ||= {
1566
+ "http" => 80,
1567
+ "https" => 443,
1568
+ "ftp" => 21,
1569
+ "tftp" => 69,
1570
+ "sftp" => 22,
1571
+ "ssh" => 22,
1572
+ "svn+ssh" => 22,
1573
+ "telnet" => 23,
1574
+ "nntp" => 119,
1575
+ "gopher" => 70,
1576
+ "wais" => 210,
1577
+ "ldap" => 389,
1578
+ "prospero" => 1525
1579
+ }
1580
+ end
1581
+
1582
+ ##
1583
+ # The port component for this URI.
1584
+ # This is the port number actually given in the URI. This does not
1585
+ # infer port numbers from default values.
1586
+ #
1587
+ # @return [Integer] The port component.
1588
+ def port
1589
+ return @port
1590
+ end
1591
+
1592
+ ##
1593
+ # The port component for this URI, normalized.
1594
+ #
1595
+ # @return [Integer] The port component, normalized.
1596
+ def normalized_port
1597
+ @normalized_port ||= (begin
1598
+ if self.class.port_mapping[normalized_scheme] == self.port
1599
+ nil
1600
+ else
1601
+ self.port
1602
+ end
1603
+ end)
1604
+ end
1605
+
1606
+ ##
1607
+ # Sets the port component for this URI.
1608
+ #
1609
+ # @param [String, Integer, #to_s] new_port The new port component.
1610
+ def port=(new_port)
1611
+ if new_port != nil && !(new_port.to_s =~ /^\d+$/)
1612
+ raise InvalidURIError,
1613
+ "Invalid port number: #{new_port.inspect}"
1614
+ end
1615
+
1616
+ @port = new_port.to_s.to_i
1617
+ @port = nil if @port == 0
1618
+
1619
+ # Reset dependant values
1620
+ @authority = nil
1621
+ @inferred_port = nil
1622
+ @normalized_port = nil
1623
+
1624
+ # Ensure we haven't created an invalid URI
1625
+ validate()
1626
+ end
1627
+
1628
+ ##
1629
+ # The inferred port component for this URI.
1630
+ # This method will normalize to the default port for the URI's scheme if
1631
+ # the port isn't explicitly specified in the URI.
1632
+ #
1633
+ # @return [Integer] The inferred port component.
1634
+ def inferred_port
1635
+ @inferred_port ||= (begin
1636
+ if port.to_i == 0
1637
+ if scheme
1638
+ self.class.port_mapping[scheme.strip.downcase]
1639
+ else
1640
+ nil
1641
+ end
1642
+ else
1643
+ port.to_i
1644
+ end
1645
+ end)
1646
+ end
1647
+
1648
+ ##
1649
+ # The path component for this URI.
1650
+ #
1651
+ # @return [String] The path component.
1652
+ def path
1653
+ return (@path || "")
1654
+ end
1655
+
1656
+ ##
1657
+ # The path component for this URI, normalized.
1658
+ #
1659
+ # @return [String] The path component, normalized.
1660
+ def normalized_path
1661
+ @normalized_path ||= (begin
1662
+ result = self.class.normalize_path(self.path.strip)
1663
+ if result == "" &&
1664
+ ["http", "https", "ftp", "tftp"].include?(self.normalized_scheme)
1665
+ result = "/"
1666
+ end
1667
+ result
1668
+ end)
1669
+ end
1670
+
1671
+ ##
1672
+ # Sets the path component for this URI.
1673
+ #
1674
+ # @param [String, #to_str] new_path The new path component.
1675
+ def path=(new_path)
1676
+ @path = (new_path || "").to_str
1677
+ if @path != "" && @path[0..0] != "/" && host != nil
1678
+ @path = "/#{@path}"
1679
+ end
1680
+
1681
+ # Reset dependant values
1682
+ @normalized_path = nil
1683
+ end
1684
+
1685
+ ##
1686
+ # The basename, if any, of the file in the path component.
1687
+ #
1688
+ # @return [String] The path's basename.
1689
+ def basename
1690
+ # Path cannot be nil
1691
+ return File.basename(self.path).gsub(/;[^\/]*$/, "")
1692
+ end
1693
+
1694
+ ##
1695
+ # The extname, if any, of the file in the path component.
1696
+ # Empty string if there is no extension.
1697
+ #
1698
+ # @return [String] The path's extname.
1699
+ def extname
1700
+ return nil unless self.path
1701
+ return File.extname(self.basename)
1702
+ end
1703
+
1704
+ ##
1705
+ # The query component for this URI.
1706
+ #
1707
+ # @return [String] The query component.
1708
+ def query
1709
+ return @query
1710
+ end
1711
+
1712
+ ##
1713
+ # The query component for this URI, normalized.
1714
+ #
1715
+ # @return [String] The query component, normalized.
1716
+ def normalized_query
1717
+ @normalized_query ||= (self.query ? self.query.strip : nil)
1718
+ end
1719
+
1720
+ ##
1721
+ # Sets the query component for this URI.
1722
+ #
1723
+ # @param [String, #to_str] new_query The new query component.
1724
+ def query=(new_query)
1725
+ @query = new_query.to_str
1726
+
1727
+ # Reset dependant values
1728
+ @normalized_query = nil
1729
+ end
1730
+
1731
+ ##
1732
+ # Converts the query component to a Hash value.
1733
+ #
1734
+ # @option [Symbol] notation
1735
+ # May be one of <tt>:flat</tt>, <tt>:dot</tt>, or <tt>:subscript</tt>.
1736
+ # The <tt>:dot</tt> notation is not supported for assignment.
1737
+ # Default value is <tt>:subscript</tt>.
1738
+ #
1739
+ # @return [Hash] The query string parsed as a Hash object.
1740
+ #
1741
+ # @example
1742
+ # Addressable::URI.parse("?one=1&two=2&three=3").query_values
1743
+ # #=> {"one" => "1", "two" => "2", "three" => "3"}
1744
+ # Addressable::URI.parse("?one[two][three]=four").query_values
1745
+ # #=> {"one" => {"two" => {"three" => "four"}}}
1746
+ # Addressable::URI.parse("?one.two.three=four").query_values(
1747
+ # :notation => :dot
1748
+ # )
1749
+ # #=> {"one" => {"two" => {"three" => "four"}}}
1750
+ # Addressable::URI.parse("?one[two][three]=four").query_values(
1751
+ # :notation => :flat
1752
+ # )
1753
+ # #=> {"one[two][three]" => "four"}
1754
+ # Addressable::URI.parse("?one.two.three=four").query_values(
1755
+ # :notation => :flat
1756
+ # )
1757
+ # #=> {"one.two.three" => "four"}
1758
+ # Addressable::URI.parse(
1759
+ # "?one[two][three][]=four&one[two][three][]=five"
1760
+ # ).query_values
1761
+ # #=> {"one" => {"two" => {"three" => ["four", "five"]}}}
1762
+ def query_values(options={})
1763
+ defaults = {:notation => :subscript}
1764
+ options = defaults.merge(options)
1765
+ if ![:flat, :dot, :subscript].include?(options[:notation])
1766
+ raise ArgumentError,
1767
+ "Invalid notation. Must be one of: [:flat, :dot, :subscript]."
1768
+ end
1769
+ return nil if self.query == nil
1770
+ return (self.query.split("&").map do |pair|
1771
+ pair.split("=")
1772
+ end).inject({}) do |accumulator, pair|
1773
+ key, value = pair
1774
+ value = true if value.nil?
1775
+ key = self.class.unencode_component(key)
1776
+ if value != true
1777
+ value = self.class.unencode_component(value).gsub(/\+/, " ")
1778
+ end
1779
+ if options[:notation] == :flat
1780
+ if accumulator[key]
1781
+ raise ArgumentError, "Key was repeated: #{key.inspect}"
1782
+ end
1783
+ accumulator[key] = value
1784
+ else
1785
+ if options[:notation] == :dot
1786
+ array_value = false
1787
+ subkeys = key.split(".")
1788
+ elsif options[:notation] == :subscript
1789
+ array_value = !!(key =~ /\[\]$/)
1790
+ subkeys = key.split(/[\[\]]+/)
1791
+ end
1792
+ current_hash = accumulator
1793
+ for i in 0...(subkeys.size - 1)
1794
+ subkey = subkeys[i]
1795
+ current_hash[subkey] = {} unless current_hash[subkey]
1796
+ current_hash = current_hash[subkey]
1797
+ end
1798
+ if array_value
1799
+ current_hash[subkeys.last] = [] unless current_hash[subkeys.last]
1800
+ current_hash[subkeys.last] << value
1801
+ else
1802
+ current_hash[subkeys.last] = value
1803
+ end
1804
+ end
1805
+ accumulator
1806
+ end
1807
+ end
1808
+
1809
+ ##
1810
+ # Sets the query component for this URI from a Hash object.
1811
+ #
1812
+ # @param [Hash, #to_hash] new_query_values The new query values.
1813
+ def query_values=(new_query_values)
1814
+ @query = (new_query_values.to_hash.inject([]) do |accumulator, pair|
1815
+ key, value = pair
1816
+ key = self.class.encode_component(key, CharacterClasses::UNRESERVED)
1817
+ if value == true
1818
+ accumulator << "#{key}"
1819
+ else
1820
+ value = self.class.encode_component(
1821
+ value, CharacterClasses::UNRESERVED)
1822
+ accumulator << "#{key}=#{value}"
1823
+ end
1824
+ end).join("&")
1825
+
1826
+ # Reset dependant values
1827
+ @normalized_query = nil
1828
+ end
1829
+
1830
+ ##
1831
+ # The fragment component for this URI.
1832
+ #
1833
+ # @return [String] The fragment component.
1834
+ def fragment
1835
+ return @fragment
1836
+ end
1837
+
1838
+ ##
1839
+ # The fragment component for this URI, normalized.
1840
+ #
1841
+ # @return [String] The fragment component, normalized.
1842
+ def normalized_fragment
1843
+ @normalized_fragment ||= (self.fragment ? self.fragment.strip : nil)
1844
+ end
1845
+
1846
+ ##
1847
+ # Sets the fragment component for this URI.
1848
+ #
1849
+ # @param [String, #to_str] new_fragment The new fragment component.
1850
+ def fragment=(new_fragment)
1851
+ @fragment = new_fragment ? new_fragment.to_str : nil
1852
+
1853
+ # Reset dependant values
1854
+ @normalized_fragment = nil
1855
+ end
1856
+
1857
+ ##
1858
+ # Determines if the scheme indicates an IP-based protocol.
1859
+ #
1860
+ # @return [TrueClass, FalseClass]
1861
+ # <tt>true</tt> if the scheme indicates an IP-based protocol.
1862
+ # <tt>false</tt> otherwise.
1863
+ def ip_based?
1864
+ if self.scheme
1865
+ return self.class.ip_based_schemes.include?(
1866
+ self.scheme.strip.downcase)
1867
+ end
1868
+ return false
1869
+ end
1870
+
1871
+ ##
1872
+ # Determines if the URI is relative.
1873
+ #
1874
+ # @return [TrueClass, FalseClass]
1875
+ # <tt>true</tt> if the URI is relative.
1876
+ # <tt>false</tt> otherwise.
1877
+ def relative?
1878
+ return self.scheme.nil?
1879
+ end
1880
+
1881
+ ##
1882
+ # Determines if the URI is absolute.
1883
+ #
1884
+ # @return [TrueClass, FalseClass]
1885
+ # <tt>true</tt> if the URI is absolute.
1886
+ # <tt>false</tt> otherwise.
1887
+ def absolute?
1888
+ return !relative?
1889
+ end
1890
+
1891
+ ##
1892
+ # Joins two URIs together.
1893
+ #
1894
+ # @param [String, Addressable::URI, #to_str] The URI to join with.
1895
+ #
1896
+ # @return [Addressable::URI] The joined URI.
1897
+ def join(uri)
1898
+ if !uri.respond_to?(:to_str)
1899
+ raise TypeError, "Can't convert #{uri.class} into String."
1900
+ end
1901
+ if !uri.kind_of?(self.class)
1902
+ # Otherwise, convert to a String, then parse.
1903
+ uri = self.class.parse(uri.to_str)
1904
+ end
1905
+ if uri.to_s == ""
1906
+ return self.dup
1907
+ end
1908
+
1909
+ joined_scheme = nil
1910
+ joined_user = nil
1911
+ joined_password = nil
1912
+ joined_host = nil
1913
+ joined_port = nil
1914
+ joined_path = nil
1915
+ joined_query = nil
1916
+ joined_fragment = nil
1917
+
1918
+ # Section 5.2.2 of RFC 3986
1919
+ if uri.scheme != nil
1920
+ joined_scheme = uri.scheme
1921
+ joined_user = uri.user
1922
+ joined_password = uri.password
1923
+ joined_host = uri.host
1924
+ joined_port = uri.port
1925
+ joined_path = self.class.normalize_path(uri.path)
1926
+ joined_query = uri.query
1927
+ else
1928
+ if uri.authority != nil
1929
+ joined_user = uri.user
1930
+ joined_password = uri.password
1931
+ joined_host = uri.host
1932
+ joined_port = uri.port
1933
+ joined_path = self.class.normalize_path(uri.path)
1934
+ joined_query = uri.query
1935
+ else
1936
+ if uri.path == nil || uri.path == ""
1937
+ joined_path = self.path
1938
+ if uri.query != nil
1939
+ joined_query = uri.query
1940
+ else
1941
+ joined_query = self.query
1942
+ end
1943
+ else
1944
+ if uri.path[0..0] == "/"
1945
+ joined_path = self.class.normalize_path(uri.path)
1946
+ else
1947
+ base_path = self.path.dup
1948
+ base_path = "" if base_path == nil
1949
+ base_path = self.class.normalize_path(base_path)
1950
+
1951
+ # Section 5.2.3 of RFC 3986
1952
+ #
1953
+ # Removes the right-most path segment from the base path.
1954
+ if base_path =~ /\//
1955
+ base_path.gsub!(/\/[^\/]+$/, "/")
1956
+ else
1957
+ base_path = ""
1958
+ end
1959
+
1960
+ # If the base path is empty and an authority segment has been
1961
+ # defined, use a base path of "/"
1962
+ if base_path == "" && self.authority != nil
1963
+ base_path = "/"
1964
+ end
1965
+
1966
+ joined_path = self.class.normalize_path(base_path + uri.path)
1967
+ end
1968
+ joined_query = uri.query
1969
+ end
1970
+ joined_user = self.user
1971
+ joined_password = self.password
1972
+ joined_host = self.host
1973
+ joined_port = self.port
1974
+ end
1975
+ joined_scheme = self.scheme
1976
+ end
1977
+ joined_fragment = uri.fragment
1978
+
1979
+ return Addressable::URI.new(
1980
+ :scheme => joined_scheme,
1981
+ :user => joined_user,
1982
+ :password => joined_password,
1983
+ :host => joined_host,
1984
+ :port => joined_port,
1985
+ :path => joined_path,
1986
+ :query => joined_query,
1987
+ :fragment => joined_fragment
1988
+ )
1989
+ end
1990
+ alias_method :+, :join
1991
+
1992
+ ##
1993
+ # Destructive form of <tt>join</tt>.
1994
+ #
1995
+ # @param [String, Addressable::URI, #to_str] The URI to join with.
1996
+ #
1997
+ # @return [Addressable::URI] The joined URI.
1998
+ #
1999
+ # @see Addressable::URI#join
2000
+ def join!(uri)
2001
+ replace_self(self.join(uri))
2002
+ end
2003
+
2004
+ ##
2005
+ # Merges a URI with a <tt>Hash</tt> of components.
2006
+ # This method has different behavior from <tt>join</tt>. Any components
2007
+ # present in the <tt>hash</tt> parameter will override the original
2008
+ # components. The path component is not treated specially.
2009
+ #
2010
+ # @param [Hash, Addressable::URI, #to_hash] The components to merge with.
2011
+ #
2012
+ # @return [Addressable::URI] The merged URI.
2013
+ #
2014
+ # @see Hash#merge
2015
+ def merge(hash)
2016
+ if !hash.respond_to?(:to_hash)
2017
+ raise TypeError, "Can't convert #{hash.class} into Hash."
2018
+ end
2019
+ hash = hash.to_hash
2020
+
2021
+ if hash.has_key?(:authority)
2022
+ if (hash.keys & [:userinfo, :user, :password, :host, :port]).any?
2023
+ raise ArgumentError,
2024
+ "Cannot specify both an authority and any of the components " +
2025
+ "within the authority."
2026
+ end
2027
+ end
2028
+ if hash.has_key?(:userinfo)
2029
+ if (hash.keys & [:user, :password]).any?
2030
+ raise ArgumentError,
2031
+ "Cannot specify both a userinfo and either the user or password."
2032
+ end
2033
+ end
2034
+
2035
+ uri = Addressable::URI.new
2036
+ uri.validation_deferred = true
2037
+ uri.scheme =
2038
+ hash.has_key?(:scheme) ? hash[:scheme] : self.scheme
2039
+ if hash.has_key?(:authority)
2040
+ uri.authority =
2041
+ hash.has_key?(:authority) ? hash[:authority] : self.authority
2042
+ end
2043
+ if hash.has_key?(:userinfo)
2044
+ uri.userinfo =
2045
+ hash.has_key?(:userinfo) ? hash[:userinfo] : self.userinfo
2046
+ end
2047
+ if !hash.has_key?(:userinfo) && !hash.has_key?(:authority)
2048
+ uri.user =
2049
+ hash.has_key?(:user) ? hash[:user] : self.user
2050
+ uri.password =
2051
+ hash.has_key?(:password) ? hash[:password] : self.password
2052
+ end
2053
+ if !hash.has_key?(:authority)
2054
+ uri.host =
2055
+ hash.has_key?(:host) ? hash[:host] : self.host
2056
+ uri.port =
2057
+ hash.has_key?(:port) ? hash[:port] : self.port
2058
+ end
2059
+ uri.path =
2060
+ hash.has_key?(:path) ? hash[:path] : self.path
2061
+ uri.query =
2062
+ hash.has_key?(:query) ? hash[:query] : self.query
2063
+ uri.fragment =
2064
+ hash.has_key?(:fragment) ? hash[:fragment] : self.fragment
2065
+ uri.validation_deferred = false
2066
+
2067
+ return uri
2068
+ end
2069
+
2070
+ ##
2071
+ # Destructive form of <tt>merge</tt>.
2072
+ #
2073
+ # @param [Hash, Addressable::URI, #to_hash] The components to merge with.
2074
+ #
2075
+ # @return [Addressable::URI] The merged URI.
2076
+ #
2077
+ # @see Addressable::URI#merge
2078
+ def merge!(uri)
2079
+ replace_self(self.merge(uri))
2080
+ end
2081
+
2082
+ ##
2083
+ # Returns the shortest normalized relative form of this URI that uses the
2084
+ # supplied URI as a base for resolution. Returns an absolute URI if
2085
+ # necessary. This is effectively the opposite of <tt>route_to</tt>.
2086
+ #
2087
+ # @param [String, Addressable::URI, #to_str] uri The URI to route from.
2088
+ #
2089
+ # @return [Addressable::URI]
2090
+ # The normalized relative URI that is equivalent to the original URI.
2091
+ def route_from(uri)
2092
+ uri = self.class.parse(uri).normalize
2093
+ normalized_self = self.normalize
2094
+ if normalized_self.relative?
2095
+ raise ArgumentError, "Expected absolute URI, got: #{self.to_s}"
2096
+ end
2097
+ if uri.relative?
2098
+ raise ArgumentError, "Expected absolute URI, got: #{uri.to_s}"
2099
+ end
2100
+ if normalized_self == uri
2101
+ return Addressable::URI.parse("##{normalized_self.fragment}")
2102
+ end
2103
+ components = normalized_self.to_hash
2104
+ if normalized_self.scheme == uri.scheme
2105
+ components[:scheme] = nil
2106
+ if normalized_self.authority == uri.authority
2107
+ components[:user] = nil
2108
+ components[:password] = nil
2109
+ components[:host] = nil
2110
+ components[:port] = nil
2111
+ if normalized_self.path == uri.path
2112
+ components[:path] = nil
2113
+ if normalized_self.query == uri.query
2114
+ components[:query] = nil
2115
+ end
2116
+ else
2117
+ if uri.path != "/"
2118
+ components[:path].gsub!(
2119
+ Regexp.new("^" + Regexp.escape(uri.path)), "")
2120
+ end
2121
+ end
2122
+ end
2123
+ end
2124
+ # Avoid network-path references.
2125
+ if components[:host] != nil
2126
+ components[:scheme] = normalized_self.scheme
2127
+ end
2128
+ return Addressable::URI.new(
2129
+ :scheme => components[:scheme],
2130
+ :user => components[:user],
2131
+ :password => components[:password],
2132
+ :host => components[:host],
2133
+ :port => components[:port],
2134
+ :path => components[:path],
2135
+ :query => components[:query],
2136
+ :fragment => components[:fragment]
2137
+ )
2138
+ end
2139
+
2140
+ ##
2141
+ # Returns the shortest normalized relative form of the supplied URI that
2142
+ # uses this URI as a base for resolution. Returns an absolute URI if
2143
+ # necessary. This is effectively the opposite of <tt>route_from</tt>.
2144
+ #
2145
+ # @param [String, Addressable::URI, #to_str] uri The URI to route to.
2146
+ #
2147
+ # @return [Addressable::URI]
2148
+ # The normalized relative URI that is equivalent to the supplied URI.
2149
+ def route_to(uri)
2150
+ return self.class.parse(uri).route_from(self)
2151
+ end
2152
+
2153
+ ##
2154
+ # Returns a normalized URI object.
2155
+ #
2156
+ # NOTE: This method does not attempt to fully conform to specifications.
2157
+ # It exists largely to correct other people's failures to read the
2158
+ # specifications, and also to deal with caching issues since several
2159
+ # different URIs may represent the same resource and should not be
2160
+ # cached multiple times.
2161
+ #
2162
+ # @return [Addressable::URI] The normalized URI.
2163
+ def normalize
2164
+ # This is a special exception for the frequently misused feed
2165
+ # URI scheme.
2166
+ if normalized_scheme == "feed"
2167
+ if self.to_s =~ /^feed:\/*http:\/*/
2168
+ return self.class.parse(
2169
+ self.to_s[/^feed:\/*(http:\/*.*)/, 1]
2170
+ ).normalize
2171
+ end
2172
+ end
2173
+
2174
+ return Addressable::URI.normalized_encode(
2175
+ Addressable::URI.new(
2176
+ :scheme => normalized_scheme,
2177
+ :authority => normalized_authority,
2178
+ :path => normalized_path,
2179
+ :query => normalized_query,
2180
+ :fragment => normalized_fragment
2181
+ ),
2182
+ ::Addressable::URI
2183
+ )
2184
+ end
2185
+
2186
+ ##
2187
+ # Destructively normalizes this URI object.
2188
+ #
2189
+ # @return [Addressable::URI] The normalized URI.
2190
+ #
2191
+ # @see Addressable::URI#normalize
2192
+ def normalize!
2193
+ replace_self(self.normalize)
2194
+ end
2195
+
2196
+ ##
2197
+ # Creates a URI suitable for display to users. If semantic attacks are
2198
+ # likely, the application should try to detect these and warn the user.
2199
+ # See <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>,
2200
+ # section 7.6 for more information.
2201
+ #
2202
+ # @return [Addressable::URI] A URI suitable for display purposes.
2203
+ def display_uri
2204
+ display_uri = self.normalize
2205
+ display_uri.instance_variable_set("@host",
2206
+ ::Addressable::IDNA.to_unicode(display_uri.host))
2207
+ return display_uri
2208
+ end
2209
+
2210
+ ##
2211
+ # Returns <tt>true</tt> if the URI objects are equal. This method
2212
+ # normalizes both URIs before doing the comparison, and allows comparison
2213
+ # against <tt>Strings</tt>.
2214
+ #
2215
+ # @param [Object] uri The URI to compare.
2216
+ #
2217
+ # @return [TrueClass, FalseClass]
2218
+ # <tt>true</tt> if the URIs are equivalent, <tt>false</tt> otherwise.
2219
+ def ===(uri)
2220
+ if uri.respond_to?(:normalize)
2221
+ uri_string = uri.normalize.to_s
2222
+ else
2223
+ begin
2224
+ uri_string = ::Addressable::URI.parse(uri).normalize.to_s
2225
+ rescue InvalidURIError, TypeError
2226
+ return false
2227
+ end
2228
+ end
2229
+ return self.normalize.to_s == uri_string
2230
+ end
2231
+
2232
+ ##
2233
+ # Returns <tt>true</tt> if the URI objects are equal. This method
2234
+ # normalizes both URIs before doing the comparison.
2235
+ #
2236
+ # @param [Object] uri The URI to compare.
2237
+ #
2238
+ # @return [TrueClass, FalseClass]
2239
+ # <tt>true</tt> if the URIs are equivalent, <tt>false</tt> otherwise.
2240
+ def ==(uri)
2241
+ return false unless uri.kind_of?(self.class)
2242
+ return self.normalize.to_s == uri.normalize.to_s
2243
+ end
2244
+
2245
+ ##
2246
+ # Returns <tt>true</tt> if the URI objects are equal. This method
2247
+ # does NOT normalize either URI before doing the comparison.
2248
+ #
2249
+ # @param [Object] uri The URI to compare.
2250
+ #
2251
+ # @return [TrueClass, FalseClass]
2252
+ # <tt>true</tt> if the URIs are equivalent, <tt>false</tt> otherwise.
2253
+ def eql?(uri)
2254
+ return false unless uri.kind_of?(self.class)
2255
+ return self.to_s == uri.to_s
2256
+ end
2257
+
2258
+ ##
2259
+ # A hash value that will make a URI equivalent to its normalized
2260
+ # form.
2261
+ #
2262
+ # @return [Integer] A hash of the URI.
2263
+ def hash
2264
+ return (self.normalize.to_s.hash * -1)
2265
+ end
2266
+
2267
+ ##
2268
+ # Clones the URI object.
2269
+ #
2270
+ # @return [Addressable::URI] The cloned URI.
2271
+ def dup
2272
+ duplicated_uri = Addressable::URI.new(
2273
+ :scheme => self.scheme ? self.scheme.dup : nil,
2274
+ :user => self.user ? self.user.dup : nil,
2275
+ :password => self.password ? self.password.dup : nil,
2276
+ :host => self.host ? self.host.dup : nil,
2277
+ :port => self.port,
2278
+ :path => self.path ? self.path.dup : nil,
2279
+ :query => self.query ? self.query.dup : nil,
2280
+ :fragment => self.fragment ? self.fragment.dup : nil
2281
+ )
2282
+ return duplicated_uri
2283
+ end
2284
+
2285
+ ##
2286
+ # Omits components from a URI.
2287
+ #
2288
+ # @param [Symbol] *components The components to be omitted.
2289
+ #
2290
+ # @return [Addressable::URI] The URI with components omitted.
2291
+ #
2292
+ # @example
2293
+ # uri = Addressable::URI.parse("http://example.com/path?query")
2294
+ # #=> #<Addressable::URI:0xcc5e7a URI:http://example.com/path?query>
2295
+ # uri.omit(:scheme, :authority)
2296
+ # #=> #<Addressable::URI:0xcc4d86 URI:/path?query>
2297
+ def omit(*components)
2298
+ invalid_components = components - [
2299
+ :scheme, :user, :password, :userinfo, :host, :port, :authority,
2300
+ :path, :query, :fragment
2301
+ ]
2302
+ unless invalid_components.empty?
2303
+ raise ArgumentError,
2304
+ "Invalid component names: #{invalid_components.inspect}."
2305
+ end
2306
+ duplicated_uri = self.dup
2307
+ duplicated_uri.validation_deferred = true
2308
+ components.each do |component|
2309
+ duplicated_uri.send((component.to_s + "=").to_sym, nil)
2310
+ end
2311
+ duplicated_uri.validation_deferred = false
2312
+ duplicated_uri
2313
+ end
2314
+
2315
+ ##
2316
+ # Destructive form of omit.
2317
+ #
2318
+ # @param [Symbol] *components The components to be omitted.
2319
+ #
2320
+ # @return [Addressable::URI] The URI with components omitted.
2321
+ #
2322
+ # @see Addressable::URI#omit
2323
+ def omit!(*components)
2324
+ replace_self(self.omit(*components))
2325
+ end
2326
+
2327
+ ##
2328
+ # Converts the URI to a <tt>String</tt>.
2329
+ #
2330
+ # @return [String] The URI's <tt>String</tt> representation.
2331
+ def to_s
2332
+ uri_string = ""
2333
+ uri_string << "#{self.scheme}:" if self.scheme != nil
2334
+ uri_string << "//#{self.authority}" if self.authority != nil
2335
+ uri_string << self.path.to_s
2336
+ uri_string << "?#{self.query}" if self.query != nil
2337
+ uri_string << "##{self.fragment}" if self.fragment != nil
2338
+ if uri_string.respond_to?(:force_encoding)
2339
+ uri_string.force_encoding(Encoding::UTF_8)
2340
+ end
2341
+ return uri_string
2342
+ end
2343
+
2344
+ ##
2345
+ # URI's are glorified <tt>Strings</tt>. Allow implicit conversion.
2346
+ alias_method :to_str, :to_s
2347
+
2348
+ ##
2349
+ # Returns a Hash of the URI components.
2350
+ #
2351
+ # @return [Hash] The URI as a <tt>Hash</tt> of components.
2352
+ def to_hash
2353
+ return {
2354
+ :scheme => self.scheme,
2355
+ :user => self.user,
2356
+ :password => self.password,
2357
+ :host => self.host,
2358
+ :port => self.port,
2359
+ :path => self.path,
2360
+ :query => self.query,
2361
+ :fragment => self.fragment
2362
+ }
2363
+ end
2364
+
2365
+ ##
2366
+ # Returns a <tt>String</tt> representation of the URI object's state.
2367
+ #
2368
+ # @return [String] The URI object's state, as a <tt>String</tt>.
2369
+ def inspect
2370
+ sprintf("#<%s:%#0x URI:%s>", self.class.to_s, self.object_id, self.to_s)
2371
+ end
2372
+
2373
+ ##
2374
+ # If URI validation needs to be disabled, this can be set to true.
2375
+ #
2376
+ # @return [TrueClass, FalseClass]
2377
+ # <tt>true</tt> if validation has been deferred,
2378
+ # <tt>false</tt> otherwise.
2379
+ def validation_deferred
2380
+ @validation_deferred ||= false
2381
+ end
2382
+
2383
+ ##
2384
+ # If URI validation needs to be disabled, this can be set to true.
2385
+ #
2386
+ # @param [TrueClass, FalseClass] new_validation_deferred
2387
+ # <tt>true</tt> if validation will be deferred,
2388
+ # <tt>false</tt> otherwise.
2389
+ def validation_deferred=(new_validation_deferred)
2390
+ @validation_deferred = new_validation_deferred
2391
+ validate unless @validation_deferred
2392
+ end
2393
+
2394
+ private
2395
+ ##
2396
+ # Resolves paths to their simplest form.
2397
+ #
2398
+ # @param [String] path The path to normalize.
2399
+ #
2400
+ # @return [String] The normalized path.
2401
+ def self.normalize_path(path)
2402
+ # Section 5.2.4 of RFC 3986
2403
+
2404
+ return nil if path.nil?
2405
+ normalized_path = path.dup
2406
+ previous_state = normalized_path.dup
2407
+ begin
2408
+ previous_state = normalized_path.dup
2409
+ normalized_path.gsub!(/\/\.\//, "/")
2410
+ normalized_path.gsub!(/\/\.$/, "/")
2411
+ parent = normalized_path[/\/([^\/]+)\/\.\.\//, 1]
2412
+ if parent != "." && parent != ".."
2413
+ normalized_path.gsub!(/\/#{parent}\/\.\.\//, "/")
2414
+ end
2415
+ parent = normalized_path[/\/([^\/]+)\/\.\.$/, 1]
2416
+ if parent != "." && parent != ".."
2417
+ normalized_path.gsub!(/\/#{parent}\/\.\.$/, "/")
2418
+ end
2419
+ normalized_path.gsub!(/^\.\.?\/?/, "")
2420
+ normalized_path.gsub!(/^\/\.\.?\//, "/")
2421
+ end until previous_state == normalized_path
2422
+ return normalized_path
2423
+ end
2424
+
2425
+ ##
2426
+ # Ensures that the URI is valid.
2427
+ def validate
2428
+ return if self.validation_deferred
2429
+ if self.scheme != nil &&
2430
+ (self.host == nil || self.host == "") &&
2431
+ (self.path == nil || self.path == "")
2432
+ raise InvalidURIError,
2433
+ "Absolute URI missing hierarchical segment: '#{self.to_s}'"
2434
+ end
2435
+ if self.host == nil
2436
+ if self.port != nil ||
2437
+ self.user != nil ||
2438
+ self.password != nil
2439
+ raise InvalidURIError, "Hostname not supplied: '#{self.to_s}'"
2440
+ end
2441
+ end
2442
+ return nil
2443
+ end
2444
+
2445
+ ##
2446
+ # Replaces the internal state of self with the specified URI's state.
2447
+ # Used in destructive operations to avoid massive code repetition.
2448
+ #
2449
+ # @param [Addressable::URI] uri The URI to replace <tt>self</tt> with.
2450
+ #
2451
+ # @return [Addressable::URI] <tt>self</tt>.
2452
+ def replace_self(uri)
2453
+ # Reset dependant values
2454
+ instance_variables.each do |var|
2455
+ instance_variable_set(var, nil)
2456
+ end
2457
+
2458
+ @scheme = uri.scheme
2459
+ @user = uri.user
2460
+ @password = uri.password
2461
+ @host = uri.host
2462
+ @port = uri.port
2463
+ @path = uri.path
2464
+ @query = uri.query
2465
+ @fragment = uri.fragment
2466
+ return self
2467
+ end
2468
+ end
2469
+ end