addressable 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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