addressable 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,24 @@
1
+ <?xml version="1.0" encoding="iso-8859-1"?>
2
+ <!DOCTYPE html
3
+ PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
4
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
5
+
6
+ <!--
7
+
8
+ Addressable -- URI Implementation
9
+
10
+ -->
11
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
12
+ <head>
13
+ <title>Addressable -- URI Implementation</title>
14
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
15
+ </head>
16
+ <frameset rows="20%, 80%">
17
+ <frameset cols="25%,35%,45%">
18
+ <frame src="fr_file_index.html" title="Files" name="Files" />
19
+ <frame src="fr_class_index.html" name="Classes" />
20
+ <frame src="fr_method_index.html" name="Methods" />
21
+ </frameset>
22
+ <frame src="files/README.html" name="docwin" />
23
+ </frameset>
24
+ </html>
@@ -0,0 +1,208 @@
1
+
2
+ body {
3
+ font-family: Verdana,Arial,Helvetica,sans-serif;
4
+ font-size: 90%;
5
+ margin: 0;
6
+ margin-left: 40px;
7
+ padding: 0;
8
+ background: white;
9
+ }
10
+
11
+ h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; }
12
+ h1 { font-size: 150%; }
13
+ h2,h3,h4 { margin-top: 1em; }
14
+
15
+ a { background: #eef; color: #039; text-decoration: none; }
16
+ a:hover { background: #039; color: #eef; }
17
+
18
+ /* Override the base stylesheet's Anchor inside a table cell */
19
+ td > a {
20
+ background: transparent;
21
+ color: #039;
22
+ text-decoration: none;
23
+ }
24
+
25
+ /* and inside a section title */
26
+ .section-title > a {
27
+ background: transparent;
28
+ color: #eee;
29
+ text-decoration: none;
30
+ }
31
+
32
+ /* === Structural elements =================================== */
33
+
34
+ div#index {
35
+ margin: 0;
36
+ margin-left: -40px;
37
+ padding: 0;
38
+ font-size: 90%;
39
+ }
40
+
41
+
42
+ div#index a {
43
+ margin-left: 0.7em;
44
+ }
45
+
46
+ div#index .section-bar {
47
+ margin-left: 0px;
48
+ padding-left: 0.7em;
49
+ background: #ccc;
50
+ font-size: small;
51
+ }
52
+
53
+
54
+ div#classHeader, div#fileHeader {
55
+ width: auto;
56
+ color: white;
57
+ padding: 0.5em 1.5em 0.5em 1.5em;
58
+ margin: 0;
59
+ margin-left: -40px;
60
+ border-bottom: 3px solid #006;
61
+ }
62
+
63
+ div#classHeader a, div#fileHeader a {
64
+ background: inherit;
65
+ color: white;
66
+ }
67
+
68
+ div#classHeader td, div#fileHeader td {
69
+ background: inherit;
70
+ color: white;
71
+ }
72
+
73
+
74
+ div#fileHeader {
75
+ background: #057;
76
+ }
77
+
78
+ div#classHeader {
79
+ background: #048;
80
+ }
81
+
82
+
83
+ .class-name-in-header {
84
+ font-size: 180%;
85
+ font-weight: bold;
86
+ }
87
+
88
+
89
+ div#bodyContent {
90
+ padding: 0 1.5em 0 1.5em;
91
+ }
92
+
93
+ div#description {
94
+ padding: 0.5em 1.5em;
95
+ background: #efefef;
96
+ border: 1px dotted #999;
97
+ }
98
+
99
+ div#description h1,h2,h3,h4,h5,h6 {
100
+ color: #125;;
101
+ background: transparent;
102
+ }
103
+
104
+ div#validator-badges {
105
+ text-align: center;
106
+ }
107
+ div#validator-badges img { border: 0; }
108
+
109
+ div#copyright {
110
+ color: #333;
111
+ background: #efefef;
112
+ font: 0.75em sans-serif;
113
+ margin-top: 5em;
114
+ margin-bottom: 0;
115
+ padding: 0.5em 2em;
116
+ }
117
+
118
+
119
+ /* === Classes =================================== */
120
+
121
+ table.header-table {
122
+ color: white;
123
+ font-size: small;
124
+ }
125
+
126
+ .type-note {
127
+ font-size: small;
128
+ color: #DEDEDE;
129
+ }
130
+
131
+ .xxsection-bar {
132
+ background: #eee;
133
+ color: #333;
134
+ padding: 3px;
135
+ }
136
+
137
+ .section-bar {
138
+ color: #333;
139
+ border-bottom: 1px solid #999;
140
+ margin-left: -20px;
141
+ }
142
+
143
+
144
+ .section-title {
145
+ background: #79a;
146
+ color: #eee;
147
+ padding: 3px;
148
+ margin-top: 2em;
149
+ margin-left: -30px;
150
+ border: 1px solid #999;
151
+ }
152
+
153
+ .top-aligned-row { vertical-align: top }
154
+ .bottom-aligned-row { vertical-align: bottom }
155
+
156
+ /* --- Context section classes ----------------------- */
157
+
158
+ .context-row { }
159
+ .context-item-name { font-family: monospace; font-weight: bold; color: black; }
160
+ .context-item-value { font-size: small; color: #448; }
161
+ .context-item-desc { color: #333; padding-left: 2em; }
162
+
163
+ /* --- Method classes -------------------------- */
164
+ .method-detail {
165
+ background: #efefef;
166
+ padding: 0;
167
+ margin-top: 0.5em;
168
+ margin-bottom: 1em;
169
+ border: 1px dotted #ccc;
170
+ }
171
+ .method-heading {
172
+ color: black;
173
+ background: #ccc;
174
+ border-bottom: 1px solid #666;
175
+ padding: 0.2em 0.5em 0 0.5em;
176
+ }
177
+ .method-signature { color: black; background: inherit; }
178
+ .method-name { font-weight: bold; }
179
+ .method-args { font-style: italic; }
180
+ .method-description { padding: 0 0.5em 0 0.5em; }
181
+
182
+ /* --- Source code sections -------------------- */
183
+
184
+ a.source-toggle { font-size: 90%; }
185
+ div.method-source-code {
186
+ background: #262626;
187
+ color: #ffdead;
188
+ margin: 1em;
189
+ padding: 0.5em;
190
+ border: 1px dashed #999;
191
+ overflow: hidden;
192
+ }
193
+
194
+ div.method-source-code pre { color: #ffdead; overflow: hidden; }
195
+
196
+ /* --- Ruby keyword styles --------------------- */
197
+
198
+ .standalone-code { background: #221111; color: #ffdead; overflow: hidden; }
199
+
200
+ .ruby-constant { color: #7fffd4; background: transparent; }
201
+ .ruby-keyword { color: #00ffff; background: transparent; }
202
+ .ruby-ivar { color: #eedd82; background: transparent; }
203
+ .ruby-operator { color: #00ffee; background: transparent; }
204
+ .ruby-identifier { color: #ffdead; background: transparent; }
205
+ .ruby-node { color: #ffa07a; background: transparent; }
206
+ .ruby-comment { color: #b22222; font-weight: bold; background: transparent; }
207
+ .ruby-regexp { color: #ffa07a; background: transparent; }
208
+ .ruby-value { color: #7fffd4; background: transparent; }
@@ -0,0 +1,1092 @@
1
+ #--
2
+ # Addressable, Copyright (c) 2006-2007 Bob Aman
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ module Addressable
25
+ # This is an implementation of a URI parser based on RFC 3986.
26
+ class URI
27
+ # Raised if something other than a uri is supplied.
28
+ class InvalidURIError < StandardError
29
+ end
30
+
31
+ # Raised if an invalid method option is supplied.
32
+ class InvalidOptionError < StandardError
33
+ end
34
+
35
+ # Raised if an invalid method option is supplied.
36
+ class InvalidTemplateValue < StandardError
37
+ end
38
+
39
+ module CharacterClasses
40
+ ALPHA = "a-zA-Z"
41
+ DIGIT = "0-9"
42
+ GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@"
43
+ SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\="
44
+ RESERVED = GEN_DELIMS + SUB_DELIMS
45
+ UNRESERVED = ALPHA + DIGIT + "\\-\\.\\_\\~"
46
+ PCHAR = UNRESERVED + SUB_DELIMS + "\\:\\@"
47
+ SCHEME = ALPHA + DIGIT + "\\-\\+\\."
48
+ AUTHORITY = PCHAR
49
+ PATH = PCHAR + "\\/"
50
+ QUERY = PCHAR + "\\/\\?"
51
+ FRAGMENT = PCHAR + "\\/\\?"
52
+ end
53
+
54
+ # Returns a URI object based on the parsed string.
55
+ def self.parse(uri_string)
56
+ return nil if uri_string.nil?
57
+
58
+ # If a URI object is passed, just return itself.
59
+ return uri_string if uri_string.kind_of?(self)
60
+
61
+ # If a URI object of the Ruby standard library variety is passed,
62
+ # convert it to a string, then parse the string.
63
+ if uri_string.class.name =~ /^URI::/
64
+ uri_string = uri_string.to_s
65
+ end
66
+
67
+ uri_regex =
68
+ /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
69
+ scan = uri_string.scan(uri_regex)
70
+ fragments = scan[0]
71
+ return nil if fragments.nil?
72
+ scheme = fragments[1]
73
+ authority = fragments[3]
74
+ path = fragments[4]
75
+ query = fragments[6]
76
+ fragment = fragments[8]
77
+ userinfo = nil
78
+ user = nil
79
+ password = nil
80
+ host = nil
81
+ port = nil
82
+ if authority != nil
83
+ userinfo = authority.scan(/^([^\[\]]*)@/).flatten[0]
84
+ if userinfo != nil
85
+ user = userinfo.strip.scan(/^([^:]*):?/).flatten[0]
86
+ password = userinfo.strip.scan(/:(.*)$/).flatten[0]
87
+ end
88
+ host = authority.gsub(/^([^\[\]]*)@/, "").gsub(/:([^:@\[\]]*?)$/, "")
89
+ port = authority.scan(/:([^:@\[\]]*?)$/).flatten[0]
90
+ end
91
+ if port.nil? || port == ""
92
+ port = nil
93
+ end
94
+
95
+ # WARNING: Not standards-compliant, but follows the theme
96
+ # of Postel's law:
97
+ #
98
+ # Special exception for dealing with the retarded idea of the
99
+ # feed pseudo-protocol. Without this exception, the parser will read
100
+ # the URI as having a blank port number, instead of as having a second
101
+ # URI embedded within. This exception translates these broken URIs
102
+ # and instead treats the inner URI as opaque.
103
+ if scheme == "feed" && host == "http"
104
+ userinfo = nil
105
+ user = nil
106
+ password = nil
107
+ host = nil
108
+ port = nil
109
+ path = authority + path
110
+ end
111
+
112
+ return Addressable::URI.new(
113
+ scheme, user, password, host, port, path, query, fragment)
114
+ end
115
+
116
+ # Converts a path to a file protocol URI. If the path supplied is
117
+ # relative, it will be returned as a relative URI. If the path supplied
118
+ # is actually a URI, it will return the parsed URI.
119
+ def self.convert_path(path)
120
+ return nil if path.nil?
121
+
122
+ converted_uri = path.strip
123
+ if converted_uri.length > 0 && converted_uri[0..0] == "/"
124
+ converted_uri = "file://" + converted_uri
125
+ end
126
+ if converted_uri.length > 0 &&
127
+ converted_uri.scan(/^[a-zA-Z]:[\\\/]/).size > 0
128
+ converted_uri = "file:///" + converted_uri
129
+ end
130
+ converted_uri.gsub!(/^file:\/*/i, "file:///")
131
+ if converted_uri =~ /^file:/i
132
+ # Adjust windows-style uris
133
+ converted_uri.gsub!(/^file:\/\/\/([a-zA-Z])\|/i, 'file:///\1:')
134
+ converted_uri.gsub!(/\\/, '/')
135
+ converted_uri = self.parse(converted_uri).normalize
136
+ if File.exists?(converted_uri.path) &&
137
+ File.stat(converted_uri.path).directory?
138
+ converted_uri.path.gsub!(/\/$/, "")
139
+ converted_uri.path = converted_uri.path + '/'
140
+ end
141
+ else
142
+ converted_uri = self.parse(converted_uri)
143
+ end
144
+
145
+ return converted_uri
146
+ end
147
+
148
+ # Expands a URI template into a full URI.
149
+ #
150
+ # An optional processor object may be supplied. The object should
151
+ # respond to either the :validate or :transform messages or both.
152
+ # Both the :validate and :transform methods should take two parameters:
153
+ # :name and :value. The :validate method should return true or false;
154
+ # true if the value of the variable is valid, false otherwise. The
155
+ # :transform method should return the transformed variable value as a
156
+ # string.
157
+ #
158
+ # An example:
159
+ #
160
+ # class ExampleProcessor
161
+ # def self.validate(name, value)
162
+ # return !!(value =~ /^[\w ]+$/) if name == "query"
163
+ # return true
164
+ # end
165
+ #
166
+ # def self.transform(name, value)
167
+ # return value.gsub(/ /, "+") if name == "query"
168
+ # return value
169
+ # end
170
+ # end
171
+ #
172
+ # Addressable::URI.expand_template(
173
+ # "http://example.com/search/{query}/",
174
+ # {"query" => "an example search query"},
175
+ # ExampleProcessor).to_s
176
+ # => "http://example.com/search/an+example+search+query/"
177
+ def self.expand_template(pattern, mapping, processor=nil)
178
+ result = pattern.dup
179
+ for name, value in mapping
180
+ transformed_value = value
181
+ if processor != nil
182
+ if processor.respond_to?(:validate)
183
+ if !processor.validate(name, value)
184
+ raise InvalidTemplateValue,
185
+ "(#{name}, #{value}) is an invalid template value."
186
+ end
187
+ end
188
+ if processor.respond_to?(:transform)
189
+ transformed_value = processor.transform(name, value)
190
+ end
191
+ end
192
+
193
+ # Handle percent escaping
194
+ transformed_value = self.encode_segment(transformed_value,
195
+ Addressable::URI::CharacterClasses::RESERVED +
196
+ Addressable::URI::CharacterClasses::UNRESERVED)
197
+
198
+ result.gsub!(/\{#{Regexp.escape(name)}\}/, transformed_value)
199
+ end
200
+ result.gsub!(/\{[#{CharacterClasses::UNRESERVED}]+\}/, "")
201
+ return Addressable::URI.parse(result)
202
+ end
203
+
204
+ # Joins several uris together.
205
+ def self.join(*uris)
206
+ uri_objects = uris.collect do |uri|
207
+ uri.kind_of?(self) ? uri : self.parse(uri.to_s)
208
+ end
209
+ result = uri_objects.shift.dup
210
+ for uri in uri_objects
211
+ result.merge!(uri)
212
+ end
213
+ return result
214
+ end
215
+
216
+ # Percent encodes a URI segment. Returns a string. Takes an optional
217
+ # character class parameter, which should be specified as a string
218
+ # containing a regular expression character class (not including the
219
+ # surrounding square brackets). The character class parameter defaults
220
+ # to the reserved plus unreserved character classes specified in
221
+ # RFC 3986. Usage of the constants within the CharacterClasses module is
222
+ # highly recommended when using this method.
223
+ #
224
+ # An example:
225
+ #
226
+ # Addressable::URI.escape_segment("simple-example", "b-zB-Z0-9")
227
+ # => "simple%2Dex%61mple"
228
+ def self.encode_segment(segment, character_class=
229
+ Addressable::URI::CharacterClasses::RESERVED +
230
+ Addressable::URI::CharacterClasses::UNRESERVED)
231
+ return nil if segment.nil?
232
+ return segment.gsub(
233
+ /[^#{character_class}]/
234
+ ) do |sequence|
235
+ ("%" + sequence.unpack('C')[0].to_s(16).upcase)
236
+ end
237
+ end
238
+
239
+ # Unencodes any percent encoded characters within a URI segment.
240
+ # Returns a string.
241
+ def self.unencode_segment(segment)
242
+ return nil if segment.nil?
243
+ return segment.to_s.gsub(/%[0-9a-f]{2}/i) do |sequence|
244
+ sequence[1..3].to_i(16).chr
245
+ end
246
+ end
247
+
248
+ # Percent encodes any special characters in the URI. This method does
249
+ # not take IRIs or IDNs into account.
250
+ def self.encode(uri)
251
+ uri_object = uri.kind_of?(self) ? uri : self.parse(uri.to_s)
252
+ return Addressable::URI.new(
253
+ self.encode_segment(uri_object.scheme,
254
+ Addressable::URI::CharacterClasses::SCHEME),
255
+ self.encode_segment(uri_object.user,
256
+ Addressable::URI::CharacterClasses::AUTHORITY),
257
+ self.encode_segment(uri_object.password,
258
+ Addressable::URI::CharacterClasses::AUTHORITY),
259
+ self.encode_segment(uri_object.host,
260
+ Addressable::URI::CharacterClasses::AUTHORITY),
261
+ self.encode_segment(uri_object.specified_port,
262
+ Addressable::URI::CharacterClasses::AUTHORITY),
263
+ self.encode_segment(uri_object.path,
264
+ Addressable::URI::CharacterClasses::PATH),
265
+ self.encode_segment(uri_object.query,
266
+ Addressable::URI::CharacterClasses::QUERY),
267
+ self.encode_segment(uri_object.fragment,
268
+ Addressable::URI::CharacterClasses::FRAGMENT)
269
+ ).to_s
270
+ end
271
+
272
+ class << self
273
+ alias_method :escape, :encode
274
+ end
275
+
276
+ # Normalizes the encoding of a URI. Characters within a hostname are
277
+ # not percent encoded to allow for internationalized domain names.
278
+ def self.normalized_encode(uri)
279
+ uri_object = uri.kind_of?(self) ? uri : self.parse(uri.to_s)
280
+ segments = {
281
+ :scheme => self.unencode_segment(uri_object.scheme),
282
+ :user => self.unencode_segment(uri_object.user),
283
+ :password => self.unencode_segment(uri_object.password),
284
+ :host => self.unencode_segment(uri_object.host),
285
+ :port => self.unencode_segment(uri_object.specified_port),
286
+ :path => self.unencode_segment(uri_object.path),
287
+ :query => self.unencode_segment(uri_object.query),
288
+ :fragment => self.unencode_segment(uri_object.fragment)
289
+ }
290
+ if URI::IDNA.send(:use_libidn?)
291
+ segments.each do |key, value|
292
+ if value != nil
293
+ segments[key] = IDN::Stringprep.nfkc_normalize(value.to_s)
294
+ end
295
+ end
296
+ end
297
+ return Addressable::URI.new(
298
+ self.encode_segment(segments[:scheme],
299
+ Addressable::URI::CharacterClasses::SCHEME),
300
+ self.encode_segment(segments[:user],
301
+ Addressable::URI::CharacterClasses::AUTHORITY),
302
+ self.encode_segment(segments[:password],
303
+ Addressable::URI::CharacterClasses::AUTHORITY),
304
+ segments[:host],
305
+ segments[:port],
306
+ self.encode_segment(segments[:path],
307
+ Addressable::URI::CharacterClasses::PATH),
308
+ self.encode_segment(segments[:query],
309
+ Addressable::URI::CharacterClasses::QUERY),
310
+ self.encode_segment(segments[:fragment],
311
+ Addressable::URI::CharacterClasses::FRAGMENT)
312
+ ).to_s
313
+ end
314
+
315
+ # Extracts uris from an arbitrary body of text.
316
+ def self.extract(text, options={})
317
+ defaults = {:base => nil, :parse => false}
318
+ options = defaults.merge(options)
319
+ raise InvalidOptionError unless (options.keys - defaults.keys).empty?
320
+ # This regular expression needs to be less forgiving or else it would
321
+ # match virtually all text. Which isn't exactly what we're going for.
322
+ extract_regex = /((([a-z\+]+):)[^ \n\<\>\"\\]+[\w\/])/
323
+ extracted_uris =
324
+ text.scan(extract_regex).collect { |match| match[0] }
325
+ sgml_extract_regex = /<[^>]+href=\"([^\"]+?)\"[^>]*>/
326
+ sgml_extracted_uris =
327
+ text.scan(sgml_extract_regex).collect { |match| match[0] }
328
+ extracted_uris.concat(sgml_extracted_uris - extracted_uris)
329
+ textile_extract_regex = /\".+?\":([^ ]+\/[^ ]+)[ \,\.\;\:\?\!\<\>\"]/i
330
+ textile_extracted_uris =
331
+ text.scan(textile_extract_regex).collect { |match| match[0] }
332
+ extracted_uris.concat(textile_extracted_uris - extracted_uris)
333
+ parsed_uris = []
334
+ base_uri = nil
335
+ if options[:base] != nil
336
+ base_uri = options[:base] if options[:base].kind_of?(self)
337
+ base_uri = self.parse(options[:base].to_s) if base_uri == nil
338
+ end
339
+ for uri_string in extracted_uris
340
+ begin
341
+ if base_uri == nil
342
+ parsed_uris << self.parse(uri_string)
343
+ else
344
+ parsed_uris << (base_uri + self.parse(uri_string))
345
+ end
346
+ rescue Exception
347
+ nil
348
+ end
349
+ end
350
+ parsed_uris.reject! do |uri|
351
+ (uri.scheme =~ /T\d+/ ||
352
+ uri.scheme == "xmlns" ||
353
+ uri.scheme == "xml" ||
354
+ uri.scheme == "thr" ||
355
+ uri.scheme == "this" ||
356
+ uri.scheme == "float" ||
357
+ uri.scheme == "user" ||
358
+ uri.scheme == "username" ||
359
+ uri.scheme == "out")
360
+ end
361
+ if options[:parse]
362
+ return parsed_uris
363
+ else
364
+ return parsed_uris.collect { |uri| uri.to_s }
365
+ end
366
+ end
367
+
368
+ # Creates a new uri object from component parts. Passing nil for
369
+ # any of these parameters is acceptable.
370
+ def initialize(scheme, user, password, host, port, path, query, fragment)
371
+ @scheme = scheme
372
+ @scheme = nil if @scheme.to_s.strip == ""
373
+ @user = user
374
+ @password = password
375
+ @host = host
376
+ @specified_port = port.to_s
377
+ @port = port
378
+ @port = @port.to_s if @port.kind_of?(Fixnum)
379
+ if @port != nil && !(@port =~ /^\d+$/)
380
+ raise InvalidURIError,
381
+ "Invalid port number: #{@port.inspect}"
382
+ end
383
+ @port = @port.to_i
384
+ @port = nil if @port == 0
385
+ @path = path
386
+ @query = query
387
+ @fragment = fragment
388
+
389
+ validate()
390
+ end
391
+
392
+ # Returns the scheme (protocol) for this URI.
393
+ def scheme
394
+ return @scheme
395
+ end
396
+
397
+ # Sets the scheme (protocol for this URI.)
398
+ def scheme=(new_scheme)
399
+ @scheme = new_scheme
400
+ end
401
+
402
+ # Returns the user for this URI.
403
+ def user
404
+ return @user
405
+ end
406
+
407
+ # Sets the user for this URI.
408
+ def user=(new_user)
409
+ @user = new_user
410
+
411
+ # You can't have a nil user with a non-nil password
412
+ if @password != nil
413
+ @user = "" if @user.nil?
414
+ end
415
+
416
+ # Reset dependant values
417
+ @userinfo = nil
418
+ @authority = nil
419
+
420
+ # Ensure we haven't created an invalid URI
421
+ validate()
422
+ end
423
+
424
+ # Returns the password for this URI.
425
+ def password
426
+ return @password
427
+ end
428
+
429
+ # Sets the password for this URI.
430
+ def password=(new_password)
431
+ @password = new_password
432
+
433
+ # You can't have a nil user with a non-nil password
434
+ if @password != nil
435
+ @user = "" if @user.nil?
436
+ end
437
+
438
+ # Reset dependant values
439
+ @userinfo = nil
440
+ @authority = nil
441
+
442
+ # Ensure we haven't created an invalid URI
443
+ validate()
444
+ end
445
+
446
+ # Returns the username and password segment of this URI.
447
+ def userinfo
448
+ if !defined?(@userinfo) || @userinfo.nil?
449
+ current_user = self.user
450
+ current_password = self.password
451
+ if current_user == nil && current_password == nil
452
+ @userinfo = nil
453
+ elsif current_user != nil && current_password == nil
454
+ @userinfo = "#{current_user}"
455
+ elsif current_user != nil && current_password != nil
456
+ @userinfo = "#{current_user}:#{current_password}"
457
+ end
458
+ end
459
+ return @userinfo
460
+ end
461
+
462
+ # Sets the username and password segment of this URI.
463
+ def userinfo=(new_userinfo)
464
+ new_user = new_userinfo.to_s.strip.scan(/^(.*):/).flatten[0]
465
+ new_password = new_userinfo.to_s.strip.scan(/:(.*)$/).flatten[0]
466
+
467
+ # Password assigned first to ensure validity in case of nil
468
+ self.password = new_password
469
+ self.user = new_user
470
+
471
+ # Reset dependant values
472
+ @authority = nil
473
+
474
+ # Ensure we haven't created an invalid URI
475
+ validate()
476
+ end
477
+
478
+ # Returns the host for this URI.
479
+ def host
480
+ return @host
481
+ end
482
+
483
+ # Sets the host for this URI.
484
+ def host=(new_host)
485
+ @host = new_host
486
+
487
+ # Reset dependant values
488
+ @authority = nil
489
+
490
+ # Ensure we haven't created an invalid URI
491
+ validate()
492
+ end
493
+
494
+ # Returns the authority segment of this URI.
495
+ def authority
496
+ if !defined?(@authority) || @authority.nil?
497
+ return nil if self.host.nil?
498
+ @authority = ""
499
+ if self.userinfo != nil
500
+ @authority << "#{self.userinfo}@"
501
+ end
502
+ @authority << self.host
503
+ if self.specified_port != nil
504
+ @authority << ":#{self.specified_port}"
505
+ end
506
+ end
507
+ return @authority
508
+ end
509
+
510
+ # Sets the authority segment of this URI.
511
+ def authority=(new_authority)
512
+ if new_authority != nil
513
+ new_userinfo = new_authority.scan(/^([^\[\]]*)@/).flatten[0]
514
+ if new_userinfo != nil
515
+ new_user = new_userinfo.strip.scan(/^([^:]*):?/).flatten[0]
516
+ new_password = new_userinfo.strip.scan(/:(.*)$/).flatten[0]
517
+ end
518
+ new_host =
519
+ new_authority.gsub(/^([^\[\]]*)@/, "").gsub(/:([^:@\[\]]*?)$/, "")
520
+ new_port =
521
+ new_authority.scan(/:([^:@\[\]]*?)$/).flatten[0]
522
+ end
523
+ new_port = nil if new_port == ""
524
+
525
+ # Password assigned first to ensure validity in case of nil
526
+ self.password = new_password
527
+ self.user = new_user
528
+ self.host = new_host
529
+
530
+ # Port reset to allow port normalization
531
+ @port = nil
532
+ @specified_port = new_port
533
+
534
+ # Ensure we haven't created an invalid URI
535
+ validate()
536
+ end
537
+
538
+ # Returns an array of known ip-based schemes. These schemes typically
539
+ # use a similar URI form:
540
+ # //<user>:<password>@<host>:<port>/<url-path>
541
+ def self.ip_based_schemes
542
+ return self.scheme_mapping.keys
543
+ end
544
+
545
+ # Returns a hash of common IP-based schemes and their default port
546
+ # numbers. Adding new schemes to this hash, as necessary, will allow
547
+ # for better URI normalization.
548
+ def self.scheme_mapping
549
+ if !defined?(@protocol_mapping) || @protocol_mapping.nil?
550
+ @protocol_mapping = {
551
+ "http" => 80,
552
+ "https" => 443,
553
+ "ftp" => 21,
554
+ "tftp" => 69,
555
+ "ssh" => 22,
556
+ "svn+ssh" => 22,
557
+ "telnet" => 23,
558
+ "nntp" => 119,
559
+ "gopher" => 70,
560
+ "wais" => 210,
561
+ "ldap" => 389,
562
+ "prospero" => 1525
563
+ }
564
+ end
565
+ return @protocol_mapping
566
+ end
567
+
568
+ # Returns the port number for this URI. This method will normalize to the
569
+ # default port for the URI's scheme if the port isn't explicitly specified
570
+ # in the URI.
571
+ def port
572
+ if @port.to_i == 0
573
+ if self.scheme.nil?
574
+ @port = nil
575
+ else
576
+ @port = self.class.scheme_mapping[self.scheme.strip.downcase]
577
+ end
578
+ return @port
579
+ else
580
+ @port = @port.to_i
581
+ return @port
582
+ end
583
+ end
584
+
585
+ # Sets the port for this URI.
586
+ def port=(new_port)
587
+ @port = new_port.to_s.to_i
588
+ @specified_port = @port
589
+ @authority = nil
590
+ end
591
+
592
+ # Returns the port number that was actually specified in the URI string.
593
+ def specified_port
594
+ port = @specified_port.to_s.to_i
595
+ if port == 0
596
+ return nil
597
+ else
598
+ return port
599
+ end
600
+ end
601
+
602
+ # Returns the path for this URI.
603
+ def path
604
+ return @path
605
+ end
606
+
607
+ # Sets the path for this URI.
608
+ def path=(new_path)
609
+ @path = new_path
610
+ end
611
+
612
+ # Returns the basename, if any, of the file at the path being referenced.
613
+ # Returns nil if there is no path component.
614
+ def basename
615
+ return nil if self.path == nil
616
+ return File.basename(self.path).gsub(/;[^\/]*$/, "")
617
+ end
618
+
619
+ # Returns the extension, if any, of the file at the path being referenced.
620
+ # Returns "" if there is no extension or nil if there is no path
621
+ # component.
622
+ def extname
623
+ return nil if self.path == nil
624
+ return File.extname(self.basename.gsub(/;[^\/]*$/, ""))
625
+ end
626
+
627
+ # Returns the query string for this URI.
628
+ def query
629
+ return @query
630
+ end
631
+
632
+ # Sets the query string for this URI.
633
+ def query=(new_query)
634
+ @query = new_query
635
+ end
636
+
637
+ # Returns the fragment for this URI.
638
+ def fragment
639
+ return @fragment
640
+ end
641
+
642
+ # Sets the fragment for this URI.
643
+ def fragment=(new_fragment)
644
+ @fragment = new_fragment
645
+ end
646
+
647
+ # Returns true if the URI uses an IP-based protocol.
648
+ def ip_based?
649
+ return false if self.scheme.nil?
650
+ return self.class.ip_based_schemes.include?(self.scheme.strip.downcase)
651
+ end
652
+
653
+ # Returns true if this URI is known to be relative.
654
+ def relative?
655
+ return self.scheme.nil?
656
+ end
657
+
658
+ # Returns true if this URI is known to be absolute.
659
+ def absolute?
660
+ return !relative?
661
+ end
662
+
663
+ # Joins two URIs together.
664
+ def +(uri)
665
+ if !uri.kind_of?(self.class)
666
+ uri = URI.parse(uri.to_s)
667
+ end
668
+ if uri.to_s == ""
669
+ return self.dup
670
+ end
671
+
672
+ joined_scheme = nil
673
+ joined_user = nil
674
+ joined_password = nil
675
+ joined_host = nil
676
+ joined_port = nil
677
+ joined_path = nil
678
+ joined_query = nil
679
+ joined_fragment = nil
680
+
681
+ # Section 5.2.2 of RFC 3986
682
+ if uri.scheme != nil
683
+ joined_scheme = uri.scheme
684
+ joined_user = uri.user
685
+ joined_password = uri.password
686
+ joined_host = uri.host
687
+ joined_port = uri.specified_port
688
+ joined_path = self.class.normalize_path(uri.path)
689
+ joined_query = uri.query
690
+ else
691
+ if uri.authority != nil
692
+ joined_user = uri.user
693
+ joined_password = uri.password
694
+ joined_host = uri.host
695
+ joined_port = uri.specified_port
696
+ joined_path = self.class.normalize_path(uri.path)
697
+ joined_query = uri.query
698
+ else
699
+ if uri.path == nil || uri.path == ""
700
+ joined_path = self.path
701
+ if uri.query != nil
702
+ joined_query = uri.query
703
+ else
704
+ joined_query = self.query
705
+ end
706
+ else
707
+ if uri.path[0..0] == "/"
708
+ joined_path = self.class.normalize_path(uri.path)
709
+ else
710
+ base_path = self.path.nil? ? "" : self.path.dup
711
+ base_path = self.class.normalize_path(base_path)
712
+ base_path.gsub!(/\/[^\/]+$/, "/")
713
+ joined_path = self.class.normalize_path(base_path + uri.path)
714
+ end
715
+ joined_query = uri.query
716
+ end
717
+ joined_user = self.user
718
+ joined_password = self.password
719
+ joined_host = self.host
720
+ joined_port = self.specified_port
721
+ end
722
+ joined_scheme = self.scheme
723
+ end
724
+ joined_fragment = uri.fragment
725
+
726
+ return Addressable::URI.new(
727
+ joined_scheme,
728
+ joined_user,
729
+ joined_password,
730
+ joined_host,
731
+ joined_port,
732
+ joined_path,
733
+ joined_query,
734
+ joined_fragment
735
+ )
736
+ end
737
+
738
+ # Merges two URIs together.
739
+ def merge(uri)
740
+ return self + uri
741
+ end
742
+
743
+ # Destructive form of merge.
744
+ def merge!(uri)
745
+ replace_self(self.merge(uri))
746
+ end
747
+
748
+ # Returns the shortest normalized relative form of this URI that uses the
749
+ # supplied URI as a base for resolution. Returns an absolute URI if
750
+ # necessary.
751
+ def route_from(uri)
752
+ uri = uri.kind_of?(self.class) ? uri : self.class.parse(uri.to_s)
753
+ uri = uri.normalize
754
+ normalized_self = self.normalize
755
+ if normalized_self.relative?
756
+ raise ArgumentError, "Expected absolute URI, got: #{self.to_s}"
757
+ end
758
+ if uri.relative?
759
+ raise ArgumentError, "Expected absolute URI, got: #{self.to_s}"
760
+ end
761
+ if normalized_self == uri
762
+ return Addressable::URI.parse("##{normalized_self.fragment}")
763
+ end
764
+ segments = normalized_self.to_h
765
+ if normalized_self.scheme == uri.scheme
766
+ segments[:scheme] = nil
767
+ if normalized_self.authority == uri.authority
768
+ segments[:user] = nil
769
+ segments[:password] = nil
770
+ segments[:host] = nil
771
+ segments[:port] = nil
772
+ if normalized_self.path == uri.path
773
+ segments[:path] = nil
774
+ if normalized_self.query == uri.query
775
+ segments[:query] = nil
776
+ end
777
+ end
778
+ end
779
+ end
780
+ # Avoid network-path references.
781
+ if segments[:scheme] == nil && segments[:host] != nil
782
+ segments[:scheme] = normalized_self.scheme
783
+ end
784
+ return Addressable::URI.new(
785
+ segments[:scheme],
786
+ segments[:user],
787
+ segments[:password],
788
+ segments[:host],
789
+ segments[:port],
790
+ segments[:path],
791
+ segments[:query],
792
+ segments[:fragment]
793
+ )
794
+ end
795
+
796
+ # Returns the shortest normalized relative form of the supplied URI that
797
+ # uses this URI as a base for resolution. Returns an absolute URI if
798
+ # necessary.
799
+ def route_to(uri)
800
+ uri = uri.kind_of?(self.class) ? uri : self.class.parse(uri.to_s)
801
+ return uri.route_from(self)
802
+ end
803
+
804
+ # Returns a normalized URI object.
805
+ #
806
+ # NOTE: This method does not attempt to fully conform to specifications.
807
+ # It exists largely to correct other people's failures to read the
808
+ # specifications, and also to deal with caching issues since several
809
+ # different URIs may represent the same resource and should not be
810
+ # cached multiple times.
811
+ def normalize
812
+ normalized_scheme = nil
813
+ normalized_scheme = self.scheme.strip.downcase if self.scheme != nil
814
+ normalized_scheme = "svn+ssh" if normalized_scheme == "ssh+svn"
815
+ if normalized_scheme == "feed"
816
+ if self.to_s =~ /^feed:\/*http:\/*/
817
+ return self.class.parse(
818
+ self.to_s.scan(/^feed:\/*(http:\/*.*)/).flatten[0]).normalize
819
+ end
820
+ end
821
+ normalized_user = nil
822
+ normalized_user = self.user.strip if self.user != nil
823
+ normalized_password = nil
824
+ normalized_password = self.password.strip if self.password != nil
825
+ normalized_host = nil
826
+ normalized_host = self.host.strip.downcase if self.host != nil
827
+ if normalized_host != nil
828
+ begin
829
+ normalized_host = URI::IDNA.to_ascii(normalized_host)
830
+ rescue Exception
831
+ nil
832
+ end
833
+ end
834
+
835
+ normalized_port = self.port
836
+ if self.class.scheme_mapping[normalized_scheme] == normalized_port
837
+ normalized_port = nil
838
+ end
839
+ normalized_path = nil
840
+ normalized_path = self.path.strip if self.path != nil
841
+ if normalized_path == nil &&
842
+ normalized_scheme != nil &&
843
+ normalized_host != nil
844
+ normalized_path = "/"
845
+ end
846
+ if normalized_path != nil
847
+ normalized_path = self.class.normalize_path(normalized_path)
848
+ end
849
+ if normalized_path == ""
850
+ if ["http", "https", "ftp", "tftp"].include?(normalized_scheme)
851
+ normalized_path = "/"
852
+ end
853
+ end
854
+
855
+ normalized_query = nil
856
+ normalized_query = self.query.strip if self.query != nil
857
+
858
+ normalized_fragment = nil
859
+ normalized_fragment = self.fragment.strip if self.fragment != nil
860
+ return Addressable::URI.parse(
861
+ Addressable::URI.normalized_encode(Addressable::URI.new(
862
+ normalized_scheme,
863
+ normalized_user,
864
+ normalized_password,
865
+ normalized_host,
866
+ normalized_port,
867
+ normalized_path,
868
+ normalized_query,
869
+ normalized_fragment
870
+ )))
871
+ end
872
+
873
+ # Destructively normalizes this URI object.
874
+ def normalize!
875
+ replace_self(self.normalize)
876
+ end
877
+
878
+ # Creates a URI suitable for display to users. If semantic attacks are
879
+ # likely, the application should try to detect these and warn the user.
880
+ # See RFC 3986 section 7.6 for more information.
881
+ def display_uri
882
+ display_uri = self.normalize
883
+ begin
884
+ display_uri.instance_variable_set("@host",
885
+ URI::IDNA.to_unicode(display_uri.host))
886
+ rescue Exception
887
+ nil
888
+ end
889
+ return display_uri
890
+ end
891
+
892
+ # Returns true if the URI objects are equal. This method normalizes
893
+ # both URIs before doing the comparison, and allows comparison against
894
+ # strings.
895
+ def ===(uri)
896
+ uri_string = nil
897
+ if uri.respond_to?(:normalize)
898
+ uri_string = uri.normalize.to_s
899
+ else
900
+ begin
901
+ uri_string = URI.parse(uri.to_s).normalize.to_s
902
+ rescue Exception
903
+ return false
904
+ end
905
+ end
906
+ return self.normalize.to_s == uri_string
907
+ end
908
+
909
+ # Returns true if the URI objects are equal. This method normalizes
910
+ # both URIs before doing the comparison.
911
+ def ==(uri)
912
+ return false unless uri.kind_of?(self.class)
913
+ return self.normalize.to_s == uri.normalize.to_s
914
+ end
915
+
916
+ # Returns true if the URI objects are equal. This method does NOT
917
+ # normalize either URI before doing the comparison.
918
+ def eql?(uri)
919
+ return false unless uri.kind_of?(self.class)
920
+ return self.to_s == uri.to_s
921
+ end
922
+
923
+ # Clones the URI object.
924
+ def dup
925
+ duplicated_scheme = nil
926
+ duplicated_scheme = self.scheme.dup if self.scheme != nil
927
+ duplicated_user = nil
928
+ duplicated_user = self.user.dup if self.user != nil
929
+ duplicated_password = nil
930
+ duplicated_password = self.password.dup if self.password != nil
931
+ duplicated_host = nil
932
+ duplicated_host = self.host.dup if self.host != nil
933
+ duplicated_port = self.port
934
+ duplicated_path = nil
935
+ duplicated_path = self.path.dup if self.path != nil
936
+ duplicated_query = nil
937
+ duplicated_query = self.query.dup if self.query != nil
938
+ duplicated_fragment = nil
939
+ duplicated_fragment = self.fragment.dup if self.fragment != nil
940
+ duplicated_uri = Addressable::URI.new(
941
+ duplicated_scheme,
942
+ duplicated_user,
943
+ duplicated_password,
944
+ duplicated_host,
945
+ duplicated_port,
946
+ duplicated_path,
947
+ duplicated_query,
948
+ duplicated_fragment
949
+ )
950
+ @specified_port = nil if !defined?(@specified_port)
951
+ duplicated_uri.instance_variable_set("@specified_port", @specified_port)
952
+ return duplicated_uri
953
+ end
954
+
955
+ # Returns the assembled URI as a string.
956
+ def to_s
957
+ uri_string = ""
958
+ uri_string << "#{self.scheme}:" if self.scheme != nil
959
+ uri_string << "//#{self.authority}" if self.authority != nil
960
+ uri_string << self.path.to_s
961
+ uri_string << "?#{self.query}" if self.query != nil
962
+ uri_string << "##{self.fragment}" if self.fragment != nil
963
+ return uri_string
964
+ end
965
+
966
+ # Returns a Hash of the URI segments.
967
+ def to_h
968
+ return {
969
+ :scheme => self.scheme,
970
+ :user => self.user,
971
+ :password => self.password,
972
+ :host => self.host,
973
+ :port => self.specified_port,
974
+ :path => self.path,
975
+ :query => self.query,
976
+ :fragment => self.fragment
977
+ }
978
+ end
979
+
980
+ # Returns a string representation of the URI object's state.
981
+ def inspect
982
+ sprintf("#<%s:%#0x URI:%s>", self.class.to_s, self.object_id, self.to_s)
983
+ end
984
+
985
+ # This module handles internationalized domain names. When Ruby has an
986
+ # implementation of nameprep, stringprep, punycode, etc, this
987
+ # module should contain an actual implementation of IDNA instead of
988
+ # returning nil if libidn can't be used.
989
+ module IDNA
990
+ # Returns the ascii representation of the label.
991
+ def self.to_ascii(label)
992
+ return nil if label.nil?
993
+ if self.use_libidn?
994
+ return IDN::Idna.toASCII(label)
995
+ else
996
+ raise NotImplementedError,
997
+ "There is no available pure-ruby implementation. " +
998
+ "Install libidn bindings."
999
+ end
1000
+ end
1001
+
1002
+ # Returns the unicode representation of the label.
1003
+ def self.to_unicode(label)
1004
+ return nil if label.nil?
1005
+ if self.use_libidn?
1006
+ return IDN::Idna.toUnicode(label)
1007
+ else
1008
+ raise NotImplementedError,
1009
+ "There is no available pure-ruby implementation. " +
1010
+ "Install libidn bindings."
1011
+ end
1012
+ end
1013
+
1014
+ private
1015
+ # Determines if the libidn bindings are available and able to be used.
1016
+ def self.use_libidn?
1017
+ if !defined?(@use_libidn) || @use_libidn.nil?
1018
+ begin
1019
+ require 'rubygems'
1020
+ rescue LoadError
1021
+ nil
1022
+ end
1023
+ begin
1024
+ require 'idn'
1025
+ rescue LoadError
1026
+ nil
1027
+ end
1028
+ @use_libidn = !!(defined?(IDN::Idna))
1029
+ end
1030
+ return @use_libidn
1031
+ end
1032
+ end
1033
+
1034
+ private
1035
+ # Resolves paths to their simplest form.
1036
+ def self.normalize_path(path)
1037
+ return nil if path.nil?
1038
+ normalized_path = path.dup
1039
+ previous_state = normalized_path.dup
1040
+ begin
1041
+ previous_state = normalized_path.dup
1042
+ normalized_path.gsub!(/\/\.\//, "/")
1043
+ normalized_path.gsub!(/\/\.$/, "/")
1044
+ parent = normalized_path.scan(/\/([^\/]+)\/\.\.\//).flatten[0]
1045
+ if parent != "." && parent != ".."
1046
+ normalized_path.gsub!(/\/#{parent}\/\.\.\//, "/")
1047
+ end
1048
+ parent = normalized_path.scan(/\/([^\/]+)\/\.\.$/).flatten[0]
1049
+ if parent != "." && parent != ".."
1050
+ normalized_path.gsub!(/\/#{parent}\/\.\.$/, "/")
1051
+ end
1052
+ normalized_path.gsub!(/^\.\.?\/?/, "")
1053
+ normalized_path.gsub!(/^\/\.\.?\//, "/")
1054
+ end until previous_state == normalized_path
1055
+ return normalized_path
1056
+ end
1057
+
1058
+ # Ensures that the URI is valid.
1059
+ def validate
1060
+ if self.scheme == nil && self.user == nil && self.password == nil &&
1061
+ self.host == nil && self.port == nil && self.path == nil &&
1062
+ self.query == nil && self.fragment == nil
1063
+ raise InvalidURIError, "All segments were nil."
1064
+ end
1065
+ if self.scheme != nil &&
1066
+ (self.host == nil || self.host == "") &&
1067
+ (self.path == nil || self.path == "")
1068
+ raise InvalidURIError,
1069
+ "Absolute URI missing hierarchical segment."
1070
+ end
1071
+ end
1072
+
1073
+ # Replaces the internal state of self with the specified URI's state.
1074
+ # Used in destructive operations to avoid massive code repetition.
1075
+ def replace_self(uri)
1076
+ # Reset dependant values
1077
+ @userinfo = nil
1078
+ @authority = nil
1079
+
1080
+ @scheme = uri.scheme
1081
+ @user = uri.user
1082
+ @password = uri.password
1083
+ @host = uri.host
1084
+ @specified_port = uri.instance_variable_get("@specified_port")
1085
+ @port = @specified_port.to_s.to_i
1086
+ @path = uri.path
1087
+ @query = uri.query
1088
+ @fragment = uri.fragment
1089
+ return self
1090
+ end
1091
+ end
1092
+ end