uri 0.12.3 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e5eb539bccaa2d0f90ead026894ac1120ef94cffc908cc98450e4fe3e261119
4
- data.tar.gz: f5708e8f3e332c3e05cd39b1147ba732645986599e59952b2ede2d9e2051d3a0
3
+ metadata.gz: 8f4eaea3d45b9a860a4a1765dfa211d195929e01ea90e2ff25405fea90948098
4
+ data.tar.gz: 5f962ae3a2b0c56f589df823edf89560420d8c2dced50ac7332e5a6322a772bc
5
5
  SHA512:
6
- metadata.gz: 2f3d2a24d61837a54530718294785ab82a12c28681de29fc5afe8cd0aa70e9ed046c8d023bfec55e0a484ef20aa74601479003d9781ed4ac9f78fb9338ecd937
7
- data.tar.gz: 765df4b7b63d7b102cf22e0ca54a8eef49b3ab3f4bbb5655f4beb003608a2caa3110f4d71f5b5b502b6a17203260dda72c0a16d6bf23a79001de010a3ee7504a
6
+ metadata.gz: 8fb0be9745374a552f34d58d834e206adabc9a81bfc15bc7179c4143b639f0f62dd73ab46637023147e10be72b61ffdb2b18ac32a697d185244026a51560dc54
7
+ data.tar.gz: 6961a3339bfa178cd339a14828cbb4be6d79249fc1d262da0ea7d68095435efe801ef71642866ab0de2e7a5edc759a168e8b54a006f038e446d755c9781dbf49
@@ -0,0 +1,46 @@
1
+ name: Deploy RDoc site to Pages
2
+
3
+ on:
4
+ push:
5
+ branches: [ 'master' ]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+ pages: write
11
+ id-token: write
12
+
13
+ concurrency:
14
+ group: "pages"
15
+ cancel-in-progress: true
16
+
17
+ jobs:
18
+ build:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - name: Checkout
22
+ uses: actions/checkout@v4
23
+ - name: Setup Ruby
24
+ uses: ruby/setup-ruby@250fcd6a742febb1123a77a841497ccaa8b9e939 # v1.152.0
25
+ with:
26
+ ruby-version: '3.2'
27
+ bundler-cache: true
28
+ - name: Setup Pages
29
+ id: pages
30
+ uses: actions/configure-pages@v3
31
+ - name: Build with RDoc
32
+ # Outputs to the './_site' directory by default
33
+ run: bundle exec rake rdoc
34
+ - name: Upload artifact
35
+ uses: actions/upload-pages-artifact@v2
36
+
37
+ deploy:
38
+ environment:
39
+ name: github-pages
40
+ url: ${{ steps.deployment.outputs.page_url }}
41
+ runs-on: ubuntu-latest
42
+ needs: build
43
+ steps:
44
+ - name: Deploy to GitHub Pages
45
+ id: deployment
46
+ uses: actions/deploy-pages@v2
@@ -3,20 +3,21 @@ name: CI
3
3
  on: [push, pull_request]
4
4
 
5
5
  jobs:
6
+ ruby-versions:
7
+ uses: ruby/actions/.github/workflows/ruby_versions.yml@master
8
+ with:
9
+ min_version: 2.5
10
+
6
11
  build:
12
+ needs: ruby-versions
7
13
  name: build (${{ matrix.ruby }} / ${{ matrix.os }})
8
14
  strategy:
9
15
  matrix:
10
- ruby: [ 3.1, '3.0', 2.7, 2.6, 2.5, 2.4, head, truffleruby ]
16
+ ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }}
11
17
  os: [ ubuntu-latest, macos-latest ]
12
- exclude:
13
- - ruby: 2.4
14
- os: macos-latest
15
- - ruby: 2.5
16
- os: macos-latest
17
18
  runs-on: ${{ matrix.os }}
18
19
  steps:
19
- - uses: actions/checkout@v3
20
+ - uses: actions/checkout@v4
20
21
  - name: Set up Ruby
21
22
  uses: ruby/setup-ruby@v1
22
23
  with:
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
  Gemfile.lock
10
+ /_site
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # URI
2
2
 
3
3
  [![CI](https://github.com/ruby/uri/actions/workflows/test.yml/badge.svg)](https://github.com/ruby/uri/actions/workflows/test.yml)
4
+ [![Yard Docs](https://img.shields.io/badge/docs-exist-blue.svg)](https://ruby.github.io/uri/)
4
5
 
5
6
  URI is a module providing classes to handle Uniform Resource Identifiers [RFC2396](http://tools.ietf.org/html/rfc2396).
6
7
 
data/Rakefile CHANGED
@@ -7,11 +7,12 @@ Rake::TestTask.new(:test) do |t|
7
7
  t.test_files = FileList["test/**/test_*.rb"]
8
8
  end
9
9
 
10
- task :sync_tool do
11
- require 'fileutils'
12
- FileUtils.cp "../ruby/tool/lib/test/unit/core_assertions.rb", "./test/lib"
13
- FileUtils.cp "../ruby/tool/lib/envutil.rb", "./test/lib"
14
- FileUtils.cp "../ruby/tool/lib/find_executable.rb", "./test/lib"
10
+ require "rdoc/task"
11
+ RDoc::Task.new do |doc|
12
+ doc.main = "README.md"
13
+ doc.title = "URI - handle Uniform Resource Identifiers"
14
+ doc.rdoc_files = FileList.new %w[lib README.md LICENSE.txt]
15
+ doc.rdoc_dir = "_site" # for github pages
15
16
  end
16
17
 
17
18
  task :default => :test
data/lib/uri/common.rb CHANGED
@@ -19,8 +19,6 @@ module URI
19
19
  Parser = RFC2396_Parser
20
20
  RFC3986_PARSER = RFC3986_Parser.new
21
21
  Ractor.make_shareable(RFC3986_PARSER) if defined?(Ractor)
22
- RFC2396_PARSER = RFC2396_Parser.new
23
- Ractor.make_shareable(RFC2396_PARSER) if defined?(Ractor)
24
22
 
25
23
  # URI::Parser.new
26
24
  DEFAULT_PARSER = Parser.new
@@ -70,16 +68,32 @@ module URI
70
68
  end
71
69
  private_constant :Schemes
72
70
 
71
+ # Registers the given +klass+ as the class to be instantiated
72
+ # when parsing a \URI with the given +scheme+:
73
73
  #
74
- # Register the given +klass+ to be instantiated when parsing URLs with the given +scheme+.
75
- # Note that currently only schemes which after .upcase are valid constant names
76
- # can be registered (no -/+/. allowed).
74
+ # URI.register_scheme('MS_SEARCH', URI::Generic) # => URI::Generic
75
+ # URI.scheme_list['MS_SEARCH'] # => URI::Generic
77
76
  #
77
+ # Note that after calling String#upcase on +scheme+, it must be a valid
78
+ # constant name.
78
79
  def self.register_scheme(scheme, klass)
79
80
  Schemes.const_set(scheme.to_s.upcase, klass)
80
81
  end
81
82
 
82
- # Returns a Hash of the defined schemes.
83
+ # Returns a hash of the defined schemes:
84
+ #
85
+ # URI.scheme_list
86
+ # # =>
87
+ # {"MAILTO"=>URI::MailTo,
88
+ # "LDAPS"=>URI::LDAPS,
89
+ # "WS"=>URI::WS,
90
+ # "HTTP"=>URI::HTTP,
91
+ # "HTTPS"=>URI::HTTPS,
92
+ # "LDAP"=>URI::LDAP,
93
+ # "FILE"=>URI::File,
94
+ # "FTP"=>URI::FTP}
95
+ #
96
+ # Related: URI.register_scheme.
83
97
  def self.scheme_list
84
98
  Schemes.constants.map { |name|
85
99
  [name.to_s.upcase, Schemes.const_get(name)]
@@ -90,9 +104,21 @@ module URI
90
104
  private_constant :INITIAL_SCHEMES
91
105
  Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor)
92
106
 
107
+ # Returns a new object constructed from the given +scheme+, +arguments+,
108
+ # and +default+:
109
+ #
110
+ # - The new object is an instance of <tt>URI.scheme_list[scheme.upcase]</tt>.
111
+ # - The object is initialized by calling the class initializer
112
+ # using +scheme+ and +arguments+.
113
+ # See URI::Generic.new.
114
+ #
115
+ # Examples:
93
116
  #
94
- # Construct a URI instance, using the scheme to detect the appropriate class
95
- # from +URI.scheme_list+.
117
+ # values = ['john.doe', 'www.example.com', '123', nil, '/forum/questions/', nil, 'tag=networking&order=newest', 'top']
118
+ # URI.for('https', *values)
119
+ # # => #<URI::HTTPS https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
120
+ # URI.for('foo', *values, default: URI::HTTP)
121
+ # # => #<URI::HTTP foo://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
96
122
  #
97
123
  def self.for(scheme, *arguments, default: Generic)
98
124
  const_name = scheme.to_s.upcase
@@ -123,95 +149,49 @@ module URI
123
149
  #
124
150
  class BadURIError < Error; end
125
151
 
126
- #
127
- # == Synopsis
128
- #
129
- # URI::split(uri)
130
- #
131
- # == Args
132
- #
133
- # +uri+::
134
- # String with URI.
135
- #
136
- # == Description
137
- #
138
- # Splits the string on following parts and returns array with result:
139
- #
140
- # * Scheme
141
- # * Userinfo
142
- # * Host
143
- # * Port
144
- # * Registry
145
- # * Path
146
- # * Opaque
147
- # * Query
148
- # * Fragment
149
- #
150
- # == Usage
151
- #
152
- # require 'uri'
153
- #
154
- # URI.split("http://www.ruby-lang.org/")
155
- # # => ["http", nil, "www.ruby-lang.org", nil, nil, "/", nil, nil, nil]
152
+ # Returns a 9-element array representing the parts of the \URI
153
+ # formed from the string +uri+;
154
+ # each array element is a string or +nil+:
155
+ #
156
+ # names = %w[scheme userinfo host port registry path opaque query fragment]
157
+ # values = URI.split('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
158
+ # names.zip(values)
159
+ # # =>
160
+ # [["scheme", "https"],
161
+ # ["userinfo", "john.doe"],
162
+ # ["host", "www.example.com"],
163
+ # ["port", "123"],
164
+ # ["registry", nil],
165
+ # ["path", "/forum/questions/"],
166
+ # ["opaque", nil],
167
+ # ["query", "tag=networking&order=newest"],
168
+ # ["fragment", "top"]]
156
169
  #
157
170
  def self.split(uri)
158
171
  RFC3986_PARSER.split(uri)
159
172
  end
160
173
 
174
+ # Returns a new \URI object constructed from the given string +uri+:
161
175
  #
162
- # == Synopsis
163
- #
164
- # URI::parse(uri_str)
165
- #
166
- # == Args
167
- #
168
- # +uri_str+::
169
- # String with URI.
170
- #
171
- # == Description
172
- #
173
- # Creates one of the URI's subclasses instance from the string.
174
- #
175
- # == Raises
176
- #
177
- # URI::InvalidURIError::
178
- # Raised if URI given is not a correct one.
176
+ # URI.parse('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
177
+ # # => #<URI::HTTPS https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
178
+ # URI.parse('http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
179
+ # # => #<URI::HTTP http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
179
180
  #
180
- # == Usage
181
- #
182
- # require 'uri'
183
- #
184
- # uri = URI.parse("http://www.ruby-lang.org/")
185
- # # => #<URI::HTTP http://www.ruby-lang.org/>
186
- # uri.scheme
187
- # # => "http"
188
- # uri.host
189
- # # => "www.ruby-lang.org"
190
- #
191
- # It's recommended to first ::escape the provided +uri_str+ if there are any
192
- # invalid URI characters.
181
+ # It's recommended to first ::escape string +uri+
182
+ # if it may contain invalid URI characters.
193
183
  #
194
184
  def self.parse(uri)
195
185
  RFC3986_PARSER.parse(uri)
196
186
  end
197
187
 
188
+ # Merges the given URI strings +str+
189
+ # per {RFC 2396}[https://www.rfc-editor.org/rfc/rfc2396.html].
198
190
  #
199
- # == Synopsis
200
- #
201
- # URI::join(str[, str, ...])
191
+ # Each string in +str+ is converted to an
192
+ # {RFC3986 URI}[https://www.rfc-editor.org/rfc/rfc3986.html] before being merged.
202
193
  #
203
- # == Args
204
- #
205
- # +str+::
206
- # String(s) to work with, will be converted to RFC3986 URIs before merging.
207
- #
208
- # == Description
209
- #
210
- # Joins URIs.
211
- #
212
- # == Usage
213
- #
214
- # require 'uri'
194
+ # Examples:
215
195
  #
216
196
  # URI.join("http://example.com/","main.rbx")
217
197
  # # => #<URI::HTTP http://example.com/main.rbx>
@@ -256,7 +236,7 @@ module URI
256
236
  # URI.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.")
257
237
  # # => ["http://foo.example.com/bla", "mailto:test@example.com"]
258
238
  #
259
- def self.extract(str, schemes = nil, &block)
239
+ def self.extract(str, schemes = nil, &block) # :nodoc:
260
240
  warn "URI.extract is obsolete", uplevel: 1 if $VERBOSE
261
241
  DEFAULT_PARSER.extract(str, schemes, &block)
262
242
  end
@@ -293,7 +273,7 @@ module URI
293
273
  # p $&
294
274
  # end
295
275
  #
296
- def self.regexp(schemes = nil)
276
+ def self.regexp(schemes = nil)# :nodoc:
297
277
  warn "URI.regexp is obsolete", uplevel: 1 if $VERBOSE
298
278
  DEFAULT_PARSER.make_regexp(schemes)
299
279
  end
@@ -316,40 +296,86 @@ module URI
316
296
  TBLDECWWWCOMP_['+'] = ' '
317
297
  TBLDECWWWCOMP_.freeze
318
298
 
319
- # Encodes given +str+ to URL-encoded form data.
299
+ # Returns a URL-encoded string derived from the given string +str+.
300
+ #
301
+ # The returned string:
302
+ #
303
+ # - Preserves:
304
+ #
305
+ # - Characters <tt>'*'</tt>, <tt>'.'</tt>, <tt>'-'</tt>, and <tt>'_'</tt>.
306
+ # - Character in ranges <tt>'a'..'z'</tt>, <tt>'A'..'Z'</tt>,
307
+ # and <tt>'0'..'9'</tt>.
308
+ #
309
+ # Example:
320
310
  #
321
- # This method doesn't convert *, -, ., 0-9, A-Z, _, a-z, but does convert SP
322
- # (ASCII space) to + and converts others to %XX.
311
+ # URI.encode_www_form_component('*.-_azAZ09')
312
+ # # => "*.-_azAZ09"
323
313
  #
324
- # If +enc+ is given, convert +str+ to the encoding before percent encoding.
314
+ # - Converts:
325
315
  #
326
- # This is an implementation of
327
- # https://www.w3.org/TR/2013/CR-html5-20130806/forms.html#url-encoded-form-data.
316
+ # - Character <tt>' '</tt> to character <tt>'+'</tt>.
317
+ # - Any other character to "percent notation";
318
+ # the percent notation for character <i>c</i> is <tt>'%%%X' % c.ord</tt>.
328
319
  #
329
- # See URI.decode_www_form_component, URI.encode_www_form.
320
+ # Example:
321
+ #
322
+ # URI.encode_www_form_component('Here are some punctuation characters: ,;?:')
323
+ # # => "Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A"
324
+ #
325
+ # Encoding:
326
+ #
327
+ # - If +str+ has encoding Encoding::ASCII_8BIT, argument +enc+ is ignored.
328
+ # - Otherwise +str+ is converted first to Encoding::UTF_8
329
+ # (with suitable character replacements),
330
+ # and then to encoding +enc+.
331
+ #
332
+ # In either case, the returned string has forced encoding Encoding::US_ASCII.
333
+ #
334
+ # Related: URI.encode_uri_component (encodes <tt>' '</tt> as <tt>'%20'</tt>).
330
335
  def self.encode_www_form_component(str, enc=nil)
331
336
  _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP_, str, enc)
332
337
  end
333
338
 
334
- # Decodes given +str+ of URL-encoded form data.
339
+ # Returns a string decoded from the given \URL-encoded string +str+.
340
+ #
341
+ # The given string is first encoded as Encoding::ASCII-8BIT (using String#b),
342
+ # then decoded (as below), and finally force-encoded to the given encoding +enc+.
343
+ #
344
+ # The returned string:
345
+ #
346
+ # - Preserves:
347
+ #
348
+ # - Characters <tt>'*'</tt>, <tt>'.'</tt>, <tt>'-'</tt>, and <tt>'_'</tt>.
349
+ # - Character in ranges <tt>'a'..'z'</tt>, <tt>'A'..'Z'</tt>,
350
+ # and <tt>'0'..'9'</tt>.
351
+ #
352
+ # Example:
353
+ #
354
+ # URI.decode_www_form_component('*.-_azAZ09')
355
+ # # => "*.-_azAZ09"
356
+ #
357
+ # - Converts:
358
+ #
359
+ # - Character <tt>'+'</tt> to character <tt>' '</tt>.
360
+ # - Each "percent notation" to an ASCII character.
335
361
  #
336
- # This decodes + to SP.
362
+ # Example:
337
363
  #
338
- # See URI.encode_www_form_component, URI.decode_www_form.
364
+ # URI.decode_www_form_component('Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A')
365
+ # # => "Here are some punctuation characters: ,;?:"
366
+ #
367
+ # Related: URI.decode_uri_component (preserves <tt>'+'</tt>).
339
368
  def self.decode_www_form_component(str, enc=Encoding::UTF_8)
340
369
  _decode_uri_component(/\+|%\h\h/, str, enc)
341
370
  end
342
371
 
343
- # Encodes +str+ using URL encoding
344
- #
345
- # This encodes SP to %20 instead of +.
372
+ # Like URI.encode_www_form_component, except that <tt>' '</tt> (space)
373
+ # is encoded as <tt>'%20'</tt> (instead of <tt>'+'</tt>).
346
374
  def self.encode_uri_component(str, enc=nil)
347
375
  _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCURICOMP_, str, enc)
348
376
  end
349
377
 
350
- # Decodes given +str+ of URL-encoded data.
351
- #
352
- # This does not decode + to SP.
378
+ # Like URI.decode_www_form_component, except that <tt>'+'</tt> is preserved.
353
379
  def self.decode_uri_component(str, enc=Encoding::UTF_8)
354
380
  _decode_uri_component(/%\h\h/, str, enc)
355
381
  end
@@ -374,33 +400,104 @@ module URI
374
400
  end
375
401
  private_class_method :_decode_uri_component
376
402
 
377
- # Generates URL-encoded form data from given +enum+.
403
+ # Returns a URL-encoded string derived from the given
404
+ # {Enumerable}[https://docs.ruby-lang.org/en/master/Enumerable.html#module-Enumerable-label-Enumerable+in+Ruby+Classes]
405
+ # +enum+.
406
+ #
407
+ # The result is suitable for use as form data
408
+ # for an \HTTP request whose <tt>Content-Type</tt> is
409
+ # <tt>'application/x-www-form-urlencoded'</tt>.
410
+ #
411
+ # The returned string consists of the elements of +enum+,
412
+ # each converted to one or more URL-encoded strings,
413
+ # and all joined with character <tt>'&'</tt>.
414
+ #
415
+ # Simple examples:
416
+ #
417
+ # URI.encode_www_form([['foo', 0], ['bar', 1], ['baz', 2]])
418
+ # # => "foo=0&bar=1&baz=2"
419
+ # URI.encode_www_form({foo: 0, bar: 1, baz: 2})
420
+ # # => "foo=0&bar=1&baz=2"
421
+ #
422
+ # The returned string is formed using method URI.encode_www_form_component,
423
+ # which converts certain characters:
424
+ #
425
+ # URI.encode_www_form('f#o': '/', 'b-r': '$', 'b z': '@')
426
+ # # => "f%23o=%2F&b-r=%24&b+z=%40"
427
+ #
428
+ # When +enum+ is Array-like, each element +ele+ is converted to a field:
429
+ #
430
+ # - If +ele+ is an array of two or more elements,
431
+ # the field is formed from its first two elements
432
+ # (and any additional elements are ignored):
433
+ #
434
+ # name = URI.encode_www_form_component(ele[0], enc)
435
+ # value = URI.encode_www_form_component(ele[1], enc)
436
+ # "#{name}=#{value}"
378
437
  #
379
- # This generates application/x-www-form-urlencoded data defined in HTML5
380
- # from given an Enumerable object.
438
+ # Examples:
381
439
  #
382
- # This internally uses URI.encode_www_form_component(str).
440
+ # URI.encode_www_form([%w[foo bar], %w[baz bat bah]])
441
+ # # => "foo=bar&baz=bat"
442
+ # URI.encode_www_form([['foo', 0], ['bar', :baz, 'bat']])
443
+ # # => "foo=0&bar=baz"
383
444
  #
384
- # This method doesn't convert the encoding of given items, so convert them
385
- # before calling this method if you want to send data as other than original
386
- # encoding or mixed encoding data. (Strings which are encoded in an HTML5
387
- # ASCII incompatible encoding are converted to UTF-8.)
445
+ # - If +ele+ is an array of one element,
446
+ # the field is formed from <tt>ele[0]</tt>:
388
447
  #
389
- # This method doesn't handle files. When you send a file, use
390
- # multipart/form-data.
448
+ # URI.encode_www_form_component(ele[0])
391
449
  #
392
- # This refers https://url.spec.whatwg.org/#concept-urlencoded-serializer
450
+ # Example:
393
451
  #
394
- # URI.encode_www_form([["q", "ruby"], ["lang", "en"]])
395
- # #=> "q=ruby&lang=en"
396
- # URI.encode_www_form("q" => "ruby", "lang" => "en")
397
- # #=> "q=ruby&lang=en"
398
- # URI.encode_www_form("q" => ["ruby", "perl"], "lang" => "en")
399
- # #=> "q=ruby&q=perl&lang=en"
400
- # URI.encode_www_form([["q", "ruby"], ["q", "perl"], ["lang", "en"]])
401
- # #=> "q=ruby&q=perl&lang=en"
452
+ # URI.encode_www_form([['foo'], [:bar], [0]])
453
+ # # => "foo&bar&0"
454
+ #
455
+ # - Otherwise the field is formed from +ele+:
456
+ #
457
+ # URI.encode_www_form_component(ele)
458
+ #
459
+ # Example:
460
+ #
461
+ # URI.encode_www_form(['foo', :bar, 0])
462
+ # # => "foo&bar&0"
463
+ #
464
+ # The elements of an Array-like +enum+ may be mixture:
465
+ #
466
+ # URI.encode_www_form([['foo', 0], ['bar', 1, 2], ['baz'], :bat])
467
+ # # => "foo=0&bar=1&baz&bat"
468
+ #
469
+ # When +enum+ is Hash-like,
470
+ # each +key+/+value+ pair is converted to one or more fields:
471
+ #
472
+ # - If +value+ is
473
+ # {Array-convertible}[https://docs.ruby-lang.org/en/master/implicit_conversion_rdoc.html#label-Array-Convertible+Objects],
474
+ # each element +ele+ in +value+ is paired with +key+ to form a field:
475
+ #
476
+ # name = URI.encode_www_form_component(key, enc)
477
+ # value = URI.encode_www_form_component(ele, enc)
478
+ # "#{name}=#{value}"
479
+ #
480
+ # Example:
481
+ #
482
+ # URI.encode_www_form({foo: [:bar, 1], baz: [:bat, :bam, 2]})
483
+ # # => "foo=bar&foo=1&baz=bat&baz=bam&baz=2"
484
+ #
485
+ # - Otherwise, +key+ and +value+ are paired to form a field:
486
+ #
487
+ # name = URI.encode_www_form_component(key, enc)
488
+ # value = URI.encode_www_form_component(value, enc)
489
+ # "#{name}=#{value}"
490
+ #
491
+ # Example:
492
+ #
493
+ # URI.encode_www_form({foo: 0, bar: 1, baz: 2})
494
+ # # => "foo=0&bar=1&baz=2"
495
+ #
496
+ # The elements of a Hash-like +enum+ may be mixture:
497
+ #
498
+ # URI.encode_www_form({foo: [0, 1], bar: 2})
499
+ # # => "foo=0&foo=1&bar=2"
402
500
  #
403
- # See URI.encode_www_form_component, URI.decode_www_form.
404
501
  def self.encode_www_form(enum, enc=nil)
405
502
  enum.map do |k,v|
406
503
  if v.nil?
@@ -421,22 +518,39 @@ module URI
421
518
  end.join('&')
422
519
  end
423
520
 
424
- # Decodes URL-encoded form data from given +str+.
521
+ # Returns name/value pairs derived from the given string +str+,
522
+ # which must be an ASCII string.
523
+ #
524
+ # The method may be used to decode the body of Net::HTTPResponse object +res+
525
+ # for which <tt>res['Content-Type']</tt> is <tt>'application/x-www-form-urlencoded'</tt>.
526
+ #
527
+ # The returned data is an array of 2-element subarrays;
528
+ # each subarray is a name/value pair (both are strings).
529
+ # Each returned string has encoding +enc+,
530
+ # and has had invalid characters removed via
531
+ # {String#scrub}[https://docs.ruby-lang.org/en/master/String.html#method-i-scrub].
425
532
  #
426
- # This decodes application/x-www-form-urlencoded data
427
- # and returns an array of key-value arrays.
533
+ # A simple example:
428
534
  #
429
- # This refers http://url.spec.whatwg.org/#concept-urlencoded-parser,
430
- # so this supports only &-separator, and doesn't support ;-separator.
535
+ # URI.decode_www_form('foo=0&bar=1&baz')
536
+ # # => [["foo", "0"], ["bar", "1"], ["baz", ""]]
431
537
  #
432
- # ary = URI.decode_www_form("a=1&a=2&b=3")
433
- # ary #=> [['a', '1'], ['a', '2'], ['b', '3']]
434
- # ary.assoc('a').last #=> '1'
435
- # ary.assoc('b').last #=> '3'
436
- # ary.rassoc('a').last #=> '2'
437
- # Hash[ary] #=> {"a"=>"2", "b"=>"3"}
538
+ # The returned strings have certain conversions,
539
+ # similar to those performed in URI.decode_www_form_component:
540
+ #
541
+ # URI.decode_www_form('f%23o=%2F&b-r=%24&b+z=%40')
542
+ # # => [["f#o", "/"], ["b-r", "$"], ["b z", "@"]]
543
+ #
544
+ # The given string may contain consecutive separators:
545
+ #
546
+ # URI.decode_www_form('foo=0&&bar=1&&baz=2')
547
+ # # => [["foo", "0"], ["", ""], ["bar", "1"], ["", ""], ["baz", "2"]]
548
+ #
549
+ # A different separator may be specified:
550
+ #
551
+ # URI.decode_www_form('foo=0--bar=1--baz', separator: '--')
552
+ # # => [["foo", "0"], ["bar", "1"], ["baz", ""]]
438
553
  #
439
- # See URI.decode_www_form_component, URI.encode_www_form.
440
554
  def self.decode_www_form(str, enc=Encoding::UTF_8, separator: '&', use__charset_: false, isindex: false)
441
555
  raise ArgumentError, "the input of #{self.name}.#{__method__} must be ASCII only string" unless str.ascii_only?
442
556
  ary = []
@@ -715,7 +829,15 @@ end # module URI
715
829
  module Kernel
716
830
 
717
831
  #
718
- # Returns +uri+ converted to an URI object.
832
+ # Returns a \URI object derived from the given +uri+,
833
+ # which may be a \URI string or an existing \URI object:
834
+ #
835
+ # # Returns a new URI.
836
+ # uri = URI('http://github.com/ruby/ruby')
837
+ # # => #<URI::HTTP http://github.com/ruby/ruby>
838
+ # # Returns the given URI.
839
+ # URI(uri)
840
+ # # => #<URI::HTTP http://github.com/ruby/ruby>
719
841
  #
720
842
  def URI(uri)
721
843
  if uri.is_a?(URI::Generic)
data/lib/uri/generic.rb CHANGED
@@ -1376,6 +1376,7 @@ module URI
1376
1376
  end
1377
1377
  str
1378
1378
  end
1379
+ alias to_str to_s
1379
1380
 
1380
1381
  #
1381
1382
  # Compares two URIs.
@@ -1,9 +1,73 @@
1
- # frozen_string_literal: false
1
+ # frozen_string_literal: true
2
2
  module URI
3
3
  class RFC3986_Parser # :nodoc:
4
4
  # URI defined in RFC3986
5
- RFC3986_URI = /\A(?<URI>(?<scheme>[A-Za-z][+\-.0-9A-Za-z]*+):(?<hier-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*+)@)?(?<host>(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h++\.[!$&-.0-;=A-Z_a-z~]++))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])*+))(?::(?<port>\d*+))?)(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*+))*+)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])++)(?:\/\g<segment>)*+)?)|(?<path-rootless>\g<segment-nz>(?:\/\g<segment>)*+)|(?<path-empty>))(?:\?(?<query>[^#]*+))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*+))?)\z/
6
- RFC3986_relative_ref = /\A(?<relative-ref>(?<relative-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*+)@)?(?<host>(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:){,1}\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h++\.[!$&-.0-;=A-Z_a-z~]++))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])++))?(?::(?<port>\d*+))?)(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*+))*+)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])++)(?:\/\g<segment>)*+)?)|(?<path-noscheme>(?<segment-nz-nc>(?:%\h\h|[!$&-.0-9;=@-Z_a-z~])++)(?:\/\g<segment>)*+)|(?<path-empty>))(?:\?(?<query>[^#]*+))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*+))?)\z/
5
+ HOST = %r[
6
+ (?<IP-literal>\[(?:
7
+ (?<IPv6address>
8
+ (?:\h{1,4}:){6}
9
+ (?<ls32>\h{1,4}:\h{1,4}
10
+ | (?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)
11
+ \.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>)
12
+ )
13
+ | ::(?:\h{1,4}:){5}\g<ls32>
14
+ | \h{1,4}?::(?:\h{1,4}:){4}\g<ls32>
15
+ | (?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>
16
+ | (?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>
17
+ | (?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>
18
+ | (?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>
19
+ | (?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}
20
+ | (?:(?:\h{1,4}:){,6}\h{1,4})?::
21
+ )
22
+ | (?<IPvFuture>v\h++\.[!$&-.0-9:;=A-Z_a-z~]++)
23
+ )\])
24
+ | \g<IPv4address>
25
+ | (?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])*+)
26
+ ]x
27
+
28
+ USERINFO = /(?:%\h\h|[!$&-.0-9:;=A-Z_a-z~])*+/
29
+
30
+ SCHEME = %r[[A-Za-z][+\-.0-9A-Za-z]*+].source
31
+ SEG = %r[(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/])].source
32
+ SEG_NC = %r[(?:%\h\h|[!$&-.0-9;=@A-Z_a-z~])].source
33
+ FRAGMENT = %r[(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/?])*+].source
34
+
35
+ RFC3986_URI = %r[\A
36
+ (?<seg>#{SEG}){0}
37
+ (?<URI>
38
+ (?<scheme>#{SCHEME}):
39
+ (?<hier-part>//
40
+ (?<authority>
41
+ (?:(?<userinfo>#{USERINFO.source})@)?
42
+ (?<host>#{HOST.source.delete(" \n")})
43
+ (?::(?<port>\d*+))?
44
+ )
45
+ (?<path-abempty>(?:/\g<seg>*+)?)
46
+ | (?<path-absolute>/((?!/)\g<seg>++)?)
47
+ | (?<path-rootless>(?!/)\g<seg>++)
48
+ | (?<path-empty>)
49
+ )
50
+ (?:\?(?<query>[^\#]*+))?
51
+ (?:\#(?<fragment>#{FRAGMENT}))?
52
+ )\z]x
53
+
54
+ RFC3986_relative_ref = %r[\A
55
+ (?<seg>#{SEG}){0}
56
+ (?<relative-ref>
57
+ (?<relative-part>//
58
+ (?<authority>
59
+ (?:(?<userinfo>#{USERINFO.source})@)?
60
+ (?<host>#{HOST.source.delete(" \n")}(?<!/))?
61
+ (?::(?<port>\d*+))?
62
+ )
63
+ (?<path-abempty>(?:/\g<seg>*+)?)
64
+ | (?<path-absolute>/\g<seg>*+)
65
+ | (?<path-noscheme>#{SEG_NC}++(?:/\g<seg>*+)?)
66
+ | (?<path-empty>)
67
+ )
68
+ (?:\?(?<query>[^#]*+))?
69
+ (?:\#(?<fragment>#{FRAGMENT}))?
70
+ )\z]x
7
71
  attr_reader :regexp
8
72
 
9
73
  def initialize
@@ -19,9 +83,9 @@ module URI
19
83
  uri.ascii_only? or
20
84
  raise InvalidURIError, "URI must be ascii only #{uri.dump}"
21
85
  if m = RFC3986_URI.match(uri)
22
- query = m["query".freeze]
23
- scheme = m["scheme".freeze]
24
- opaque = m["path-rootless".freeze]
86
+ query = m["query"]
87
+ scheme = m["scheme"]
88
+ opaque = m["path-rootless"]
25
89
  if opaque
26
90
  opaque << "?#{query}" if query
27
91
  [ scheme,
@@ -32,35 +96,35 @@ module URI
32
96
  nil, # path
33
97
  opaque,
34
98
  nil, # query
35
- m["fragment".freeze]
99
+ m["fragment"]
36
100
  ]
37
101
  else # normal
38
102
  [ scheme,
39
- m["userinfo".freeze],
40
- m["host".freeze],
41
- m["port".freeze],
103
+ m["userinfo"],
104
+ m["host"],
105
+ m["port"],
42
106
  nil, # registry
43
- (m["path-abempty".freeze] ||
44
- m["path-absolute".freeze] ||
45
- m["path-empty".freeze]),
107
+ (m["path-abempty"] ||
108
+ m["path-absolute"] ||
109
+ m["path-empty"]),
46
110
  nil, # opaque
47
111
  query,
48
- m["fragment".freeze]
112
+ m["fragment"]
49
113
  ]
50
114
  end
51
115
  elsif m = RFC3986_relative_ref.match(uri)
52
116
  [ nil, # scheme
53
- m["userinfo".freeze],
54
- m["host".freeze],
55
- m["port".freeze],
117
+ m["userinfo"],
118
+ m["host"],
119
+ m["port"],
56
120
  nil, # registry,
57
- (m["path-abempty".freeze] ||
58
- m["path-absolute".freeze] ||
59
- m["path-noscheme".freeze] ||
60
- m["path-empty".freeze]),
121
+ (m["path-abempty"] ||
122
+ m["path-absolute"] ||
123
+ m["path-noscheme"] ||
124
+ m["path-empty"]),
61
125
  nil, # opaque
62
- m["query".freeze],
63
- m["fragment".freeze]
126
+ m["query"],
127
+ m["fragment"]
64
128
  ]
65
129
  else
66
130
  raise InvalidURIError, "bad URI(is not URI?): #{uri.inspect}"
@@ -92,14 +156,14 @@ module URI
92
156
 
93
157
  def default_regexp # :nodoc:
94
158
  {
95
- SCHEME: /\A[A-Za-z][A-Za-z0-9+\-.]*\z/,
96
- USERINFO: /\A(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*\z/,
97
- HOST: /\A(?:(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{,4}::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h+\.[!$&-.0-;=A-Z_a-z~]+))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])*))\z/,
98
- ABS_PATH: /\A\/(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*(?:\/(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*)*\z/,
99
- REL_PATH: /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+(?:\/(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*)*\z/,
100
- QUERY: /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*\z/,
101
- FRAGMENT: /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*\z/,
102
- OPAQUE: /\A(?:[^\/].*)?\z/,
159
+ SCHEME: %r[\A#{SCHEME}\z]o,
160
+ USERINFO: %r[\A#{USERINFO}\z]o,
161
+ HOST: %r[\A#{HOST}\z]o,
162
+ ABS_PATH: %r[\A/#{SEG}*+\z]o,
163
+ REL_PATH: %r[\A(?!/)#{SEG}++\z]o,
164
+ QUERY: %r[\A(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/?])*+\z],
165
+ FRAGMENT: %r[\A#{FRAGMENT}\z]o,
166
+ OPAQUE: %r[\A(?:[^/].*)?\z],
103
167
  PORT: /\A[\x09\x0a\x0c\x0d ]*+\d*[\x09\x0a\x0c\x0d ]*\z/,
104
168
  }
105
169
  end
data/lib/uri/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module URI
2
2
  # :stopdoc:
3
- VERSION_CODE = '001203'.freeze
3
+ VERSION_CODE = '001300'.freeze
4
4
  VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze
5
5
  # :startdoc:
6
6
  end
@@ -0,0 +1,17 @@
1
+ task :sync_tool, [:from] do |t, from: nil|
2
+ from ||= (File.identical?(__dir__, "rakelib") ? "../ruby/tool" : File.dirname(__dir__))
3
+
4
+ require 'fileutils'
5
+
6
+ {
7
+ "rakelib/sync_tool.rake" => "rakelib",
8
+ "lib/core_assertions.rb" => "test/lib",
9
+ "lib/envutil.rb" => "test/lib",
10
+ "lib/find_executable.rb" => "test/lib",
11
+ "lib/helper.rb" => "test/lib",
12
+ }.each do |src, dest|
13
+ FileUtils.mkpath(dest)
14
+ FileUtils.cp "#{from}/#{src}", dest
15
+ rescue Errno::ENOENT
16
+ end
17
+ end
data/uri.gemspec CHANGED
@@ -12,18 +12,26 @@ Gem::Specification.new do |spec|
12
12
 
13
13
  spec.summary = %q{URI is a module providing classes to handle Uniform Resource Identifiers}
14
14
  spec.description = spec.summary
15
- spec.homepage = "https://github.com/ruby/uri"
15
+
16
+ github_link = "https://github.com/ruby/uri"
17
+
18
+ spec.homepage = github_link
16
19
  spec.licenses = ["Ruby", "BSD-2-Clause"]
17
20
 
18
- spec.required_ruby_version = '>= 2.4'
21
+ spec.required_ruby_version = '>= 2.5'
19
22
 
20
- spec.metadata["homepage_uri"] = spec.homepage
21
- spec.metadata["source_code_uri"] = spec.homepage
23
+ spec.metadata = {
24
+ "bug_tracker_uri" => "#{github_link}/issues",
25
+ "changelog_uri" => "#{github_link}/releases",
26
+ "documentation_uri" => "https://ruby.github.io/uri/",
27
+ "homepage_uri" => spec.homepage,
28
+ "source_code_uri" => github_link
29
+ }
22
30
 
23
31
  # Specify which files should be added to the gem when it is released.
24
32
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
33
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
26
- `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
34
+ `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27
35
  end
28
36
  spec.bindir = "exe"
29
37
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uri
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.3
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Akira Yamada
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 2024-08-27 00:00:00.000000000 Z
11
+ date: 2023-11-06 00:00:00.000000000 Z
11
12
  dependencies: []
12
13
  description: URI is a module providing classes to handle Uniform Resource Identifiers
13
14
  email:
@@ -17,6 +18,7 @@ extensions: []
17
18
  extra_rdoc_files: []
18
19
  files:
19
20
  - ".github/dependabot.yml"
21
+ - ".github/workflows/gh-pages.yml"
20
22
  - ".github/workflows/test.yml"
21
23
  - ".gitignore"
22
24
  - Gemfile
@@ -40,14 +42,19 @@ files:
40
42
  - lib/uri/version.rb
41
43
  - lib/uri/ws.rb
42
44
  - lib/uri/wss.rb
45
+ - rakelib/sync_tool.rake
43
46
  - uri.gemspec
44
47
  homepage: https://github.com/ruby/uri
45
48
  licenses:
46
49
  - Ruby
47
50
  - BSD-2-Clause
48
51
  metadata:
52
+ bug_tracker_uri: https://github.com/ruby/uri/issues
53
+ changelog_uri: https://github.com/ruby/uri/releases
54
+ documentation_uri: https://ruby.github.io/uri/
49
55
  homepage_uri: https://github.com/ruby/uri
50
56
  source_code_uri: https://github.com/ruby/uri
57
+ post_install_message:
51
58
  rdoc_options: []
52
59
  require_paths:
53
60
  - lib
@@ -55,14 +62,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
55
62
  requirements:
56
63
  - - ">="
57
64
  - !ruby/object:Gem::Version
58
- version: '2.4'
65
+ version: '2.5'
59
66
  required_rubygems_version: !ruby/object:Gem::Requirement
60
67
  requirements:
61
68
  - - ">="
62
69
  - !ruby/object:Gem::Version
63
70
  version: '0'
64
71
  requirements: []
65
- rubygems_version: 3.6.0.dev
72
+ rubygems_version: 3.5.0.dev
73
+ signing_key:
66
74
  specification_version: 4
67
75
  summary: URI is a module providing classes to handle Uniform Resource Identifiers
68
76
  test_files: []