Add bin and edit workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s

This commit is contained in:
2026-09-16 13:11:16 -06:00
parent c8ac4fcae5
commit 4cee170d66
17576 changed files with 895740 additions and 2 deletions
+6
View File
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'weekly'
+46
View File
@@ -0,0 +1,46 @@
name: Deploy RDoc site to Pages
on:
push:
branches: [ 'master' ]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Ruby
uses: ruby/setup-ruby@250fcd6a742febb1123a77a841497ccaa8b9e939 # v1.152.0
with:
ruby-version: '3.2'
bundler-cache: true
- name: Setup Pages
id: pages
uses: actions/configure-pages@v3
- name: Build with RDoc
# Outputs to the './_site' directory by default
run: bundle exec rake rdoc
- name: Upload artifact
uses: actions/upload-pages-artifact@v2
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v2
+30
View File
@@ -0,0 +1,30 @@
name: CI
on: [push, pull_request]
jobs:
ruby-versions:
uses: ruby/actions/.github/workflows/ruby_versions.yml@master
with:
min_version: 2.5
build:
needs: ruby-versions
name: build (${{ matrix.ruby }} / ${{ matrix.os }})
strategy:
matrix:
ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }}
os: [ ubuntu-latest, macos-latest ]
exclude:
- ruby: 2.5
os: macos-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
- run: bundle install --jobs 4 --retry 3
- name: Run test
run: rake test
+10
View File
@@ -0,0 +1,10 @@
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
Gemfile.lock
/_site
+10
View File
@@ -0,0 +1,10 @@
source "https://rubygems.org"
gemspec
group :development do
gem "bundler"
gem "rake"
gem "test-unit"
gem "test-unit-ruby-core"
end
+22
View File
@@ -0,0 +1,22 @@
Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
+55
View File
@@ -0,0 +1,55 @@
# URI
[![CI](https://github.com/ruby/uri/actions/workflows/test.yml/badge.svg)](https://github.com/ruby/uri/actions/workflows/test.yml)
[![Yard Docs](https://img.shields.io/badge/docs-exist-blue.svg)](https://ruby.github.io/uri/)
URI is a module providing classes to handle Uniform Resource Identifiers [RFC2396](http://tools.ietf.org/html/rfc2396).
## Features
* Uniform way of handling URIs.
* Flexibility to introduce custom URI schemes.
* Flexibility to have an alternate URI::Parser (or just different patterns and regexp's).
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'uri'
```
And then execute:
$ bundle
Or install it yourself as:
$ gem install uri
## Usage
```ruby
require 'uri'
uri = URI("http://foo.com/posts?id=30&limit=5#time=1305298413")
#=> #<URI::HTTP http://foo.com/posts?id=30&limit=5#time=1305298413>
uri.scheme #=> "http"
uri.host #=> "foo.com"
uri.path #=> "/posts"
uri.query #=> "id=30&limit=5"
uri.fragment #=> "time=1305298413"
uri.to_s #=> "http://foo.com/posts?id=30&limit=5#time=1305298413"
```
## Development
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/uri.
+18
View File
@@ -0,0 +1,18 @@
require "bundler/gem_tasks"
require "rake/testtask"
Rake::TestTask.new(:test) do |t|
t.libs << "test/lib"
t.ruby_opts << "-rhelper"
t.test_files = FileList["test/**/test_*.rb"]
end
require "rdoc/task"
RDoc::Task.new do |doc|
doc.main = "README.md"
doc.title = "URI - handle Uniform Resource Identifiers"
doc.rdoc_files = FileList.new %w[lib README.md LICENSE.txt]
doc.rdoc_dir = "_site" # for github pages
end
task :default => :test
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env ruby
require "bundler/setup"
require "uri"
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start
require "irb"
IRB.start(__FILE__)
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx
bundle install
# Do any other automated setup that you need to do here
+104
View File
@@ -0,0 +1,104 @@
# frozen_string_literal: false
# URI is a module providing classes to handle Uniform Resource Identifiers
# (RFC2396[http://tools.ietf.org/html/rfc2396]).
#
# == Features
#
# * Uniform way of handling URIs.
# * Flexibility to introduce custom URI schemes.
# * Flexibility to have an alternate URI::Parser (or just different patterns
# and regexp's).
#
# == Basic example
#
# require 'uri'
#
# uri = URI("http://foo.com/posts?id=30&limit=5#time=1305298413")
# #=> #<URI::HTTP http://foo.com/posts?id=30&limit=5#time=1305298413>
#
# uri.scheme #=> "http"
# uri.host #=> "foo.com"
# uri.path #=> "/posts"
# uri.query #=> "id=30&limit=5"
# uri.fragment #=> "time=1305298413"
#
# uri.to_s #=> "http://foo.com/posts?id=30&limit=5#time=1305298413"
#
# == Adding custom URIs
#
# module URI
# class RSYNC < Generic
# DEFAULT_PORT = 873
# end
# register_scheme 'RSYNC', RSYNC
# end
# #=> URI::RSYNC
#
# URI.scheme_list
# #=> {"FILE"=>URI::File, "FTP"=>URI::FTP, "HTTP"=>URI::HTTP,
# # "HTTPS"=>URI::HTTPS, "LDAP"=>URI::LDAP, "LDAPS"=>URI::LDAPS,
# # "MAILTO"=>URI::MailTo, "RSYNC"=>URI::RSYNC}
#
# uri = URI("rsync://rsync.foo.com")
# #=> #<URI::RSYNC rsync://rsync.foo.com>
#
# == RFC References
#
# A good place to view an RFC spec is http://www.ietf.org/rfc.html.
#
# Here is a list of all related RFC's:
# - RFC822[http://tools.ietf.org/html/rfc822]
# - RFC1738[http://tools.ietf.org/html/rfc1738]
# - RFC2255[http://tools.ietf.org/html/rfc2255]
# - RFC2368[http://tools.ietf.org/html/rfc2368]
# - RFC2373[http://tools.ietf.org/html/rfc2373]
# - RFC2396[http://tools.ietf.org/html/rfc2396]
# - RFC2732[http://tools.ietf.org/html/rfc2732]
# - RFC3986[http://tools.ietf.org/html/rfc3986]
#
# == Class tree
#
# - URI::Generic (in uri/generic.rb)
# - URI::File - (in uri/file.rb)
# - URI::FTP - (in uri/ftp.rb)
# - URI::HTTP - (in uri/http.rb)
# - URI::HTTPS - (in uri/https.rb)
# - URI::LDAP - (in uri/ldap.rb)
# - URI::LDAPS - (in uri/ldaps.rb)
# - URI::MailTo - (in uri/mailto.rb)
# - URI::Parser - (in uri/common.rb)
# - URI::REGEXP - (in uri/common.rb)
# - URI::REGEXP::PATTERN - (in uri/common.rb)
# - URI::Util - (in uri/common.rb)
# - URI::Error - (in uri/common.rb)
# - URI::InvalidURIError - (in uri/common.rb)
# - URI::InvalidComponentError - (in uri/common.rb)
# - URI::BadURIError - (in uri/common.rb)
#
# == Copyright Info
#
# Author:: Akira Yamada <akira@ruby-lang.org>
# Documentation::
# Akira Yamada <akira@ruby-lang.org>
# Dmitry V. Sabanin <sdmitry@lrn.ru>
# Vincent Batts <vbatts@hashbangbash.com>
# License::
# Copyright (c) 2001 akira yamada <akira@ruby-lang.org>
# You can redistribute it and/or modify it under the same term as Ruby.
#
module URI
end
require_relative 'uri/version'
require_relative 'uri/common'
require_relative 'uri/generic'
require_relative 'uri/file'
require_relative 'uri/ftp'
require_relative 'uri/http'
require_relative 'uri/https'
require_relative 'uri/ldap'
require_relative 'uri/ldaps'
require_relative 'uri/mailto'
require_relative 'uri/ws'
require_relative 'uri/wss'
+855
View File
@@ -0,0 +1,855 @@
# frozen_string_literal: true
#--
# = uri/common.rb
#
# Author:: Akira Yamada <akira@ruby-lang.org>
# License::
# You can redistribute it and/or modify it under the same term as Ruby.
#
# See URI for general documentation
#
require_relative "rfc2396_parser"
require_relative "rfc3986_parser"
module URI
include RFC2396_REGEXP
REGEXP = RFC2396_REGEXP
Parser = RFC2396_Parser
RFC3986_PARSER = RFC3986_Parser.new
Ractor.make_shareable(RFC3986_PARSER) if defined?(Ractor)
RFC2396_PARSER = RFC2396_Parser.new
Ractor.make_shareable(RFC2396_PARSER) if defined?(Ractor)
# URI::Parser.new
DEFAULT_PARSER = Parser.new
DEFAULT_PARSER.pattern.each_pair do |sym, str|
unless REGEXP::PATTERN.const_defined?(sym)
REGEXP::PATTERN.const_set(sym, str)
end
end
DEFAULT_PARSER.regexp.each_pair do |sym, str|
const_set(sym, str)
end
Ractor.make_shareable(DEFAULT_PARSER) if defined?(Ractor)
module Util # :nodoc:
def make_components_hash(klass, array_hash)
tmp = {}
if array_hash.kind_of?(Array) &&
array_hash.size == klass.component.size - 1
klass.component[1..-1].each_index do |i|
begin
tmp[klass.component[i + 1]] = array_hash[i].clone
rescue TypeError
tmp[klass.component[i + 1]] = array_hash[i]
end
end
elsif array_hash.kind_of?(Hash)
array_hash.each do |key, value|
begin
tmp[key] = value.clone
rescue TypeError
tmp[key] = value
end
end
else
raise ArgumentError,
"expected Array of or Hash of components of #{klass} (#{klass.component[1..-1].join(', ')})"
end
tmp[:scheme] = klass.to_s.sub(/\A.*::/, '').downcase
return tmp
end
module_function :make_components_hash
end
module Schemes
end
private_constant :Schemes
# Registers the given +klass+ as the class to be instantiated
# when parsing a \URI with the given +scheme+:
#
# URI.register_scheme('MS_SEARCH', URI::Generic) # => URI::Generic
# URI.scheme_list['MS_SEARCH'] # => URI::Generic
#
# Note that after calling String#upcase on +scheme+, it must be a valid
# constant name.
def self.register_scheme(scheme, klass)
Schemes.const_set(scheme.to_s.upcase, klass)
end
# Returns a hash of the defined schemes:
#
# URI.scheme_list
# # =>
# {"MAILTO"=>URI::MailTo,
# "LDAPS"=>URI::LDAPS,
# "WS"=>URI::WS,
# "HTTP"=>URI::HTTP,
# "HTTPS"=>URI::HTTPS,
# "LDAP"=>URI::LDAP,
# "FILE"=>URI::File,
# "FTP"=>URI::FTP}
#
# Related: URI.register_scheme.
def self.scheme_list
Schemes.constants.map { |name|
[name.to_s.upcase, Schemes.const_get(name)]
}.to_h
end
INITIAL_SCHEMES = scheme_list
private_constant :INITIAL_SCHEMES
Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor)
# Returns a new object constructed from the given +scheme+, +arguments+,
# and +default+:
#
# - The new object is an instance of <tt>URI.scheme_list[scheme.upcase]</tt>.
# - The object is initialized by calling the class initializer
# using +scheme+ and +arguments+.
# See URI::Generic.new.
#
# Examples:
#
# values = ['john.doe', 'www.example.com', '123', nil, '/forum/questions/', nil, 'tag=networking&order=newest', 'top']
# URI.for('https', *values)
# # => #<URI::HTTPS https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
# URI.for('foo', *values, default: URI::HTTP)
# # => #<URI::HTTP foo://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
#
def self.for(scheme, *arguments, default: Generic)
const_name = scheme.to_s.upcase
uri_class = INITIAL_SCHEMES[const_name]
uri_class ||= if /\A[A-Z]\w*\z/.match?(const_name) && Schemes.const_defined?(const_name, false)
Schemes.const_get(const_name, false)
end
uri_class ||= default
return uri_class.new(scheme, *arguments)
end
#
# Base class for all URI exceptions.
#
class Error < StandardError; end
#
# Not a URI.
#
class InvalidURIError < Error; end
#
# Not a URI component.
#
class InvalidComponentError < Error; end
#
# URI is valid, bad usage is not.
#
class BadURIError < Error; end
# Returns a 9-element array representing the parts of the \URI
# formed from the string +uri+;
# each array element is a string or +nil+:
#
# names = %w[scheme userinfo host port registry path opaque query fragment]
# values = URI.split('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
# names.zip(values)
# # =>
# [["scheme", "https"],
# ["userinfo", "john.doe"],
# ["host", "www.example.com"],
# ["port", "123"],
# ["registry", nil],
# ["path", "/forum/questions/"],
# ["opaque", nil],
# ["query", "tag=networking&order=newest"],
# ["fragment", "top"]]
#
def self.split(uri)
RFC3986_PARSER.split(uri)
end
# Returns a new \URI object constructed from the given string +uri+:
#
# URI.parse('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
# # => #<URI::HTTPS https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
# URI.parse('http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
# # => #<URI::HTTP http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
#
# It's recommended to first ::escape string +uri+
# if it may contain invalid URI characters.
#
def self.parse(uri)
RFC3986_PARSER.parse(uri)
end
# Merges the given URI strings +str+
# per {RFC 2396}[https://www.rfc-editor.org/rfc/rfc2396.html].
#
# Each string in +str+ is converted to an
# {RFC3986 URI}[https://www.rfc-editor.org/rfc/rfc3986.html] before being merged.
#
# Examples:
#
# URI.join("http://example.com/","main.rbx")
# # => #<URI::HTTP http://example.com/main.rbx>
#
# URI.join('http://example.com', 'foo')
# # => #<URI::HTTP http://example.com/foo>
#
# URI.join('http://example.com', '/foo', '/bar')
# # => #<URI::HTTP http://example.com/bar>
#
# URI.join('http://example.com', '/foo', 'bar')
# # => #<URI::HTTP http://example.com/bar>
#
# URI.join('http://example.com', '/foo/', 'bar')
# # => #<URI::HTTP http://example.com/foo/bar>
#
def self.join(*str)
RFC3986_PARSER.join(*str)
end
#
# == Synopsis
#
# URI::extract(str[, schemes][,&blk])
#
# == Args
#
# +str+::
# String to extract URIs from.
# +schemes+::
# Limit URI matching to specific schemes.
#
# == Description
#
# Extracts URIs from a string. If block given, iterates through all matched URIs.
# Returns nil if block given or array with matches.
#
# == Usage
#
# require "uri"
#
# URI.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.")
# # => ["http://foo.example.com/bla", "mailto:test@example.com"]
#
def self.extract(str, schemes = nil, &block) # :nodoc:
warn "URI.extract is obsolete", uplevel: 1 if $VERBOSE
DEFAULT_PARSER.extract(str, schemes, &block)
end
#
# == Synopsis
#
# URI::regexp([match_schemes])
#
# == Args
#
# +match_schemes+::
# Array of schemes. If given, resulting regexp matches to URIs
# whose scheme is one of the match_schemes.
#
# == Description
#
# Returns a Regexp object which matches to URI-like strings.
# The Regexp object returned by this method includes arbitrary
# number of capture group (parentheses). Never rely on its number.
#
# == Usage
#
# require 'uri'
#
# # extract first URI from html_string
# html_string.slice(URI.regexp)
#
# # remove ftp URIs
# html_string.sub(URI.regexp(['ftp']), '')
#
# # You should not rely on the number of parentheses
# html_string.scan(URI.regexp) do |*matches|
# p $&
# end
#
def self.regexp(schemes = nil)# :nodoc:
warn "URI.regexp is obsolete", uplevel: 1 if $VERBOSE
DEFAULT_PARSER.make_regexp(schemes)
end
TBLENCWWWCOMP_ = {} # :nodoc:
256.times do |i|
TBLENCWWWCOMP_[-i.chr] = -('%%%02X' % i)
end
TBLENCURICOMP_ = TBLENCWWWCOMP_.dup.freeze
TBLENCWWWCOMP_[' '] = '+'
TBLENCWWWCOMP_.freeze
TBLDECWWWCOMP_ = {} # :nodoc:
256.times do |i|
h, l = i>>4, i&15
TBLDECWWWCOMP_[-('%%%X%X' % [h, l])] = -i.chr
TBLDECWWWCOMP_[-('%%%x%X' % [h, l])] = -i.chr
TBLDECWWWCOMP_[-('%%%X%x' % [h, l])] = -i.chr
TBLDECWWWCOMP_[-('%%%x%x' % [h, l])] = -i.chr
end
TBLDECWWWCOMP_['+'] = ' '
TBLDECWWWCOMP_.freeze
# Returns a URL-encoded string derived from the given string +str+.
#
# The returned string:
#
# - Preserves:
#
# - Characters <tt>'*'</tt>, <tt>'.'</tt>, <tt>'-'</tt>, and <tt>'_'</tt>.
# - Character in ranges <tt>'a'..'z'</tt>, <tt>'A'..'Z'</tt>,
# and <tt>'0'..'9'</tt>.
#
# Example:
#
# URI.encode_www_form_component('*.-_azAZ09')
# # => "*.-_azAZ09"
#
# - Converts:
#
# - Character <tt>' '</tt> to character <tt>'+'</tt>.
# - Any other character to "percent notation";
# the percent notation for character <i>c</i> is <tt>'%%%X' % c.ord</tt>.
#
# Example:
#
# URI.encode_www_form_component('Here are some punctuation characters: ,;?:')
# # => "Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A"
#
# Encoding:
#
# - If +str+ has encoding Encoding::ASCII_8BIT, argument +enc+ is ignored.
# - Otherwise +str+ is converted first to Encoding::UTF_8
# (with suitable character replacements),
# and then to encoding +enc+.
#
# In either case, the returned string has forced encoding Encoding::US_ASCII.
#
# Related: URI.encode_uri_component (encodes <tt>' '</tt> as <tt>'%20'</tt>).
def self.encode_www_form_component(str, enc=nil)
_encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP_, str, enc)
end
# Returns a string decoded from the given \URL-encoded string +str+.
#
# The given string is first encoded as Encoding::ASCII-8BIT (using String#b),
# then decoded (as below), and finally force-encoded to the given encoding +enc+.
#
# The returned string:
#
# - Preserves:
#
# - Characters <tt>'*'</tt>, <tt>'.'</tt>, <tt>'-'</tt>, and <tt>'_'</tt>.
# - Character in ranges <tt>'a'..'z'</tt>, <tt>'A'..'Z'</tt>,
# and <tt>'0'..'9'</tt>.
#
# Example:
#
# URI.decode_www_form_component('*.-_azAZ09')
# # => "*.-_azAZ09"
#
# - Converts:
#
# - Character <tt>'+'</tt> to character <tt>' '</tt>.
# - Each "percent notation" to an ASCII character.
#
# Example:
#
# URI.decode_www_form_component('Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A')
# # => "Here are some punctuation characters: ,;?:"
#
# Related: URI.decode_uri_component (preserves <tt>'+'</tt>).
def self.decode_www_form_component(str, enc=Encoding::UTF_8)
_decode_uri_component(/\+|%\h\h/, str, enc)
end
# Like URI.encode_www_form_component, except that <tt>' '</tt> (space)
# is encoded as <tt>'%20'</tt> (instead of <tt>'+'</tt>).
def self.encode_uri_component(str, enc=nil)
_encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCURICOMP_, str, enc)
end
# Like URI.decode_www_form_component, except that <tt>'+'</tt> is preserved.
def self.decode_uri_component(str, enc=Encoding::UTF_8)
_decode_uri_component(/%\h\h/, str, enc)
end
def self._encode_uri_component(regexp, table, str, enc)
str = str.to_s.dup
if str.encoding != Encoding::ASCII_8BIT
if enc && enc != Encoding::ASCII_8BIT
str.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace)
str.encode!(enc, fallback: ->(x){"&##{x.ord};"})
end
str.force_encoding(Encoding::ASCII_8BIT)
end
str.gsub!(regexp, table)
str.force_encoding(Encoding::US_ASCII)
end
private_class_method :_encode_uri_component
def self._decode_uri_component(regexp, str, enc)
raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/.match?(str)
str.b.gsub(regexp, TBLDECWWWCOMP_).force_encoding(enc)
end
private_class_method :_decode_uri_component
# Returns a URL-encoded string derived from the given
# {Enumerable}[https://docs.ruby-lang.org/en/master/Enumerable.html#module-Enumerable-label-Enumerable+in+Ruby+Classes]
# +enum+.
#
# The result is suitable for use as form data
# for an \HTTP request whose <tt>Content-Type</tt> is
# <tt>'application/x-www-form-urlencoded'</tt>.
#
# The returned string consists of the elements of +enum+,
# each converted to one or more URL-encoded strings,
# and all joined with character <tt>'&'</tt>.
#
# Simple examples:
#
# URI.encode_www_form([['foo', 0], ['bar', 1], ['baz', 2]])
# # => "foo=0&bar=1&baz=2"
# URI.encode_www_form({foo: 0, bar: 1, baz: 2})
# # => "foo=0&bar=1&baz=2"
#
# The returned string is formed using method URI.encode_www_form_component,
# which converts certain characters:
#
# URI.encode_www_form('f#o': '/', 'b-r': '$', 'b z': '@')
# # => "f%23o=%2F&b-r=%24&b+z=%40"
#
# When +enum+ is Array-like, each element +ele+ is converted to a field:
#
# - If +ele+ is an array of two or more elements,
# the field is formed from its first two elements
# (and any additional elements are ignored):
#
# name = URI.encode_www_form_component(ele[0], enc)
# value = URI.encode_www_form_component(ele[1], enc)
# "#{name}=#{value}"
#
# Examples:
#
# URI.encode_www_form([%w[foo bar], %w[baz bat bah]])
# # => "foo=bar&baz=bat"
# URI.encode_www_form([['foo', 0], ['bar', :baz, 'bat']])
# # => "foo=0&bar=baz"
#
# - If +ele+ is an array of one element,
# the field is formed from <tt>ele[0]</tt>:
#
# URI.encode_www_form_component(ele[0])
#
# Example:
#
# URI.encode_www_form([['foo'], [:bar], [0]])
# # => "foo&bar&0"
#
# - Otherwise the field is formed from +ele+:
#
# URI.encode_www_form_component(ele)
#
# Example:
#
# URI.encode_www_form(['foo', :bar, 0])
# # => "foo&bar&0"
#
# The elements of an Array-like +enum+ may be mixture:
#
# URI.encode_www_form([['foo', 0], ['bar', 1, 2], ['baz'], :bat])
# # => "foo=0&bar=1&baz&bat"
#
# When +enum+ is Hash-like,
# each +key+/+value+ pair is converted to one or more fields:
#
# - If +value+ is
# {Array-convertible}[https://docs.ruby-lang.org/en/master/implicit_conversion_rdoc.html#label-Array-Convertible+Objects],
# each element +ele+ in +value+ is paired with +key+ to form a field:
#
# name = URI.encode_www_form_component(key, enc)
# value = URI.encode_www_form_component(ele, enc)
# "#{name}=#{value}"
#
# Example:
#
# URI.encode_www_form({foo: [:bar, 1], baz: [:bat, :bam, 2]})
# # => "foo=bar&foo=1&baz=bat&baz=bam&baz=2"
#
# - Otherwise, +key+ and +value+ are paired to form a field:
#
# name = URI.encode_www_form_component(key, enc)
# value = URI.encode_www_form_component(value, enc)
# "#{name}=#{value}"
#
# Example:
#
# URI.encode_www_form({foo: 0, bar: 1, baz: 2})
# # => "foo=0&bar=1&baz=2"
#
# The elements of a Hash-like +enum+ may be mixture:
#
# URI.encode_www_form({foo: [0, 1], bar: 2})
# # => "foo=0&foo=1&bar=2"
#
def self.encode_www_form(enum, enc=nil)
enum.map do |k,v|
if v.nil?
encode_www_form_component(k, enc)
elsif v.respond_to?(:to_ary)
v.to_ary.map do |w|
str = encode_www_form_component(k, enc)
unless w.nil?
str << '='
str << encode_www_form_component(w, enc)
end
end.join('&')
else
str = encode_www_form_component(k, enc)
str << '='
str << encode_www_form_component(v, enc)
end
end.join('&')
end
# Returns name/value pairs derived from the given string +str+,
# which must be an ASCII string.
#
# The method may be used to decode the body of Net::HTTPResponse object +res+
# for which <tt>res['Content-Type']</tt> is <tt>'application/x-www-form-urlencoded'</tt>.
#
# The returned data is an array of 2-element subarrays;
# each subarray is a name/value pair (both are strings).
# Each returned string has encoding +enc+,
# and has had invalid characters removed via
# {String#scrub}[https://docs.ruby-lang.org/en/master/String.html#method-i-scrub].
#
# A simple example:
#
# URI.decode_www_form('foo=0&bar=1&baz')
# # => [["foo", "0"], ["bar", "1"], ["baz", ""]]
#
# The returned strings have certain conversions,
# similar to those performed in URI.decode_www_form_component:
#
# URI.decode_www_form('f%23o=%2F&b-r=%24&b+z=%40')
# # => [["f#o", "/"], ["b-r", "$"], ["b z", "@"]]
#
# The given string may contain consecutive separators:
#
# URI.decode_www_form('foo=0&&bar=1&&baz=2')
# # => [["foo", "0"], ["", ""], ["bar", "1"], ["", ""], ["baz", "2"]]
#
# A different separator may be specified:
#
# URI.decode_www_form('foo=0--bar=1--baz', separator: '--')
# # => [["foo", "0"], ["bar", "1"], ["baz", ""]]
#
def self.decode_www_form(str, enc=Encoding::UTF_8, separator: '&', use__charset_: false, isindex: false)
raise ArgumentError, "the input of #{self.name}.#{__method__} must be ASCII only string" unless str.ascii_only?
ary = []
return ary if str.empty?
enc = Encoding.find(enc)
str.b.each_line(separator) do |string|
string.chomp!(separator)
key, sep, val = string.partition('=')
if isindex
if sep.empty?
val = key
key = +''
end
isindex = false
end
if use__charset_ and key == '_charset_' and e = get_encoding(val)
enc = e
use__charset_ = false
end
key.gsub!(/\+|%\h\h/, TBLDECWWWCOMP_)
if val
val.gsub!(/\+|%\h\h/, TBLDECWWWCOMP_)
else
val = +''
end
ary << [key, val]
end
ary.each do |k, v|
k.force_encoding(enc)
k.scrub!
v.force_encoding(enc)
v.scrub!
end
ary
end
private
=begin command for WEB_ENCODINGS_
curl https://encoding.spec.whatwg.org/encodings.json|
ruby -rjson -e 'H={}
h={
"shift_jis"=>"Windows-31J",
"euc-jp"=>"cp51932",
"iso-2022-jp"=>"cp50221",
"x-mac-cyrillic"=>"macCyrillic",
}
JSON($<.read).map{|x|x["encodings"]}.flatten.each{|x|
Encoding.find(n=h.fetch(n=x["name"].downcase,n))rescue next
x["labels"].each{|y|H[y]=n}
}
puts "{"
H.each{|k,v|puts %[ #{k.dump}=>#{v.dump},]}
puts "}"
'
=end
WEB_ENCODINGS_ = {
"unicode-1-1-utf-8"=>"utf-8",
"utf-8"=>"utf-8",
"utf8"=>"utf-8",
"866"=>"ibm866",
"cp866"=>"ibm866",
"csibm866"=>"ibm866",
"ibm866"=>"ibm866",
"csisolatin2"=>"iso-8859-2",
"iso-8859-2"=>"iso-8859-2",
"iso-ir-101"=>"iso-8859-2",
"iso8859-2"=>"iso-8859-2",
"iso88592"=>"iso-8859-2",
"iso_8859-2"=>"iso-8859-2",
"iso_8859-2:1987"=>"iso-8859-2",
"l2"=>"iso-8859-2",
"latin2"=>"iso-8859-2",
"csisolatin3"=>"iso-8859-3",
"iso-8859-3"=>"iso-8859-3",
"iso-ir-109"=>"iso-8859-3",
"iso8859-3"=>"iso-8859-3",
"iso88593"=>"iso-8859-3",
"iso_8859-3"=>"iso-8859-3",
"iso_8859-3:1988"=>"iso-8859-3",
"l3"=>"iso-8859-3",
"latin3"=>"iso-8859-3",
"csisolatin4"=>"iso-8859-4",
"iso-8859-4"=>"iso-8859-4",
"iso-ir-110"=>"iso-8859-4",
"iso8859-4"=>"iso-8859-4",
"iso88594"=>"iso-8859-4",
"iso_8859-4"=>"iso-8859-4",
"iso_8859-4:1988"=>"iso-8859-4",
"l4"=>"iso-8859-4",
"latin4"=>"iso-8859-4",
"csisolatincyrillic"=>"iso-8859-5",
"cyrillic"=>"iso-8859-5",
"iso-8859-5"=>"iso-8859-5",
"iso-ir-144"=>"iso-8859-5",
"iso8859-5"=>"iso-8859-5",
"iso88595"=>"iso-8859-5",
"iso_8859-5"=>"iso-8859-5",
"iso_8859-5:1988"=>"iso-8859-5",
"arabic"=>"iso-8859-6",
"asmo-708"=>"iso-8859-6",
"csiso88596e"=>"iso-8859-6",
"csiso88596i"=>"iso-8859-6",
"csisolatinarabic"=>"iso-8859-6",
"ecma-114"=>"iso-8859-6",
"iso-8859-6"=>"iso-8859-6",
"iso-8859-6-e"=>"iso-8859-6",
"iso-8859-6-i"=>"iso-8859-6",
"iso-ir-127"=>"iso-8859-6",
"iso8859-6"=>"iso-8859-6",
"iso88596"=>"iso-8859-6",
"iso_8859-6"=>"iso-8859-6",
"iso_8859-6:1987"=>"iso-8859-6",
"csisolatingreek"=>"iso-8859-7",
"ecma-118"=>"iso-8859-7",
"elot_928"=>"iso-8859-7",
"greek"=>"iso-8859-7",
"greek8"=>"iso-8859-7",
"iso-8859-7"=>"iso-8859-7",
"iso-ir-126"=>"iso-8859-7",
"iso8859-7"=>"iso-8859-7",
"iso88597"=>"iso-8859-7",
"iso_8859-7"=>"iso-8859-7",
"iso_8859-7:1987"=>"iso-8859-7",
"sun_eu_greek"=>"iso-8859-7",
"csiso88598e"=>"iso-8859-8",
"csisolatinhebrew"=>"iso-8859-8",
"hebrew"=>"iso-8859-8",
"iso-8859-8"=>"iso-8859-8",
"iso-8859-8-e"=>"iso-8859-8",
"iso-ir-138"=>"iso-8859-8",
"iso8859-8"=>"iso-8859-8",
"iso88598"=>"iso-8859-8",
"iso_8859-8"=>"iso-8859-8",
"iso_8859-8:1988"=>"iso-8859-8",
"visual"=>"iso-8859-8",
"csisolatin6"=>"iso-8859-10",
"iso-8859-10"=>"iso-8859-10",
"iso-ir-157"=>"iso-8859-10",
"iso8859-10"=>"iso-8859-10",
"iso885910"=>"iso-8859-10",
"l6"=>"iso-8859-10",
"latin6"=>"iso-8859-10",
"iso-8859-13"=>"iso-8859-13",
"iso8859-13"=>"iso-8859-13",
"iso885913"=>"iso-8859-13",
"iso-8859-14"=>"iso-8859-14",
"iso8859-14"=>"iso-8859-14",
"iso885914"=>"iso-8859-14",
"csisolatin9"=>"iso-8859-15",
"iso-8859-15"=>"iso-8859-15",
"iso8859-15"=>"iso-8859-15",
"iso885915"=>"iso-8859-15",
"iso_8859-15"=>"iso-8859-15",
"l9"=>"iso-8859-15",
"iso-8859-16"=>"iso-8859-16",
"cskoi8r"=>"koi8-r",
"koi"=>"koi8-r",
"koi8"=>"koi8-r",
"koi8-r"=>"koi8-r",
"koi8_r"=>"koi8-r",
"koi8-ru"=>"koi8-u",
"koi8-u"=>"koi8-u",
"dos-874"=>"windows-874",
"iso-8859-11"=>"windows-874",
"iso8859-11"=>"windows-874",
"iso885911"=>"windows-874",
"tis-620"=>"windows-874",
"windows-874"=>"windows-874",
"cp1250"=>"windows-1250",
"windows-1250"=>"windows-1250",
"x-cp1250"=>"windows-1250",
"cp1251"=>"windows-1251",
"windows-1251"=>"windows-1251",
"x-cp1251"=>"windows-1251",
"ansi_x3.4-1968"=>"windows-1252",
"ascii"=>"windows-1252",
"cp1252"=>"windows-1252",
"cp819"=>"windows-1252",
"csisolatin1"=>"windows-1252",
"ibm819"=>"windows-1252",
"iso-8859-1"=>"windows-1252",
"iso-ir-100"=>"windows-1252",
"iso8859-1"=>"windows-1252",
"iso88591"=>"windows-1252",
"iso_8859-1"=>"windows-1252",
"iso_8859-1:1987"=>"windows-1252",
"l1"=>"windows-1252",
"latin1"=>"windows-1252",
"us-ascii"=>"windows-1252",
"windows-1252"=>"windows-1252",
"x-cp1252"=>"windows-1252",
"cp1253"=>"windows-1253",
"windows-1253"=>"windows-1253",
"x-cp1253"=>"windows-1253",
"cp1254"=>"windows-1254",
"csisolatin5"=>"windows-1254",
"iso-8859-9"=>"windows-1254",
"iso-ir-148"=>"windows-1254",
"iso8859-9"=>"windows-1254",
"iso88599"=>"windows-1254",
"iso_8859-9"=>"windows-1254",
"iso_8859-9:1989"=>"windows-1254",
"l5"=>"windows-1254",
"latin5"=>"windows-1254",
"windows-1254"=>"windows-1254",
"x-cp1254"=>"windows-1254",
"cp1255"=>"windows-1255",
"windows-1255"=>"windows-1255",
"x-cp1255"=>"windows-1255",
"cp1256"=>"windows-1256",
"windows-1256"=>"windows-1256",
"x-cp1256"=>"windows-1256",
"cp1257"=>"windows-1257",
"windows-1257"=>"windows-1257",
"x-cp1257"=>"windows-1257",
"cp1258"=>"windows-1258",
"windows-1258"=>"windows-1258",
"x-cp1258"=>"windows-1258",
"x-mac-cyrillic"=>"macCyrillic",
"x-mac-ukrainian"=>"macCyrillic",
"chinese"=>"gbk",
"csgb2312"=>"gbk",
"csiso58gb231280"=>"gbk",
"gb2312"=>"gbk",
"gb_2312"=>"gbk",
"gb_2312-80"=>"gbk",
"gbk"=>"gbk",
"iso-ir-58"=>"gbk",
"x-gbk"=>"gbk",
"gb18030"=>"gb18030",
"big5"=>"big5",
"big5-hkscs"=>"big5",
"cn-big5"=>"big5",
"csbig5"=>"big5",
"x-x-big5"=>"big5",
"cseucpkdfmtjapanese"=>"cp51932",
"euc-jp"=>"cp51932",
"x-euc-jp"=>"cp51932",
"csiso2022jp"=>"cp50221",
"iso-2022-jp"=>"cp50221",
"csshiftjis"=>"Windows-31J",
"ms932"=>"Windows-31J",
"ms_kanji"=>"Windows-31J",
"shift-jis"=>"Windows-31J",
"shift_jis"=>"Windows-31J",
"sjis"=>"Windows-31J",
"windows-31j"=>"Windows-31J",
"x-sjis"=>"Windows-31J",
"cseuckr"=>"euc-kr",
"csksc56011987"=>"euc-kr",
"euc-kr"=>"euc-kr",
"iso-ir-149"=>"euc-kr",
"korean"=>"euc-kr",
"ks_c_5601-1987"=>"euc-kr",
"ks_c_5601-1989"=>"euc-kr",
"ksc5601"=>"euc-kr",
"ksc_5601"=>"euc-kr",
"windows-949"=>"euc-kr",
"utf-16be"=>"utf-16be",
"utf-16"=>"utf-16le",
"utf-16le"=>"utf-16le",
} # :nodoc:
Ractor.make_shareable(WEB_ENCODINGS_) if defined?(Ractor)
# :nodoc:
# return encoding or nil
# http://encoding.spec.whatwg.org/#concept-encoding-get
def self.get_encoding(label)
Encoding.find(WEB_ENCODINGS_[label.to_str.strip.downcase]) rescue nil
end
end # module URI
module Kernel
#
# Returns a \URI object derived from the given +uri+,
# which may be a \URI string or an existing \URI object:
#
# # Returns a new URI.
# uri = URI('http://github.com/ruby/ruby')
# # => #<URI::HTTP http://github.com/ruby/ruby>
# # Returns the given URI.
# URI(uri)
# # => #<URI::HTTP http://github.com/ruby/ruby>
#
def URI(uri)
if uri.is_a?(URI::Generic)
uri
elsif uri = String.try_convert(uri)
URI.parse(uri)
else
raise ArgumentError,
"bad argument (expected URI object or URI string)"
end
end
module_function :URI
end
+100
View File
@@ -0,0 +1,100 @@
# frozen_string_literal: true
require_relative 'generic'
module URI
#
# The "file" URI is defined by RFC8089.
#
class File < Generic
# A Default port of nil for URI::File.
DEFAULT_PORT = nil
#
# An Array of the available components for URI::File.
#
COMPONENT = [
:scheme,
:host,
:path
].freeze
#
# == Description
#
# Creates a new URI::File object from components, with syntax checking.
#
# The components accepted are +host+ and +path+.
#
# The components should be provided either as an Array, or as a Hash
# with keys formed by preceding the component names with a colon.
#
# If an Array is used, the components must be passed in the
# order <code>[host, path]</code>.
#
# A path from e.g. the File class should be escaped before
# being passed.
#
# Examples:
#
# require 'uri'
#
# uri1 = URI::File.build(['host.example.com', '/path/file.zip'])
# uri1.to_s # => "file://host.example.com/path/file.zip"
#
# uri2 = URI::File.build({:host => 'host.example.com',
# :path => '/ruby/src'})
# uri2.to_s # => "file://host.example.com/ruby/src"
#
# uri3 = URI::File.build({:path => URI::escape('/path/my file.txt')})
# uri3.to_s # => "file:///path/my%20file.txt"
#
def self.build(args)
tmp = Util::make_components_hash(self, args)
super(tmp)
end
# Protected setter for the host component +v+.
#
# See also URI::Generic.host=.
#
def set_host(v)
v = "" if v.nil? || v == "localhost"
@host = v
end
# do nothing
def set_port(v)
end
# raise InvalidURIError
def check_userinfo(user)
raise URI::InvalidURIError, "can not set userinfo for file URI"
end
# raise InvalidURIError
def check_user(user)
raise URI::InvalidURIError, "can not set user for file URI"
end
# raise InvalidURIError
def check_password(user)
raise URI::InvalidURIError, "can not set password for file URI"
end
# do nothing
def set_userinfo(v)
end
# do nothing
def set_user(v)
end
# do nothing
def set_password(v)
end
end
register_scheme 'FILE', File
end
+267
View File
@@ -0,0 +1,267 @@
# frozen_string_literal: false
# = uri/ftp.rb
#
# Author:: Akira Yamada <akira@ruby-lang.org>
# License:: You can redistribute it and/or modify it under the same term as Ruby.
#
# See URI for general documentation
#
require_relative 'generic'
module URI
#
# FTP URI syntax is defined by RFC1738 section 3.2.
#
# This class will be redesigned because of difference of implementations;
# the structure of its path. draft-hoffman-ftp-uri-04 is a draft but it
# is a good summary about the de facto spec.
# http://tools.ietf.org/html/draft-hoffman-ftp-uri-04
#
class FTP < Generic
# A Default port of 21 for URI::FTP.
DEFAULT_PORT = 21
#
# An Array of the available components for URI::FTP.
#
COMPONENT = [
:scheme,
:userinfo, :host, :port,
:path, :typecode
].freeze
#
# Typecode is "a", "i", or "d".
#
# * "a" indicates a text file (the FTP command was ASCII)
# * "i" indicates a binary file (FTP command IMAGE)
# * "d" indicates the contents of a directory should be displayed
#
TYPECODE = ['a', 'i', 'd'].freeze
# Typecode prefix ";type=".
TYPECODE_PREFIX = ';type='.freeze
def self.new2(user, password, host, port, path,
typecode = nil, arg_check = true) # :nodoc:
# Do not use this method! Not tested. [Bug #7301]
# This methods remains just for compatibility,
# Keep it undocumented until the active maintainer is assigned.
typecode = nil if typecode.size == 0
if typecode && !TYPECODE.include?(typecode)
raise ArgumentError,
"bad typecode is specified: #{typecode}"
end
# do escape
self.new('ftp',
[user, password],
host, port, nil,
typecode ? path + TYPECODE_PREFIX + typecode : path,
nil, nil, nil, arg_check)
end
#
# == Description
#
# Creates a new URI::FTP object from components, with syntax checking.
#
# The components accepted are +userinfo+, +host+, +port+, +path+, and
# +typecode+.
#
# The components should be provided either as an Array, or as a Hash
# with keys formed by preceding the component names with a colon.
#
# If an Array is used, the components must be passed in the
# order <code>[userinfo, host, port, path, typecode]</code>.
#
# If the path supplied is absolute, it will be escaped in order to
# make it absolute in the URI.
#
# Examples:
#
# require 'uri'
#
# uri1 = URI::FTP.build(['user:password', 'ftp.example.com', nil,
# '/path/file.zip', 'i'])
# uri1.to_s # => "ftp://user:password@ftp.example.com/%2Fpath/file.zip;type=i"
#
# uri2 = URI::FTP.build({:host => 'ftp.example.com',
# :path => 'ruby/src'})
# uri2.to_s # => "ftp://ftp.example.com/ruby/src"
#
def self.build(args)
# Fix the incoming path to be generic URL syntax
# FTP path -> URL path
# foo/bar /foo/bar
# /foo/bar /%2Ffoo/bar
#
if args.kind_of?(Array)
args[3] = '/' + args[3].sub(/^\//, '%2F')
else
args[:path] = '/' + args[:path].sub(/^\//, '%2F')
end
tmp = Util::make_components_hash(self, args)
if tmp[:typecode]
if tmp[:typecode].size == 1
tmp[:typecode] = TYPECODE_PREFIX + tmp[:typecode]
end
tmp[:path] << tmp[:typecode]
end
return super(tmp)
end
#
# == Description
#
# Creates a new URI::FTP object from generic URL components with no
# syntax checking.
#
# Unlike build(), this method does not escape the path component as
# required by RFC1738; instead it is treated as per RFC2396.
#
# Arguments are +scheme+, +userinfo+, +host+, +port+, +registry+, +path+,
# +opaque+, +query+, and +fragment+, in that order.
#
def initialize(scheme,
userinfo, host, port, registry,
path, opaque,
query,
fragment,
parser = nil,
arg_check = false)
raise InvalidURIError unless path
path = path.sub(/^\//,'')
path.sub!(/^%2F/,'/')
super(scheme, userinfo, host, port, registry, path, opaque,
query, fragment, parser, arg_check)
@typecode = nil
if tmp = @path.index(TYPECODE_PREFIX)
typecode = @path[tmp + TYPECODE_PREFIX.size..-1]
@path = @path[0..tmp - 1]
if arg_check
self.typecode = typecode
else
self.set_typecode(typecode)
end
end
end
# typecode accessor.
#
# See URI::FTP::COMPONENT.
attr_reader :typecode
# Validates typecode +v+,
# returns +true+ or +false+.
#
def check_typecode(v)
if TYPECODE.include?(v)
return true
else
raise InvalidComponentError,
"bad typecode(expected #{TYPECODE.join(', ')}): #{v}"
end
end
private :check_typecode
# Private setter for the typecode +v+.
#
# See also URI::FTP.typecode=.
#
def set_typecode(v)
@typecode = v
end
protected :set_typecode
#
# == Args
#
# +v+::
# String
#
# == Description
#
# Public setter for the typecode +v+
# (with validation).
#
# See also URI::FTP.check_typecode.
#
# == Usage
#
# require 'uri'
#
# uri = URI.parse("ftp://john@ftp.example.com/my_file.img")
# #=> #<URI::FTP ftp://john@ftp.example.com/my_file.img>
# uri.typecode = "i"
# uri
# #=> #<URI::FTP ftp://john@ftp.example.com/my_file.img;type=i>
#
def typecode=(typecode)
check_typecode(typecode)
set_typecode(typecode)
typecode
end
def merge(oth) # :nodoc:
tmp = super(oth)
if self != tmp
tmp.set_typecode(oth.typecode)
end
return tmp
end
# Returns the path from an FTP URI.
#
# RFC 1738 specifically states that the path for an FTP URI does not
# include the / which separates the URI path from the URI host. Example:
#
# <code>ftp://ftp.example.com/pub/ruby</code>
#
# The above URI indicates that the client should connect to
# ftp.example.com then cd to pub/ruby from the initial login directory.
#
# If you want to cd to an absolute directory, you must include an
# escaped / (%2F) in the path. Example:
#
# <code>ftp://ftp.example.com/%2Fpub/ruby</code>
#
# This method will then return "/pub/ruby".
#
def path
return @path.sub(/^\//,'').sub(/^%2F/,'/')
end
# Private setter for the path of the URI::FTP.
def set_path(v)
super("/" + v.sub(/^\//, "%2F"))
end
protected :set_path
# Returns a String representation of the URI::FTP.
def to_s
save_path = nil
if @typecode
save_path = @path
@path = @path + TYPECODE_PREFIX + @typecode
end
str = super
if @typecode
@path = save_path
end
return str
end
end
register_scheme 'FTP', FTP
end
File diff suppressed because it is too large Load Diff
+125
View File
@@ -0,0 +1,125 @@
# frozen_string_literal: false
# = uri/http.rb
#
# Author:: Akira Yamada <akira@ruby-lang.org>
# License:: You can redistribute it and/or modify it under the same term as Ruby.
#
# See URI for general documentation
#
require_relative 'generic'
module URI
#
# The syntax of HTTP URIs is defined in RFC1738 section 3.3.
#
# Note that the Ruby URI library allows HTTP URLs containing usernames and
# passwords. This is not legal as per the RFC, but used to be
# supported in Internet Explorer 5 and 6, before the MS04-004 security
# update. See <URL:http://support.microsoft.com/kb/834489>.
#
class HTTP < Generic
# A Default port of 80 for URI::HTTP.
DEFAULT_PORT = 80
# An Array of the available components for URI::HTTP.
COMPONENT = %i[
scheme
userinfo host port
path
query
fragment
].freeze
#
# == Description
#
# Creates a new URI::HTTP object from components, with syntax checking.
#
# The components accepted are userinfo, host, port, path, query, and
# fragment.
#
# The components should be provided either as an Array, or as a Hash
# with keys formed by preceding the component names with a colon.
#
# If an Array is used, the components must be passed in the
# order <code>[userinfo, host, port, path, query, fragment]</code>.
#
# Example:
#
# uri = URI::HTTP.build(host: 'www.example.com', path: '/foo/bar')
#
# uri = URI::HTTP.build([nil, "www.example.com", nil, "/path",
# "query", 'fragment'])
#
# Currently, if passed userinfo components this method generates
# invalid HTTP URIs as per RFC 1738.
#
def self.build(args)
tmp = Util.make_components_hash(self, args)
super(tmp)
end
#
# == Description
#
# Returns the full path for an HTTP request, as required by Net::HTTP::Get.
#
# If the URI contains a query, the full path is URI#path + '?' + URI#query.
# Otherwise, the path is simply URI#path.
#
# Example:
#
# uri = URI::HTTP.build(path: '/foo/bar', query: 'test=true')
# uri.request_uri # => "/foo/bar?test=true"
#
def request_uri
return unless @path
url = @query ? "#@path?#@query" : @path.dup
url.start_with?(?/.freeze) ? url : ?/ + url
end
#
# == Description
#
# Returns the authority for an HTTP uri, as defined in
# https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.
#
#
# Example:
#
# URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').authority #=> "www.example.com"
# URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').authority #=> "www.example.com:8000"
# URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').authority #=> "www.example.com"
#
def authority
if port == default_port
host
else
"#{host}:#{port}"
end
end
#
# == Description
#
# Returns the origin for an HTTP uri, as defined in
# https://datatracker.ietf.org/doc/html/rfc6454.
#
#
# Example:
#
# URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').origin #=> "http://www.example.com"
# URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').origin #=> "http://www.example.com:8000"
# URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').origin #=> "http://www.example.com"
# URI::HTTPS.build(host: 'www.example.com', path: '/foo/bar').origin #=> "https://www.example.com"
#
def origin
"#{scheme}://#{authority}"
end
end
register_scheme 'HTTP', HTTP
end
+23
View File
@@ -0,0 +1,23 @@
# frozen_string_literal: false
# = uri/https.rb
#
# Author:: Akira Yamada <akira@ruby-lang.org>
# License:: You can redistribute it and/or modify it under the same term as Ruby.
#
# See URI for general documentation
#
require_relative 'http'
module URI
# The default port for HTTPS URIs is 443, and the scheme is 'https:' rather
# than 'http:'. Other than that, HTTPS URIs are identical to HTTP URIs;
# see URI::HTTP.
class HTTPS < HTTP
# A Default port of 443 for URI::HTTPS
DEFAULT_PORT = 443
end
register_scheme 'HTTPS', HTTPS
end
+261
View File
@@ -0,0 +1,261 @@
# frozen_string_literal: false
# = uri/ldap.rb
#
# Author::
# Takaaki Tateishi <ttate@jaist.ac.jp>
# Akira Yamada <akira@ruby-lang.org>
# License::
# URI::LDAP is copyrighted free software by Takaaki Tateishi and Akira Yamada.
# You can redistribute it and/or modify it under the same term as Ruby.
#
# See URI for general documentation
#
require_relative 'generic'
module URI
#
# LDAP URI SCHEMA (described in RFC2255).
#--
# ldap://<host>/<dn>[?<attrs>[?<scope>[?<filter>[?<extensions>]]]]
#++
class LDAP < Generic
# A Default port of 389 for URI::LDAP.
DEFAULT_PORT = 389
# An Array of the available components for URI::LDAP.
COMPONENT = [
:scheme,
:host, :port,
:dn,
:attributes,
:scope,
:filter,
:extensions,
].freeze
# Scopes available for the starting point.
#
# * SCOPE_BASE - the Base DN
# * SCOPE_ONE - one level under the Base DN, not including the base DN and
# not including any entries under this
# * SCOPE_SUB - subtrees, all entries at all levels
#
SCOPE = [
SCOPE_ONE = 'one',
SCOPE_SUB = 'sub',
SCOPE_BASE = 'base',
].freeze
#
# == Description
#
# Creates a new URI::LDAP object from components, with syntax checking.
#
# The components accepted are host, port, dn, attributes,
# scope, filter, and extensions.
#
# The components should be provided either as an Array, or as a Hash
# with keys formed by preceding the component names with a colon.
#
# If an Array is used, the components must be passed in the
# order <code>[host, port, dn, attributes, scope, filter, extensions]</code>.
#
# Example:
#
# uri = URI::LDAP.build({:host => 'ldap.example.com',
# :dn => '/dc=example'})
#
# uri = URI::LDAP.build(["ldap.example.com", nil,
# "/dc=example;dc=com", "query", nil, nil, nil])
#
def self.build(args)
tmp = Util::make_components_hash(self, args)
if tmp[:dn]
tmp[:path] = tmp[:dn]
end
query = []
[:extensions, :filter, :scope, :attributes].collect do |x|
next if !tmp[x] && query.size == 0
query.unshift(tmp[x])
end
tmp[:query] = query.join('?')
return super(tmp)
end
#
# == Description
#
# Creates a new URI::LDAP object from generic URI components as per
# RFC 2396. No LDAP-specific syntax checking is performed.
#
# Arguments are +scheme+, +userinfo+, +host+, +port+, +registry+, +path+,
# +opaque+, +query+, and +fragment+, in that order.
#
# Example:
#
# uri = URI::LDAP.new("ldap", nil, "ldap.example.com", nil, nil,
# "/dc=example;dc=com", nil, "query", nil)
#
# See also URI::Generic.new.
#
def initialize(*arg)
super(*arg)
if @fragment
raise InvalidURIError, 'bad LDAP URL'
end
parse_dn
parse_query
end
# Private method to cleanup +dn+ from using the +path+ component attribute.
def parse_dn
raise InvalidURIError, 'bad LDAP URL' unless @path
@dn = @path[1..-1]
end
private :parse_dn
# Private method to cleanup +attributes+, +scope+, +filter+, and +extensions+
# from using the +query+ component attribute.
def parse_query
@attributes = nil
@scope = nil
@filter = nil
@extensions = nil
if @query
attrs, scope, filter, extensions = @query.split('?')
@attributes = attrs if attrs && attrs.size > 0
@scope = scope if scope && scope.size > 0
@filter = filter if filter && filter.size > 0
@extensions = extensions if extensions && extensions.size > 0
end
end
private :parse_query
# Private method to assemble +query+ from +attributes+, +scope+, +filter+, and +extensions+.
def build_path_query
@path = '/' + @dn
query = []
[@extensions, @filter, @scope, @attributes].each do |x|
next if !x && query.size == 0
query.unshift(x)
end
@query = query.join('?')
end
private :build_path_query
# Returns dn.
def dn
@dn
end
# Private setter for dn +val+.
def set_dn(val)
@dn = val
build_path_query
@dn
end
protected :set_dn
# Setter for dn +val+.
def dn=(val)
set_dn(val)
val
end
# Returns attributes.
def attributes
@attributes
end
# Private setter for attributes +val+.
def set_attributes(val)
@attributes = val
build_path_query
@attributes
end
protected :set_attributes
# Setter for attributes +val+.
def attributes=(val)
set_attributes(val)
val
end
# Returns scope.
def scope
@scope
end
# Private setter for scope +val+.
def set_scope(val)
@scope = val
build_path_query
@scope
end
protected :set_scope
# Setter for scope +val+.
def scope=(val)
set_scope(val)
val
end
# Returns filter.
def filter
@filter
end
# Private setter for filter +val+.
def set_filter(val)
@filter = val
build_path_query
@filter
end
protected :set_filter
# Setter for filter +val+.
def filter=(val)
set_filter(val)
val
end
# Returns extensions.
def extensions
@extensions
end
# Private setter for extensions +val+.
def set_extensions(val)
@extensions = val
build_path_query
@extensions
end
protected :set_extensions
# Setter for extensions +val+.
def extensions=(val)
set_extensions(val)
val
end
# Checks if URI has a path.
# For URI::LDAP this will return +false+.
def hierarchical?
false
end
end
register_scheme 'LDAP', LDAP
end
+22
View File
@@ -0,0 +1,22 @@
# frozen_string_literal: false
# = uri/ldap.rb
#
# License:: You can redistribute it and/or modify it under the same term as Ruby.
#
# See URI for general documentation
#
require_relative 'ldap'
module URI
# The default port for LDAPS URIs is 636, and the scheme is 'ldaps:' rather
# than 'ldap:'. Other than that, LDAPS URIs are identical to LDAP URIs;
# see URI::LDAP.
class LDAPS < LDAP
# A Default port of 636 for URI::LDAPS
DEFAULT_PORT = 636
end
register_scheme 'LDAPS', LDAPS
end
+293
View File
@@ -0,0 +1,293 @@
# frozen_string_literal: false
# = uri/mailto.rb
#
# Author:: Akira Yamada <akira@ruby-lang.org>
# License:: You can redistribute it and/or modify it under the same term as Ruby.
#
# See URI for general documentation
#
require_relative 'generic'
module URI
#
# RFC6068, the mailto URL scheme.
#
class MailTo < Generic
include RFC2396_REGEXP
# A Default port of nil for URI::MailTo.
DEFAULT_PORT = nil
# An Array of the available components for URI::MailTo.
COMPONENT = [ :scheme, :to, :headers ].freeze
# :stopdoc:
# "hname" and "hvalue" are encodings of an RFC 822 header name and
# value, respectively. As with "to", all URL reserved characters must
# be encoded.
#
# "#mailbox" is as specified in RFC 822 [RFC822]. This means that it
# consists of zero or more comma-separated mail addresses, possibly
# including "phrase" and "comment" components. Note that all URL
# reserved characters in "to" must be encoded: in particular,
# parentheses, commas, and the percent sign ("%"), which commonly occur
# in the "mailbox" syntax.
#
# Within mailto URLs, the characters "?", "=", "&" are reserved.
# ; RFC 6068
# hfields = "?" hfield *( "&" hfield )
# hfield = hfname "=" hfvalue
# hfname = *qchar
# hfvalue = *qchar
# qchar = unreserved / pct-encoded / some-delims
# some-delims = "!" / "$" / "'" / "(" / ")" / "*"
# / "+" / "," / ";" / ":" / "@"
#
# ; RFC3986
# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
# pct-encoded = "%" HEXDIG HEXDIG
HEADER_REGEXP = /\A(?<hfield>(?:%\h\h|[!$'-.0-;@-Z_a-z~])*=(?:%\h\h|[!$'-.0-;@-Z_a-z~])*)(?:&\g<hfield>)*\z/
# practical regexp for email address
# https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
EMAIL_REGEXP = /\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\z/
# :startdoc:
#
# == Description
#
# Creates a new URI::MailTo object from components, with syntax checking.
#
# Components can be provided as an Array or Hash. If an Array is used,
# the components must be supplied as <code>[to, headers]</code>.
#
# If a Hash is used, the keys are the component names preceded by colons.
#
# The headers can be supplied as a pre-encoded string, such as
# <code>"subject=subscribe&cc=address"</code>, or as an Array of Arrays
# like <code>[['subject', 'subscribe'], ['cc', 'address']]</code>.
#
# Examples:
#
# require 'uri'
#
# m1 = URI::MailTo.build(['joe@example.com', 'subject=Ruby'])
# m1.to_s # => "mailto:joe@example.com?subject=Ruby"
#
# m2 = URI::MailTo.build(['john@example.com', [['Subject', 'Ruby'], ['Cc', 'jack@example.com']]])
# m2.to_s # => "mailto:john@example.com?Subject=Ruby&Cc=jack@example.com"
#
# m3 = URI::MailTo.build({:to => 'listman@example.com', :headers => [['subject', 'subscribe']]})
# m3.to_s # => "mailto:listman@example.com?subject=subscribe"
#
def self.build(args)
tmp = Util.make_components_hash(self, args)
case tmp[:to]
when Array
tmp[:opaque] = tmp[:to].join(',')
when String
tmp[:opaque] = tmp[:to].dup
else
tmp[:opaque] = ''
end
if tmp[:headers]
query =
case tmp[:headers]
when Array
tmp[:headers].collect { |x|
if x.kind_of?(Array)
x[0] + '=' + x[1..-1].join
else
x.to_s
end
}.join('&')
when Hash
tmp[:headers].collect { |h,v|
h + '=' + v
}.join('&')
else
tmp[:headers].to_s
end
unless query.empty?
tmp[:opaque] << '?' << query
end
end
super(tmp)
end
#
# == Description
#
# Creates a new URI::MailTo object from generic URL components with
# no syntax checking.
#
# This method is usually called from URI::parse, which checks
# the validity of each component.
#
def initialize(*arg)
super(*arg)
@to = nil
@headers = []
# The RFC3986 parser does not normally populate opaque
@opaque = "?#{@query}" if @query && !@opaque
unless @opaque
raise InvalidComponentError,
"missing opaque part for mailto URL"
end
to, header = @opaque.split('?', 2)
# allow semicolon as a addr-spec separator
# http://support.microsoft.com/kb/820868
unless /\A(?:[^@,;]+@[^@,;]+(?:\z|[,;]))*\z/ =~ to
raise InvalidComponentError,
"unrecognised opaque part for mailtoURL: #{@opaque}"
end
if arg[10] # arg_check
self.to = to
self.headers = header
else
set_to(to)
set_headers(header)
end
end
# The primary e-mail address of the URL, as a String.
attr_reader :to
# E-mail headers set by the URL, as an Array of Arrays.
attr_reader :headers
# Checks the to +v+ component.
def check_to(v)
return true unless v
return true if v.size == 0
v.split(/[,;]/).each do |addr|
# check url safety as path-rootless
if /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*\z/ !~ addr
raise InvalidComponentError,
"an address in 'to' is invalid as URI #{addr.dump}"
end
# check addr-spec
# don't s/\+/ /g
addr.gsub!(/%\h\h/, URI::TBLDECWWWCOMP_)
if EMAIL_REGEXP !~ addr
raise InvalidComponentError,
"an address in 'to' is invalid as uri-escaped addr-spec #{addr.dump}"
end
end
true
end
private :check_to
# Private setter for to +v+.
def set_to(v)
@to = v
end
protected :set_to
# Setter for to +v+.
def to=(v)
check_to(v)
set_to(v)
v
end
# Checks the headers +v+ component against either
# * HEADER_REGEXP
def check_headers(v)
return true unless v
return true if v.size == 0
if HEADER_REGEXP !~ v
raise InvalidComponentError,
"bad component(expected opaque component): #{v}"
end
true
end
private :check_headers
# Private setter for headers +v+.
def set_headers(v)
@headers = []
if v
v.split('&').each do |x|
@headers << x.split(/=/, 2)
end
end
end
protected :set_headers
# Setter for headers +v+.
def headers=(v)
check_headers(v)
set_headers(v)
v
end
# Constructs String from URI.
def to_s
@scheme + ':' +
if @to
@to
else
''
end +
if @headers.size > 0
'?' + @headers.collect{|x| x.join('=')}.join('&')
else
''
end +
if @fragment
'#' + @fragment
else
''
end
end
# Returns the RFC822 e-mail text equivalent of the URL, as a String.
#
# Example:
#
# require 'uri'
#
# uri = URI.parse("mailto:ruby-list@ruby-lang.org?Subject=subscribe&cc=myaddr")
# uri.to_mailtext
# # => "To: ruby-list@ruby-lang.org\nSubject: subscribe\nCc: myaddr\n\n\n"
#
def to_mailtext
to = URI.decode_www_form_component(@to)
head = ''
body = ''
@headers.each do |x|
case x[0]
when 'body'
body = URI.decode_www_form_component(x[1])
when 'to'
to << ', ' + URI.decode_www_form_component(x[1])
else
head << URI.decode_www_form_component(x[0]).capitalize + ': ' +
URI.decode_www_form_component(x[1]) + "\n"
end
end
"To: #{to}
#{head}
#{body}
"
end
alias to_rfc822text to_mailtext
end
register_scheme 'MAILTO', MailTo
end
@@ -0,0 +1,539 @@
# frozen_string_literal: false
#--
# = uri/common.rb
#
# Author:: Akira Yamada <akira@ruby-lang.org>
# License::
# You can redistribute it and/or modify it under the same term as Ruby.
#
# See URI for general documentation
#
module URI
#
# Includes URI::REGEXP::PATTERN
#
module RFC2396_REGEXP
#
# Patterns used to parse URI's
#
module PATTERN
# :stopdoc:
# RFC 2396 (URI Generic Syntax)
# RFC 2732 (IPv6 Literal Addresses in URL's)
# RFC 2373 (IPv6 Addressing Architecture)
# alpha = lowalpha | upalpha
ALPHA = "a-zA-Z"
# alphanum = alpha | digit
ALNUM = "#{ALPHA}\\d"
# hex = digit | "A" | "B" | "C" | "D" | "E" | "F" |
# "a" | "b" | "c" | "d" | "e" | "f"
HEX = "a-fA-F\\d"
# escaped = "%" hex hex
ESCAPED = "%[#{HEX}]{2}"
# mark = "-" | "_" | "." | "!" | "~" | "*" | "'" |
# "(" | ")"
# unreserved = alphanum | mark
UNRESERVED = "\\-_.!~*'()#{ALNUM}"
# reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
# "$" | ","
# reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
# "$" | "," | "[" | "]" (RFC 2732)
RESERVED = ";/?:@&=+$,\\[\\]"
# domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
DOMLABEL = "(?:[#{ALNUM}](?:[-#{ALNUM}]*[#{ALNUM}])?)"
# toplabel = alpha | alpha *( alphanum | "-" ) alphanum
TOPLABEL = "(?:[#{ALPHA}](?:[-#{ALNUM}]*[#{ALNUM}])?)"
# hostname = *( domainlabel "." ) toplabel [ "." ]
HOSTNAME = "(?:#{DOMLABEL}\\.)*#{TOPLABEL}\\.?"
# :startdoc:
end # PATTERN
# :startdoc:
end # REGEXP
# Class that parses String's into URI's.
#
# It contains a Hash set of patterns and Regexp's that match and validate.
#
class RFC2396_Parser
include RFC2396_REGEXP
#
# == Synopsis
#
# URI::Parser.new([opts])
#
# == Args
#
# The constructor accepts a hash as options for parser.
# Keys of options are pattern names of URI components
# and values of options are pattern strings.
# The constructor generates set of regexps for parsing URIs.
#
# You can use the following keys:
#
# * :ESCAPED (URI::PATTERN::ESCAPED in default)
# * :UNRESERVED (URI::PATTERN::UNRESERVED in default)
# * :DOMLABEL (URI::PATTERN::DOMLABEL in default)
# * :TOPLABEL (URI::PATTERN::TOPLABEL in default)
# * :HOSTNAME (URI::PATTERN::HOSTNAME in default)
#
# == Examples
#
# p = URI::Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})")
# u = p.parse("http://example.jp/%uABCD") #=> #<URI::HTTP http://example.jp/%uABCD>
# URI.parse(u.to_s) #=> raises URI::InvalidURIError
#
# s = "http://example.com/ABCD"
# u1 = p.parse(s) #=> #<URI::HTTP http://example.com/ABCD>
# u2 = URI.parse(s) #=> #<URI::HTTP http://example.com/ABCD>
# u1 == u2 #=> true
# u1.eql?(u2) #=> false
#
def initialize(opts = {})
@pattern = initialize_pattern(opts)
@pattern.each_value(&:freeze)
@pattern.freeze
@regexp = initialize_regexp(@pattern)
@regexp.each_value(&:freeze)
@regexp.freeze
end
# The Hash of patterns.
#
# See also URI::Parser.initialize_pattern.
attr_reader :pattern
# The Hash of Regexp.
#
# See also URI::Parser.initialize_regexp.
attr_reader :regexp
# Returns a split URI against +regexp[:ABS_URI]+.
def split(uri)
case uri
when ''
# null uri
when @regexp[:ABS_URI]
scheme, opaque, userinfo, host, port,
registry, path, query, fragment = $~[1..-1]
# URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
# absoluteURI = scheme ":" ( hier_part | opaque_part )
# hier_part = ( net_path | abs_path ) [ "?" query ]
# opaque_part = uric_no_slash *uric
# abs_path = "/" path_segments
# net_path = "//" authority [ abs_path ]
# authority = server | reg_name
# server = [ [ userinfo "@" ] hostport ]
if !scheme
raise InvalidURIError,
"bad URI(absolute but no scheme): #{uri}"
end
if !opaque && (!path && (!host && !registry))
raise InvalidURIError,
"bad URI(absolute but no path): #{uri}"
end
when @regexp[:REL_URI]
scheme = nil
opaque = nil
userinfo, host, port, registry,
rel_segment, abs_path, query, fragment = $~[1..-1]
if rel_segment && abs_path
path = rel_segment + abs_path
elsif rel_segment
path = rel_segment
elsif abs_path
path = abs_path
end
# URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
# relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ]
# net_path = "//" authority [ abs_path ]
# abs_path = "/" path_segments
# rel_path = rel_segment [ abs_path ]
# authority = server | reg_name
# server = [ [ userinfo "@" ] hostport ]
else
raise InvalidURIError, "bad URI(is not URI?): #{uri}"
end
path = '' if !path && !opaque # (see RFC2396 Section 5.2)
ret = [
scheme,
userinfo, host, port, # X
registry, # X
path, # Y
opaque, # Y
query,
fragment
]
return ret
end
#
# == Args
#
# +uri+::
# String
#
# == Description
#
# Parses +uri+ and constructs either matching URI scheme object
# (File, FTP, HTTP, HTTPS, LDAP, LDAPS, or MailTo) or URI::Generic.
#
# == Usage
#
# p = URI::Parser.new
# p.parse("ldap://ldap.example.com/dc=example?user=john")
# #=> #<URI::LDAP ldap://ldap.example.com/dc=example?user=john>
#
def parse(uri)
URI.for(*self.split(uri), self)
end
#
# == Args
#
# +uris+::
# an Array of Strings
#
# == Description
#
# Attempts to parse and merge a set of URIs.
#
def join(*uris)
uris[0] = convert_to_uri(uris[0])
uris.inject :merge
end
#
# :call-seq:
# extract( str )
# extract( str, schemes )
# extract( str, schemes ) {|item| block }
#
# == Args
#
# +str+::
# String to search
# +schemes+::
# Patterns to apply to +str+
#
# == Description
#
# Attempts to parse and merge a set of URIs.
# If no +block+ given, then returns the result,
# else it calls +block+ for each element in result.
#
# See also URI::Parser.make_regexp.
#
def extract(str, schemes = nil)
if block_given?
str.scan(make_regexp(schemes)) { yield $& }
nil
else
result = []
str.scan(make_regexp(schemes)) { result.push $& }
result
end
end
# Returns Regexp that is default +self.regexp[:ABS_URI_REF]+,
# unless +schemes+ is provided. Then it is a Regexp.union with +self.pattern[:X_ABS_URI]+.
def make_regexp(schemes = nil)
unless schemes
@regexp[:ABS_URI_REF]
else
/(?=#{Regexp.union(*schemes)}:)#{@pattern[:X_ABS_URI]}/x
end
end
#
# :call-seq:
# escape( str )
# escape( str, unsafe )
#
# == Args
#
# +str+::
# String to make safe
# +unsafe+::
# Regexp to apply. Defaults to +self.regexp[:UNSAFE]+
#
# == Description
#
# Constructs a safe String from +str+, removing unsafe characters,
# replacing them with codes.
#
def escape(str, unsafe = @regexp[:UNSAFE])
unless unsafe.kind_of?(Regexp)
# perhaps unsafe is String object
unsafe = Regexp.new("[#{Regexp.quote(unsafe)}]", false)
end
str.gsub(unsafe) do
us = $&
tmp = ''
us.each_byte do |uc|
tmp << sprintf('%%%02X', uc)
end
tmp
end.force_encoding(Encoding::US_ASCII)
end
#
# :call-seq:
# unescape( str )
# unescape( str, escaped )
#
# == Args
#
# +str+::
# String to remove escapes from
# +escaped+::
# Regexp to apply. Defaults to +self.regexp[:ESCAPED]+
#
# == Description
#
# Removes escapes from +str+.
#
def unescape(str, escaped = @regexp[:ESCAPED])
enc = str.encoding
enc = Encoding::UTF_8 if enc == Encoding::US_ASCII
str.gsub(escaped) { [$&[1, 2]].pack('H2').force_encoding(enc) }
end
@@to_s = Kernel.instance_method(:to_s)
if @@to_s.respond_to?(:bind_call)
def inspect
@@to_s.bind_call(self)
end
else
def inspect
@@to_s.bind(self).call
end
end
private
# Constructs the default Hash of patterns.
def initialize_pattern(opts = {})
ret = {}
ret[:ESCAPED] = escaped = (opts.delete(:ESCAPED) || PATTERN::ESCAPED)
ret[:UNRESERVED] = unreserved = opts.delete(:UNRESERVED) || PATTERN::UNRESERVED
ret[:RESERVED] = reserved = opts.delete(:RESERVED) || PATTERN::RESERVED
ret[:DOMLABEL] = opts.delete(:DOMLABEL) || PATTERN::DOMLABEL
ret[:TOPLABEL] = opts.delete(:TOPLABEL) || PATTERN::TOPLABEL
ret[:HOSTNAME] = hostname = opts.delete(:HOSTNAME)
# RFC 2396 (URI Generic Syntax)
# RFC 2732 (IPv6 Literal Addresses in URL's)
# RFC 2373 (IPv6 Addressing Architecture)
# uric = reserved | unreserved | escaped
ret[:URIC] = uric = "(?:[#{unreserved}#{reserved}]|#{escaped})"
# uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" |
# "&" | "=" | "+" | "$" | ","
ret[:URIC_NO_SLASH] = uric_no_slash = "(?:[#{unreserved};?:@&=+$,]|#{escaped})"
# query = *uric
ret[:QUERY] = query = "#{uric}*"
# fragment = *uric
ret[:FRAGMENT] = fragment = "#{uric}*"
# hostname = *( domainlabel "." ) toplabel [ "." ]
# reg-name = *( unreserved / pct-encoded / sub-delims ) # RFC3986
unless hostname
ret[:HOSTNAME] = hostname = "(?:[a-zA-Z0-9\\-.]|%\\h\\h)+"
end
# RFC 2373, APPENDIX B:
# IPv6address = hexpart [ ":" IPv4address ]
# IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT
# hexpart = hexseq | hexseq "::" [ hexseq ] | "::" [ hexseq ]
# hexseq = hex4 *( ":" hex4)
# hex4 = 1*4HEXDIG
#
# XXX: This definition has a flaw. "::" + IPv4address must be
# allowed too. Here is a replacement.
#
# IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT
ret[:IPV4ADDR] = ipv4addr = "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"
# hex4 = 1*4HEXDIG
hex4 = "[#{PATTERN::HEX}]{1,4}"
# lastpart = hex4 | IPv4address
lastpart = "(?:#{hex4}|#{ipv4addr})"
# hexseq1 = *( hex4 ":" ) hex4
hexseq1 = "(?:#{hex4}:)*#{hex4}"
# hexseq2 = *( hex4 ":" ) lastpart
hexseq2 = "(?:#{hex4}:)*#{lastpart}"
# IPv6address = hexseq2 | [ hexseq1 ] "::" [ hexseq2 ]
ret[:IPV6ADDR] = ipv6addr = "(?:#{hexseq2}|(?:#{hexseq1})?::(?:#{hexseq2})?)"
# IPv6prefix = ( hexseq1 | [ hexseq1 ] "::" [ hexseq1 ] ) "/" 1*2DIGIT
# unused
# ipv6reference = "[" IPv6address "]" (RFC 2732)
ret[:IPV6REF] = ipv6ref = "\\[#{ipv6addr}\\]"
# host = hostname | IPv4address
# host = hostname | IPv4address | IPv6reference (RFC 2732)
ret[:HOST] = host = "(?:#{hostname}|#{ipv4addr}|#{ipv6ref})"
# port = *digit
ret[:PORT] = port = '\d*'
# hostport = host [ ":" port ]
ret[:HOSTPORT] = hostport = "#{host}(?::#{port})?"
# userinfo = *( unreserved | escaped |
# ";" | ":" | "&" | "=" | "+" | "$" | "," )
ret[:USERINFO] = userinfo = "(?:[#{unreserved};:&=+$,]|#{escaped})*"
# pchar = unreserved | escaped |
# ":" | "@" | "&" | "=" | "+" | "$" | ","
pchar = "(?:[#{unreserved}:@&=+$,]|#{escaped})"
# param = *pchar
param = "#{pchar}*"
# segment = *pchar *( ";" param )
segment = "#{pchar}*(?:;#{param})*"
# path_segments = segment *( "/" segment )
ret[:PATH_SEGMENTS] = path_segments = "#{segment}(?:/#{segment})*"
# server = [ [ userinfo "@" ] hostport ]
server = "(?:#{userinfo}@)?#{hostport}"
# reg_name = 1*( unreserved | escaped | "$" | "," |
# ";" | ":" | "@" | "&" | "=" | "+" )
ret[:REG_NAME] = reg_name = "(?:[#{unreserved}$,;:@&=+]|#{escaped})+"
# authority = server | reg_name
authority = "(?:#{server}|#{reg_name})"
# rel_segment = 1*( unreserved | escaped |
# ";" | "@" | "&" | "=" | "+" | "$" | "," )
ret[:REL_SEGMENT] = rel_segment = "(?:[#{unreserved};@&=+$,]|#{escaped})+"
# scheme = alpha *( alpha | digit | "+" | "-" | "." )
ret[:SCHEME] = scheme = "[#{PATTERN::ALPHA}][\\-+.#{PATTERN::ALPHA}\\d]*"
# abs_path = "/" path_segments
ret[:ABS_PATH] = abs_path = "/#{path_segments}"
# rel_path = rel_segment [ abs_path ]
ret[:REL_PATH] = rel_path = "#{rel_segment}(?:#{abs_path})?"
# net_path = "//" authority [ abs_path ]
ret[:NET_PATH] = net_path = "//#{authority}(?:#{abs_path})?"
# hier_part = ( net_path | abs_path ) [ "?" query ]
ret[:HIER_PART] = hier_part = "(?:#{net_path}|#{abs_path})(?:\\?(?:#{query}))?"
# opaque_part = uric_no_slash *uric
ret[:OPAQUE_PART] = opaque_part = "#{uric_no_slash}#{uric}*"
# absoluteURI = scheme ":" ( hier_part | opaque_part )
ret[:ABS_URI] = abs_uri = "#{scheme}:(?:#{hier_part}|#{opaque_part})"
# relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ]
ret[:REL_URI] = rel_uri = "(?:#{net_path}|#{abs_path}|#{rel_path})(?:\\?#{query})?"
# URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
ret[:URI_REF] = "(?:#{abs_uri}|#{rel_uri})?(?:##{fragment})?"
ret[:X_ABS_URI] = "
(#{scheme}): (?# 1: scheme)
(?:
(#{opaque_part}) (?# 2: opaque)
|
(?:(?:
//(?:
(?:(?:(#{userinfo})@)? (?# 3: userinfo)
(?:(#{host})(?::(\\d*))?))? (?# 4: host, 5: port)
|
(#{reg_name}) (?# 6: registry)
)
|
(?!//)) (?# XXX: '//' is the mark for hostport)
(#{abs_path})? (?# 7: path)
)(?:\\?(#{query}))? (?# 8: query)
)
(?:\\#(#{fragment}))? (?# 9: fragment)
"
ret[:X_REL_URI] = "
(?:
(?:
//
(?:
(?:(#{userinfo})@)? (?# 1: userinfo)
(#{host})?(?::(\\d*))? (?# 2: host, 3: port)
|
(#{reg_name}) (?# 4: registry)
)
)
|
(#{rel_segment}) (?# 5: rel_segment)
)?
(#{abs_path})? (?# 6: abs_path)
(?:\\?(#{query}))? (?# 7: query)
(?:\\#(#{fragment}))? (?# 8: fragment)
"
ret
end
# Constructs the default Hash of Regexp's.
def initialize_regexp(pattern)
ret = {}
# for URI::split
ret[:ABS_URI] = Regexp.new('\A\s*+' + pattern[:X_ABS_URI] + '\s*\z', Regexp::EXTENDED)
ret[:REL_URI] = Regexp.new('\A\s*+' + pattern[:X_REL_URI] + '\s*\z', Regexp::EXTENDED)
# for URI::extract
ret[:URI_REF] = Regexp.new(pattern[:URI_REF])
ret[:ABS_URI_REF] = Regexp.new(pattern[:X_ABS_URI], Regexp::EXTENDED)
ret[:REL_URI_REF] = Regexp.new(pattern[:X_REL_URI], Regexp::EXTENDED)
# for URI::escape/unescape
ret[:ESCAPED] = Regexp.new(pattern[:ESCAPED])
ret[:UNSAFE] = Regexp.new("[^#{pattern[:UNRESERVED]}#{pattern[:RESERVED]}]")
# for Generic#initialize
ret[:SCHEME] = Regexp.new("\\A#{pattern[:SCHEME]}\\z")
ret[:USERINFO] = Regexp.new("\\A#{pattern[:USERINFO]}\\z")
ret[:HOST] = Regexp.new("\\A#{pattern[:HOST]}\\z")
ret[:PORT] = Regexp.new("\\A#{pattern[:PORT]}\\z")
ret[:OPAQUE] = Regexp.new("\\A#{pattern[:OPAQUE_PART]}\\z")
ret[:REGISTRY] = Regexp.new("\\A#{pattern[:REG_NAME]}\\z")
ret[:ABS_PATH] = Regexp.new("\\A#{pattern[:ABS_PATH]}\\z")
ret[:REL_PATH] = Regexp.new("\\A#{pattern[:REL_PATH]}\\z")
ret[:QUERY] = Regexp.new("\\A#{pattern[:QUERY]}\\z")
ret[:FRAGMENT] = Regexp.new("\\A#{pattern[:FRAGMENT]}\\z")
ret
end
def convert_to_uri(uri)
if uri.is_a?(URI::Generic)
uri
elsif uri = String.try_convert(uri)
parse(uri)
else
raise ArgumentError,
"bad argument (expected URI object or URI string)"
end
end
end # class Parser
end # module URI
@@ -0,0 +1,183 @@
# frozen_string_literal: true
module URI
class RFC3986_Parser # :nodoc:
# URI defined in RFC3986
HOST = %r[
(?<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-9:;=A-Z_a-z~]++)
)\])
| \g<IPv4address>
| (?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])*+)
]x
USERINFO = /(?:%\h\h|[!$&-.0-9:;=A-Z_a-z~])*+/
SCHEME = %r[[A-Za-z][+\-.0-9A-Za-z]*+].source
SEG = %r[(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/])].source
SEG_NC = %r[(?:%\h\h|[!$&-.0-9;=@A-Z_a-z~])].source
FRAGMENT = %r[(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/?])*+].source
RFC3986_URI = %r[\A
(?<seg>#{SEG}){0}
(?<URI>
(?<scheme>#{SCHEME}):
(?<hier-part>//
(?<authority>
(?:(?<userinfo>#{USERINFO.source})@)?
(?<host>#{HOST.source.delete(" \n")})
(?::(?<port>\d*+))?
)
(?<path-abempty>(?:/\g<seg>*+)?)
| (?<path-absolute>/((?!/)\g<seg>++)?)
| (?<path-rootless>(?!/)\g<seg>++)
| (?<path-empty>)
)
(?:\?(?<query>[^\#]*+))?
(?:\#(?<fragment>#{FRAGMENT}))?
)\z]x
RFC3986_relative_ref = %r[\A
(?<seg>#{SEG}){0}
(?<relative-ref>
(?<relative-part>//
(?<authority>
(?:(?<userinfo>#{USERINFO.source})@)?
(?<host>#{HOST.source.delete(" \n")}(?<!/))?
(?::(?<port>\d*+))?
)
(?<path-abempty>(?:/\g<seg>*+)?)
| (?<path-absolute>/\g<seg>*+)
| (?<path-noscheme>#{SEG_NC}++(?:/\g<seg>*+)?)
| (?<path-empty>)
)
(?:\?(?<query>[^#]*+))?
(?:\#(?<fragment>#{FRAGMENT}))?
)\z]x
attr_reader :regexp
def initialize
@regexp = default_regexp.each_value(&:freeze).freeze
end
def split(uri) #:nodoc:
begin
uri = uri.to_str
rescue NoMethodError
raise InvalidURIError, "bad URI(is not URI?): #{uri.inspect}"
end
uri.ascii_only? or
raise InvalidURIError, "URI must be ascii only #{uri.dump}"
if m = RFC3986_URI.match(uri)
query = m["query"]
scheme = m["scheme"]
opaque = m["path-rootless"]
if opaque
opaque << "?#{query}" if query
[ scheme,
nil, # userinfo
nil, # host
nil, # port
nil, # registry
nil, # path
opaque,
nil, # query
m["fragment"]
]
else # normal
[ scheme,
m["userinfo"],
m["host"],
m["port"],
nil, # registry
(m["path-abempty"] ||
m["path-absolute"] ||
m["path-empty"]),
nil, # opaque
query,
m["fragment"]
]
end
elsif m = RFC3986_relative_ref.match(uri)
[ nil, # scheme
m["userinfo"],
m["host"],
m["port"],
nil, # registry,
(m["path-abempty"] ||
m["path-absolute"] ||
m["path-noscheme"] ||
m["path-empty"]),
nil, # opaque
m["query"],
m["fragment"]
]
else
raise InvalidURIError, "bad URI(is not URI?): #{uri.inspect}"
end
end
def parse(uri) # :nodoc:
URI.for(*self.split(uri), self)
end
def join(*uris) # :nodoc:
uris[0] = convert_to_uri(uris[0])
uris.inject :merge
end
@@to_s = Kernel.instance_method(:to_s)
if @@to_s.respond_to?(:bind_call)
def inspect
@@to_s.bind_call(self)
end
else
def inspect
@@to_s.bind(self).call
end
end
private
def default_regexp # :nodoc:
{
SCHEME: %r[\A#{SCHEME}\z]o,
USERINFO: %r[\A#{USERINFO}\z]o,
HOST: %r[\A#{HOST}\z]o,
ABS_PATH: %r[\A/#{SEG}*+\z]o,
REL_PATH: %r[\A(?!/)#{SEG}++\z]o,
QUERY: %r[\A(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/?])*+\z],
FRAGMENT: %r[\A#{FRAGMENT}\z]o,
OPAQUE: %r[\A(?:[^/].*)?\z],
PORT: /\A[\x09\x0a\x0c\x0d ]*+\d*[\x09\x0a\x0c\x0d ]*\z/,
}
end
def convert_to_uri(uri)
if uri.is_a?(URI::Generic)
uri
elsif uri = String.try_convert(uri)
parse(uri)
else
raise ArgumentError,
"bad argument (expected URI object or URI string)"
end
end
end # class Parser
end # module URI
+6
View File
@@ -0,0 +1,6 @@
module URI
# :stopdoc:
VERSION_CODE = '001302'.freeze
VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze
# :startdoc:
end
+83
View File
@@ -0,0 +1,83 @@
# frozen_string_literal: false
# = uri/ws.rb
#
# Author:: Matt Muller <mamuller@amazon.com>
# License:: You can redistribute it and/or modify it under the same term as Ruby.
#
# See URI for general documentation
#
require_relative 'generic'
module URI
#
# The syntax of WS URIs is defined in RFC6455 section 3.
#
# Note that the Ruby URI library allows WS URLs containing usernames and
# passwords. This is not legal as per the RFC, but used to be
# supported in Internet Explorer 5 and 6, before the MS04-004 security
# update. See <URL:http://support.microsoft.com/kb/834489>.
#
class WS < Generic
# A Default port of 80 for URI::WS.
DEFAULT_PORT = 80
# An Array of the available components for URI::WS.
COMPONENT = %i[
scheme
userinfo host port
path
query
].freeze
#
# == Description
#
# Creates a new URI::WS object from components, with syntax checking.
#
# The components accepted are userinfo, host, port, path, and query.
#
# The components should be provided either as an Array, or as a Hash
# with keys formed by preceding the component names with a colon.
#
# If an Array is used, the components must be passed in the
# order <code>[userinfo, host, port, path, query]</code>.
#
# Example:
#
# uri = URI::WS.build(host: 'www.example.com', path: '/foo/bar')
#
# uri = URI::WS.build([nil, "www.example.com", nil, "/path", "query"])
#
# Currently, if passed userinfo components this method generates
# invalid WS URIs as per RFC 1738.
#
def self.build(args)
tmp = Util.make_components_hash(self, args)
super(tmp)
end
#
# == Description
#
# Returns the full path for a WS URI, as required by Net::HTTP::Get.
#
# If the URI contains a query, the full path is URI#path + '?' + URI#query.
# Otherwise, the path is simply URI#path.
#
# Example:
#
# uri = URI::WS.build(path: '/foo/bar', query: 'test=true')
# uri.request_uri # => "/foo/bar?test=true"
#
def request_uri
return unless @path
url = @query ? "#@path?#@query" : @path.dup
url.start_with?(?/.freeze) ? url : ?/ + url
end
end
register_scheme 'WS', WS
end
+23
View File
@@ -0,0 +1,23 @@
# frozen_string_literal: false
# = uri/wss.rb
#
# Author:: Matt Muller <mamuller@amazon.com>
# License:: You can redistribute it and/or modify it under the same term as Ruby.
#
# See URI for general documentation
#
require_relative 'ws'
module URI
# The default port for WSS URIs is 443, and the scheme is 'wss:' rather
# than 'ws:'. Other than that, WSS URIs are identical to WS URIs;
# see URI::WS.
class WSS < WS
# A Default port of 443 for URI::WSS
DEFAULT_PORT = 443
end
register_scheme 'WSS', WSS
end
@@ -0,0 +1,17 @@
task :sync_tool, [:from] do |t, from: nil|
from ||= (File.identical?(__dir__, "rakelib") ? "../ruby/tool" : File.dirname(__dir__))
require 'fileutils'
{
"rakelib/sync_tool.rake" => "rakelib",
"lib/core_assertions.rb" => "test/lib",
"lib/envutil.rb" => "test/lib",
"lib/find_executable.rb" => "test/lib",
"lib/helper.rb" => "test/lib",
}.each do |src, dest|
FileUtils.mkpath(dest)
FileUtils.cp "#{from}/#{src}", dest
rescue Errno::ENOENT
end
end
+39
View File
@@ -0,0 +1,39 @@
begin
require_relative "lib/uri/version"
rescue LoadError # Fallback to load version file in ruby core repository
require_relative "version"
end
Gem::Specification.new do |spec|
spec.name = "uri"
spec.version = URI::VERSION
spec.authors = ["Akira Yamada"]
spec.email = ["akira@ruby-lang.org"]
spec.summary = %q{URI is a module providing classes to handle Uniform Resource Identifiers}
spec.description = spec.summary
github_link = "https://github.com/ruby/uri"
spec.homepage = github_link
spec.licenses = ["Ruby", "BSD-2-Clause"]
spec.required_ruby_version = '>= 2.5'
spec.metadata = {
"bug_tracker_uri" => "#{github_link}/issues",
"changelog_uri" => "#{github_link}/releases",
"documentation_uri" => "https://ruby.github.io/uri/",
"homepage_uri" => spec.homepage,
"source_code_uri" => github_link
}
# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
`git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
end