├── .document ├── .github ├── dependabot.yml └── workflows │ ├── gh-pages.yml │ ├── push_gem.yml │ └── test.yml ├── .gitignore ├── .rdoc_options ├── BSDL ├── COPYING ├── Gemfile ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── docs └── kernel.rb ├── lib ├── uri.rb └── uri │ ├── common.rb │ ├── file.rb │ ├── ftp.rb │ ├── generic.rb │ ├── http.rb │ ├── https.rb │ ├── ldap.rb │ ├── ldaps.rb │ ├── mailto.rb │ ├── rfc2396_parser.rb │ ├── rfc3986_parser.rb │ ├── version.rb │ ├── ws.rb │ └── wss.rb ├── rakelib └── sync_tool.rake ├── test ├── lib │ └── helper.rb └── uri │ ├── test_common.rb │ ├── test_file.rb │ ├── test_ftp.rb │ ├── test_generic.rb │ ├── test_http.rb │ ├── test_ldap.rb │ ├── test_mailto.rb │ ├── test_parser.rb │ ├── test_ws.rb │ └── test_wss.rb └── uri.gemspec /.document: -------------------------------------------------------------------------------- 1 | BSDL 2 | COPYING 3 | README.md 4 | docs/ 5 | lib/ 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 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@32110d4e311bd8996b2a82bf2a43b714ccc91777 # v1.221.0 25 | with: 26 | ruby-version: '3.4' 27 | bundler-cache: true 28 | - name: Setup Pages 29 | id: pages 30 | uses: actions/configure-pages@v5 31 | - name: Build with RDoc 32 | # Outputs to the './_site' directory by default 33 | run: rake rdoc 34 | - name: Upload artifact 35 | uses: actions/upload-pages-artifact@v3 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@v4 47 | -------------------------------------------------------------------------------- /.github/workflows/push_gem.yml: -------------------------------------------------------------------------------- 1 | name: Publish gem to rubygems.org 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | push: 13 | if: github.repository == 'ruby/uri' 14 | runs-on: ubuntu-latest 15 | 16 | environment: 17 | name: rubygems.org 18 | url: https://rubygems.org/gems/uri 19 | 20 | permissions: 21 | contents: write 22 | id-token: write 23 | 24 | steps: 25 | - name: Harden Runner 26 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 27 | with: 28 | egress-policy: audit 29 | 30 | - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 31 | 32 | - name: Set up Ruby 33 | uses: ruby/setup-ruby@a6e6f86333f0a2523ece813039b8b4be04560854 # v1.190.0 34 | with: 35 | bundler-cache: true 36 | ruby-version: ruby 37 | 38 | - name: Publish to RubyGems 39 | uses: rubygems/release-gem@a25424ba2ba8b387abc8ef40807c2c85b96cbe32 # v1.1.1 40 | 41 | - name: Create GitHub release 42 | run: | 43 | tag_name="$(git describe --tags --abbrev=0)" 44 | gh release create "${tag_name}" --verify-tag --generate-notes 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }} 47 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ruby-versions: 7 | uses: ruby/actions/.github/workflows/ruby_versions.yml@master 8 | with: 9 | min_version: 2.5 10 | 11 | build: 12 | needs: ruby-versions 13 | name: build (${{ matrix.ruby }} / ${{ matrix.os }}) 14 | strategy: 15 | matrix: 16 | ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} 17 | os: [ ubuntu-latest, macos-latest ] 18 | exclude: 19 | - ruby: 2.5 20 | os: macos-latest 21 | runs-on: ${{ matrix.os }} 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Set up Ruby 25 | uses: ruby/setup-ruby@v1 26 | with: 27 | ruby-version: ${{ matrix.ruby }} 28 | - run: bundle install --jobs 4 --retry 3 29 | - name: Run test 30 | run: rake test 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | Gemfile.lock 10 | /_site 11 | -------------------------------------------------------------------------------- /.rdoc_options: -------------------------------------------------------------------------------- 1 | main_page: README.md 2 | op_dir: _site 3 | warn_missing_rdoc_ref: true 4 | title: URI Documentation 5 | -------------------------------------------------------------------------------- /BSDL: -------------------------------------------------------------------------------- 1 | Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Ruby is copyrighted free software by Yukihiro Matsumoto . 2 | You can redistribute it and/or modify it under either the terms of the 3 | 2-clause BSDL (see the file BSDL), or the conditions below: 4 | 5 | 1. You may make and give away verbatim copies of the source form of the 6 | software without restriction, provided that you duplicate all of the 7 | original copyright notices and associated disclaimers. 8 | 9 | 2. You may modify your copy of the software in any way, provided that 10 | you do at least ONE of the following: 11 | 12 | a. place your modifications in the Public Domain or otherwise 13 | make them Freely Available, such as by posting said 14 | modifications to Usenet or an equivalent medium, or by allowing 15 | the author to include your modifications in the software. 16 | 17 | b. use the modified software only within your corporation or 18 | organization. 19 | 20 | c. give non-standard binaries non-standard names, with 21 | instructions on where to get the original software distribution. 22 | 23 | d. make other distribution arrangements with the author. 24 | 25 | 3. You may distribute the software in object code or binary form, 26 | provided that you do at least ONE of the following: 27 | 28 | a. distribute the binaries and library files of the software, 29 | together with instructions (in the manual page or equivalent) 30 | on where to get the original distribution. 31 | 32 | b. accompany the distribution with the machine-readable source of 33 | the software. 34 | 35 | c. give non-standard binaries non-standard names, with 36 | instructions on where to get the original software distribution. 37 | 38 | d. make other distribution arrangements with the author. 39 | 40 | 4. You may modify and include the part of the software into any other 41 | software (possibly commercial). But some files in the distribution 42 | are not written by the author, so that they are not under these terms. 43 | 44 | For the list of those files and their copying conditions, see the 45 | file LEGAL. 46 | 47 | 5. The scripts and library files supplied as input to or produced as 48 | output from the software do not automatically fall under the 49 | copyright of the software, but belong to whomever generated them, 50 | and may be sold commercially, and may be aggregated with this 51 | software. 52 | 53 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 54 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 56 | PURPOSE. 57 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | group :development do 6 | gem "bundler" 7 | gem "rake" 8 | gem "test-unit" 9 | gem "test-unit-ruby-core", ">= 1.0.7" 10 | end 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # URI 2 | 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/) 5 | 6 | URI is a module providing classes to handle Uniform Resource Identifiers [RFC3986](http://tools.ietf.org/html/rfc3986). 7 | 8 | ## Features 9 | 10 | * Uniform way of handling URIs. 11 | * Flexibility to introduce custom URI schemes. 12 | * Flexibility to have an alternate URI::Parser (or just different patterns and regexp's). 13 | 14 | ## Installation 15 | 16 | Add this line to your application's Gemfile: 17 | 18 | ```ruby 19 | gem 'uri' 20 | ``` 21 | 22 | And then execute: 23 | 24 | $ bundle 25 | 26 | Or install it yourself as: 27 | 28 | $ gem install uri 29 | 30 | ## Usage 31 | 32 | ```ruby 33 | require 'uri' 34 | 35 | uri = URI("http://foo.com/posts?id=30&limit=5#time=1305298413") 36 | #=> # 37 | 38 | uri.scheme #=> "http" 39 | uri.host #=> "foo.com" 40 | uri.path #=> "/posts" 41 | uri.query #=> "id=30&limit=5" 42 | uri.fragment #=> "time=1305298413" 43 | 44 | uri.to_s #=> "http://foo.com/posts?id=30&limit=5#time=1305298413" 45 | ``` 46 | 47 | ## Development 48 | 49 | 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. 50 | 51 | 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). 52 | 53 | ## Contributing 54 | 55 | Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/uri. 56 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test/lib" 6 | t.ruby_opts << "-rhelper" 7 | t.test_files = FileList["test/**/test_*.rb"] 8 | end 9 | 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 BSDL COPYING] 15 | doc.rdoc_dir = "_site" # for github pages 16 | end 17 | 18 | task :default => :test 19 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "uri" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /docs/kernel.rb: -------------------------------------------------------------------------------- 1 | # :stopdoc: 2 | module Kernel end 3 | -------------------------------------------------------------------------------- /lib/uri.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # URI is a module providing classes to handle Uniform Resource Identifiers 3 | # (RFC2396[https://www.rfc-editor.org/rfc/rfc2396]). 4 | # 5 | # == Features 6 | # 7 | # * Uniform way of handling URIs. 8 | # * Flexibility to introduce custom URI schemes. 9 | # * Flexibility to have an alternate URI::Parser (or just different patterns 10 | # and regexp's). 11 | # 12 | # == Basic example 13 | # 14 | # require 'uri' 15 | # 16 | # uri = URI("http://foo.com/posts?id=30&limit=5#time=1305298413") 17 | # #=> # 18 | # 19 | # uri.scheme #=> "http" 20 | # uri.host #=> "foo.com" 21 | # uri.path #=> "/posts" 22 | # uri.query #=> "id=30&limit=5" 23 | # uri.fragment #=> "time=1305298413" 24 | # 25 | # uri.to_s #=> "http://foo.com/posts?id=30&limit=5#time=1305298413" 26 | # 27 | # == Adding custom URIs 28 | # 29 | # module URI 30 | # class RSYNC < Generic 31 | # DEFAULT_PORT = 873 32 | # end 33 | # register_scheme 'RSYNC', RSYNC 34 | # end 35 | # #=> URI::RSYNC 36 | # 37 | # URI.scheme_list 38 | # #=> {"FILE"=>URI::File, "FTP"=>URI::FTP, "HTTP"=>URI::HTTP, 39 | # # "HTTPS"=>URI::HTTPS, "LDAP"=>URI::LDAP, "LDAPS"=>URI::LDAPS, 40 | # # "MAILTO"=>URI::MailTo, "RSYNC"=>URI::RSYNC} 41 | # 42 | # uri = URI("rsync://rsync.foo.com") 43 | # #=> # 44 | # 45 | # == RFC References 46 | # 47 | # A good place to view an RFC spec is http://www.ietf.org/rfc.html. 48 | # 49 | # Here is a list of all related RFC's: 50 | # - RFC822[https://www.rfc-editor.org/rfc/rfc822] 51 | # - RFC1738[https://www.rfc-editor.org/rfc/rfc1738] 52 | # - RFC2255[https://www.rfc-editor.org/rfc/rfc2255] 53 | # - RFC2368[https://www.rfc-editor.org/rfc/rfc2368] 54 | # - RFC2373[https://www.rfc-editor.org/rfc/rfc2373] 55 | # - RFC2396[https://www.rfc-editor.org/rfc/rfc2396] 56 | # - RFC2732[https://www.rfc-editor.org/rfc/rfc2732] 57 | # - RFC3986[https://www.rfc-editor.org/rfc/rfc3986] 58 | # 59 | # == Class tree 60 | # 61 | # - URI::Generic (in uri/generic.rb) 62 | # - URI::File - (in uri/file.rb) 63 | # - URI::FTP - (in uri/ftp.rb) 64 | # - URI::HTTP - (in uri/http.rb) 65 | # - URI::HTTPS - (in uri/https.rb) 66 | # - URI::LDAP - (in uri/ldap.rb) 67 | # - URI::LDAPS - (in uri/ldaps.rb) 68 | # - URI::MailTo - (in uri/mailto.rb) 69 | # - URI::Parser - (in uri/common.rb) 70 | # - URI::REGEXP - (in uri/common.rb) 71 | # - URI::REGEXP::PATTERN - (in uri/common.rb) 72 | # - URI::Util - (in uri/common.rb) 73 | # - URI::Error - (in uri/common.rb) 74 | # - URI::InvalidURIError - (in uri/common.rb) 75 | # - URI::InvalidComponentError - (in uri/common.rb) 76 | # - URI::BadURIError - (in uri/common.rb) 77 | # 78 | # == Copyright Info 79 | # 80 | # Author:: Akira Yamada 81 | # Documentation:: 82 | # Akira Yamada 83 | # Dmitry V. Sabanin 84 | # Vincent Batts 85 | # License:: 86 | # Copyright (c) 2001 akira yamada 87 | # You can redistribute it and/or modify it under the same term as Ruby. 88 | # 89 | 90 | module URI 91 | end 92 | 93 | require_relative 'uri/version' 94 | require_relative 'uri/common' 95 | require_relative 'uri/generic' 96 | require_relative 'uri/file' 97 | require_relative 'uri/ftp' 98 | require_relative 'uri/http' 99 | require_relative 'uri/https' 100 | require_relative 'uri/ldap' 101 | require_relative 'uri/ldaps' 102 | require_relative 'uri/mailto' 103 | require_relative 'uri/ws' 104 | require_relative 'uri/wss' 105 | -------------------------------------------------------------------------------- /lib/uri/common.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | #-- 3 | # = uri/common.rb 4 | # 5 | # Author:: Akira Yamada 6 | # License:: 7 | # You can redistribute it and/or modify it under the same term as Ruby. 8 | # 9 | # See URI for general documentation 10 | # 11 | 12 | require_relative "rfc2396_parser" 13 | require_relative "rfc3986_parser" 14 | 15 | module URI 16 | # The default parser instance for RFC 2396. 17 | RFC2396_PARSER = RFC2396_Parser.new 18 | Ractor.make_shareable(RFC2396_PARSER) if defined?(Ractor) 19 | 20 | # The default parser instance for RFC 3986. 21 | RFC3986_PARSER = RFC3986_Parser.new 22 | Ractor.make_shareable(RFC3986_PARSER) if defined?(Ractor) 23 | 24 | # The default parser instance. 25 | DEFAULT_PARSER = RFC3986_PARSER 26 | Ractor.make_shareable(DEFAULT_PARSER) if defined?(Ractor) 27 | 28 | # Set the default parser instance. 29 | def self.parser=(parser = RFC3986_PARSER) 30 | remove_const(:Parser) if defined?(::URI::Parser) 31 | const_set("Parser", parser.class) 32 | 33 | remove_const(:REGEXP) if defined?(::URI::REGEXP) 34 | remove_const(:PATTERN) if defined?(::URI::PATTERN) 35 | if Parser == RFC2396_Parser 36 | const_set("REGEXP", URI::RFC2396_REGEXP) 37 | const_set("PATTERN", URI::RFC2396_REGEXP::PATTERN) 38 | end 39 | 40 | Parser.new.regexp.each_pair do |sym, str| 41 | remove_const(sym) if const_defined?(sym, false) 42 | const_set(sym, str) 43 | end 44 | end 45 | self.parser = RFC3986_PARSER 46 | 47 | def self.const_missing(const) # :nodoc: 48 | if const == :REGEXP 49 | warn "URI::REGEXP is obsolete. Use URI::RFC2396_REGEXP explicitly.", uplevel: 1 if $VERBOSE 50 | URI::RFC2396_REGEXP 51 | elsif value = RFC2396_PARSER.regexp[const] 52 | warn "URI::#{const} is obsolete. Use URI::RFC2396_PARSER.regexp[#{const.inspect}] explicitly.", uplevel: 1 if $VERBOSE 53 | value 54 | elsif value = RFC2396_Parser.const_get(const) 55 | warn "URI::#{const} is obsolete. Use URI::RFC2396_Parser::#{const} explicitly.", uplevel: 1 if $VERBOSE 56 | value 57 | else 58 | super 59 | end 60 | end 61 | 62 | module Util # :nodoc: 63 | def make_components_hash(klass, array_hash) 64 | tmp = {} 65 | if array_hash.kind_of?(Array) && 66 | array_hash.size == klass.component.size - 1 67 | klass.component[1..-1].each_index do |i| 68 | begin 69 | tmp[klass.component[i + 1]] = array_hash[i].clone 70 | rescue TypeError 71 | tmp[klass.component[i + 1]] = array_hash[i] 72 | end 73 | end 74 | 75 | elsif array_hash.kind_of?(Hash) 76 | array_hash.each do |key, value| 77 | begin 78 | tmp[key] = value.clone 79 | rescue TypeError 80 | tmp[key] = value 81 | end 82 | end 83 | else 84 | raise ArgumentError, 85 | "expected Array of or Hash of components of #{klass} (#{klass.component[1..-1].join(', ')})" 86 | end 87 | tmp[:scheme] = klass.to_s.sub(/\A.*::/, '').downcase 88 | 89 | return tmp 90 | end 91 | module_function :make_components_hash 92 | end 93 | 94 | module Schemes # :nodoc: 95 | end 96 | private_constant :Schemes 97 | 98 | # Registers the given +klass+ as the class to be instantiated 99 | # when parsing a \URI with the given +scheme+: 100 | # 101 | # URI.register_scheme('MS_SEARCH', URI::Generic) # => URI::Generic 102 | # URI.scheme_list['MS_SEARCH'] # => URI::Generic 103 | # 104 | # Note that after calling String#upcase on +scheme+, it must be a valid 105 | # constant name. 106 | def self.register_scheme(scheme, klass) 107 | Schemes.const_set(scheme.to_s.upcase, klass) 108 | end 109 | 110 | # Returns a hash of the defined schemes: 111 | # 112 | # URI.scheme_list 113 | # # => 114 | # {"MAILTO"=>URI::MailTo, 115 | # "LDAPS"=>URI::LDAPS, 116 | # "WS"=>URI::WS, 117 | # "HTTP"=>URI::HTTP, 118 | # "HTTPS"=>URI::HTTPS, 119 | # "LDAP"=>URI::LDAP, 120 | # "FILE"=>URI::File, 121 | # "FTP"=>URI::FTP} 122 | # 123 | # Related: URI.register_scheme. 124 | def self.scheme_list 125 | Schemes.constants.map { |name| 126 | [name.to_s.upcase, Schemes.const_get(name)] 127 | }.to_h 128 | end 129 | 130 | INITIAL_SCHEMES = scheme_list 131 | private_constant :INITIAL_SCHEMES 132 | Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor) 133 | 134 | # Returns a new object constructed from the given +scheme+, +arguments+, 135 | # and +default+: 136 | # 137 | # - The new object is an instance of URI.scheme_list[scheme.upcase]. 138 | # - The object is initialized by calling the class initializer 139 | # using +scheme+ and +arguments+. 140 | # See URI::Generic.new. 141 | # 142 | # Examples: 143 | # 144 | # values = ['john.doe', 'www.example.com', '123', nil, '/forum/questions/', nil, 'tag=networking&order=newest', 'top'] 145 | # URI.for('https', *values) 146 | # # => # 147 | # URI.for('foo', *values, default: URI::HTTP) 148 | # # => # 149 | # 150 | def self.for(scheme, *arguments, default: Generic) 151 | const_name = scheme.to_s.upcase 152 | 153 | uri_class = INITIAL_SCHEMES[const_name] 154 | uri_class ||= if /\A[A-Z]\w*\z/.match?(const_name) && Schemes.const_defined?(const_name, false) 155 | Schemes.const_get(const_name, false) 156 | end 157 | uri_class ||= default 158 | 159 | return uri_class.new(scheme, *arguments) 160 | end 161 | 162 | # 163 | # Base class for all URI exceptions. 164 | # 165 | class Error < StandardError; end 166 | # 167 | # Not a URI. 168 | # 169 | class InvalidURIError < Error; end 170 | # 171 | # Not a URI component. 172 | # 173 | class InvalidComponentError < Error; end 174 | # 175 | # URI is valid, bad usage is not. 176 | # 177 | class BadURIError < Error; end 178 | 179 | # Returns a 9-element array representing the parts of the \URI 180 | # formed from the string +uri+; 181 | # each array element is a string or +nil+: 182 | # 183 | # names = %w[scheme userinfo host port registry path opaque query fragment] 184 | # values = URI.split('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') 185 | # names.zip(values) 186 | # # => 187 | # [["scheme", "https"], 188 | # ["userinfo", "john.doe"], 189 | # ["host", "www.example.com"], 190 | # ["port", "123"], 191 | # ["registry", nil], 192 | # ["path", "/forum/questions/"], 193 | # ["opaque", nil], 194 | # ["query", "tag=networking&order=newest"], 195 | # ["fragment", "top"]] 196 | # 197 | def self.split(uri) 198 | DEFAULT_PARSER.split(uri) 199 | end 200 | 201 | # Returns a new \URI object constructed from the given string +uri+: 202 | # 203 | # URI.parse('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') 204 | # # => # 205 | # URI.parse('http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') 206 | # # => # 207 | # 208 | # It's recommended to first ::escape string +uri+ 209 | # if it may contain invalid URI characters. 210 | # 211 | def self.parse(uri) 212 | DEFAULT_PARSER.parse(uri) 213 | end 214 | 215 | # Merges the given URI strings +str+ 216 | # per {RFC 2396}[https://www.rfc-editor.org/rfc/rfc2396.html]. 217 | # 218 | # Each string in +str+ is converted to an 219 | # {RFC3986 URI}[https://www.rfc-editor.org/rfc/rfc3986.html] before being merged. 220 | # 221 | # Examples: 222 | # 223 | # URI.join("http://example.com/","main.rbx") 224 | # # => # 225 | # 226 | # URI.join('http://example.com', 'foo') 227 | # # => # 228 | # 229 | # URI.join('http://example.com', '/foo', '/bar') 230 | # # => # 231 | # 232 | # URI.join('http://example.com', '/foo', 'bar') 233 | # # => # 234 | # 235 | # URI.join('http://example.com', '/foo/', 'bar') 236 | # # => # 237 | # 238 | def self.join(*str) 239 | DEFAULT_PARSER.join(*str) 240 | end 241 | 242 | # 243 | # == Synopsis 244 | # 245 | # URI::extract(str[, schemes][,&blk]) 246 | # 247 | # == Args 248 | # 249 | # +str+:: 250 | # String to extract URIs from. 251 | # +schemes+:: 252 | # Limit URI matching to specific schemes. 253 | # 254 | # == Description 255 | # 256 | # Extracts URIs from a string. If block given, iterates through all matched URIs. 257 | # Returns nil if block given or array with matches. 258 | # 259 | # == Usage 260 | # 261 | # require "uri" 262 | # 263 | # URI.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.") 264 | # # => ["http://foo.example.com/bla", "mailto:test@example.com"] 265 | # 266 | def self.extract(str, schemes = nil, &block) # :nodoc: 267 | warn "URI.extract is obsolete", uplevel: 1 if $VERBOSE 268 | DEFAULT_PARSER.extract(str, schemes, &block) 269 | end 270 | 271 | # 272 | # == Synopsis 273 | # 274 | # URI::regexp([match_schemes]) 275 | # 276 | # == Args 277 | # 278 | # +match_schemes+:: 279 | # Array of schemes. If given, resulting regexp matches to URIs 280 | # whose scheme is one of the match_schemes. 281 | # 282 | # == Description 283 | # 284 | # Returns a Regexp object which matches to URI-like strings. 285 | # The Regexp object returned by this method includes arbitrary 286 | # number of capture group (parentheses). Never rely on its number. 287 | # 288 | # == Usage 289 | # 290 | # require 'uri' 291 | # 292 | # # extract first URI from html_string 293 | # html_string.slice(URI.regexp) 294 | # 295 | # # remove ftp URIs 296 | # html_string.sub(URI.regexp(['ftp']), '') 297 | # 298 | # # You should not rely on the number of parentheses 299 | # html_string.scan(URI.regexp) do |*matches| 300 | # p $& 301 | # end 302 | # 303 | def self.regexp(schemes = nil)# :nodoc: 304 | warn "URI.regexp is obsolete", uplevel: 1 if $VERBOSE 305 | DEFAULT_PARSER.make_regexp(schemes) 306 | end 307 | 308 | TBLENCWWWCOMP_ = {} # :nodoc: 309 | 256.times do |i| 310 | TBLENCWWWCOMP_[-i.chr] = -('%%%02X' % i) 311 | end 312 | TBLENCURICOMP_ = TBLENCWWWCOMP_.dup.freeze # :nodoc: 313 | TBLENCWWWCOMP_[' '] = '+' 314 | TBLENCWWWCOMP_.freeze 315 | TBLDECWWWCOMP_ = {} # :nodoc: 316 | 256.times do |i| 317 | h, l = i>>4, i&15 318 | TBLDECWWWCOMP_[-('%%%X%X' % [h, l])] = -i.chr 319 | TBLDECWWWCOMP_[-('%%%x%X' % [h, l])] = -i.chr 320 | TBLDECWWWCOMP_[-('%%%X%x' % [h, l])] = -i.chr 321 | TBLDECWWWCOMP_[-('%%%x%x' % [h, l])] = -i.chr 322 | end 323 | TBLDECWWWCOMP_['+'] = ' ' 324 | TBLDECWWWCOMP_.freeze 325 | 326 | # Returns a URL-encoded string derived from the given string +str+. 327 | # 328 | # The returned string: 329 | # 330 | # - Preserves: 331 | # 332 | # - Characters '*', '.', '-', and '_'. 333 | # - Character in ranges 'a'..'z', 'A'..'Z', 334 | # and '0'..'9'. 335 | # 336 | # Example: 337 | # 338 | # URI.encode_www_form_component('*.-_azAZ09') 339 | # # => "*.-_azAZ09" 340 | # 341 | # - Converts: 342 | # 343 | # - Character ' ' to character '+'. 344 | # - Any other character to "percent notation"; 345 | # the percent notation for character c is '%%%X' % c.ord. 346 | # 347 | # Example: 348 | # 349 | # URI.encode_www_form_component('Here are some punctuation characters: ,;?:') 350 | # # => "Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A" 351 | # 352 | # Encoding: 353 | # 354 | # - If +str+ has encoding Encoding::ASCII_8BIT, argument +enc+ is ignored. 355 | # - Otherwise +str+ is converted first to Encoding::UTF_8 356 | # (with suitable character replacements), 357 | # and then to encoding +enc+. 358 | # 359 | # In either case, the returned string has forced encoding Encoding::US_ASCII. 360 | # 361 | # Related: URI.encode_uri_component (encodes ' ' as '%20'). 362 | def self.encode_www_form_component(str, enc=nil) 363 | _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP_, str, enc) 364 | end 365 | 366 | # Returns a string decoded from the given \URL-encoded string +str+. 367 | # 368 | # The given string is first encoded as Encoding::ASCII-8BIT (using String#b), 369 | # then decoded (as below), and finally force-encoded to the given encoding +enc+. 370 | # 371 | # The returned string: 372 | # 373 | # - Preserves: 374 | # 375 | # - Characters '*', '.', '-', and '_'. 376 | # - Character in ranges 'a'..'z', 'A'..'Z', 377 | # and '0'..'9'. 378 | # 379 | # Example: 380 | # 381 | # URI.decode_www_form_component('*.-_azAZ09') 382 | # # => "*.-_azAZ09" 383 | # 384 | # - Converts: 385 | # 386 | # - Character '+' to character ' '. 387 | # - Each "percent notation" to an ASCII character. 388 | # 389 | # Example: 390 | # 391 | # URI.decode_www_form_component('Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A') 392 | # # => "Here are some punctuation characters: ,;?:" 393 | # 394 | # Related: URI.decode_uri_component (preserves '+'). 395 | def self.decode_www_form_component(str, enc=Encoding::UTF_8) 396 | _decode_uri_component(/\+|%\h\h/, str, enc) 397 | end 398 | 399 | # Like URI.encode_www_form_component, except that ' ' (space) 400 | # is encoded as '%20' (instead of '+'). 401 | def self.encode_uri_component(str, enc=nil) 402 | _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCURICOMP_, str, enc) 403 | end 404 | 405 | # Like URI.decode_www_form_component, except that '+' is preserved. 406 | def self.decode_uri_component(str, enc=Encoding::UTF_8) 407 | _decode_uri_component(/%\h\h/, str, enc) 408 | end 409 | 410 | def self._encode_uri_component(regexp, table, str, enc) 411 | str = str.to_s.dup 412 | if str.encoding != Encoding::ASCII_8BIT 413 | if enc && enc != Encoding::ASCII_8BIT 414 | str.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace) 415 | str.encode!(enc, fallback: ->(x){"&##{x.ord};"}) 416 | end 417 | str.force_encoding(Encoding::ASCII_8BIT) 418 | end 419 | str.gsub!(regexp, table) 420 | str.force_encoding(Encoding::US_ASCII) 421 | end 422 | private_class_method :_encode_uri_component 423 | 424 | def self._decode_uri_component(regexp, str, enc) 425 | raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/.match?(str) 426 | str.b.gsub(regexp, TBLDECWWWCOMP_).force_encoding(enc) 427 | end 428 | private_class_method :_decode_uri_component 429 | 430 | # Returns a URL-encoded string derived from the given 431 | # {Enumerable}[https://docs.ruby-lang.org/en/master/Enumerable.html#module-Enumerable-label-Enumerable+in+Ruby+Classes] 432 | # +enum+. 433 | # 434 | # The result is suitable for use as form data 435 | # for an \HTTP request whose Content-Type is 436 | # 'application/x-www-form-urlencoded'. 437 | # 438 | # The returned string consists of the elements of +enum+, 439 | # each converted to one or more URL-encoded strings, 440 | # and all joined with character '&'. 441 | # 442 | # Simple examples: 443 | # 444 | # URI.encode_www_form([['foo', 0], ['bar', 1], ['baz', 2]]) 445 | # # => "foo=0&bar=1&baz=2" 446 | # URI.encode_www_form({foo: 0, bar: 1, baz: 2}) 447 | # # => "foo=0&bar=1&baz=2" 448 | # 449 | # The returned string is formed using method URI.encode_www_form_component, 450 | # which converts certain characters: 451 | # 452 | # URI.encode_www_form('f#o': '/', 'b-r': '$', 'b z': '@') 453 | # # => "f%23o=%2F&b-r=%24&b+z=%40" 454 | # 455 | # When +enum+ is Array-like, each element +ele+ is converted to a field: 456 | # 457 | # - If +ele+ is an array of two or more elements, 458 | # the field is formed from its first two elements 459 | # (and any additional elements are ignored): 460 | # 461 | # name = URI.encode_www_form_component(ele[0], enc) 462 | # value = URI.encode_www_form_component(ele[1], enc) 463 | # "#{name}=#{value}" 464 | # 465 | # Examples: 466 | # 467 | # URI.encode_www_form([%w[foo bar], %w[baz bat bah]]) 468 | # # => "foo=bar&baz=bat" 469 | # URI.encode_www_form([['foo', 0], ['bar', :baz, 'bat']]) 470 | # # => "foo=0&bar=baz" 471 | # 472 | # - If +ele+ is an array of one element, 473 | # the field is formed from ele[0]: 474 | # 475 | # URI.encode_www_form_component(ele[0]) 476 | # 477 | # Example: 478 | # 479 | # URI.encode_www_form([['foo'], [:bar], [0]]) 480 | # # => "foo&bar&0" 481 | # 482 | # - Otherwise the field is formed from +ele+: 483 | # 484 | # URI.encode_www_form_component(ele) 485 | # 486 | # Example: 487 | # 488 | # URI.encode_www_form(['foo', :bar, 0]) 489 | # # => "foo&bar&0" 490 | # 491 | # The elements of an Array-like +enum+ may be mixture: 492 | # 493 | # URI.encode_www_form([['foo', 0], ['bar', 1, 2], ['baz'], :bat]) 494 | # # => "foo=0&bar=1&baz&bat" 495 | # 496 | # When +enum+ is Hash-like, 497 | # each +key+/+value+ pair is converted to one or more fields: 498 | # 499 | # - If +value+ is 500 | # {Array-convertible}[https://docs.ruby-lang.org/en/master/implicit_conversion_rdoc.html#label-Array-Convertible+Objects], 501 | # each element +ele+ in +value+ is paired with +key+ to form a field: 502 | # 503 | # name = URI.encode_www_form_component(key, enc) 504 | # value = URI.encode_www_form_component(ele, enc) 505 | # "#{name}=#{value}" 506 | # 507 | # Example: 508 | # 509 | # URI.encode_www_form({foo: [:bar, 1], baz: [:bat, :bam, 2]}) 510 | # # => "foo=bar&foo=1&baz=bat&baz=bam&baz=2" 511 | # 512 | # - Otherwise, +key+ and +value+ are paired to form a field: 513 | # 514 | # name = URI.encode_www_form_component(key, enc) 515 | # value = URI.encode_www_form_component(value, enc) 516 | # "#{name}=#{value}" 517 | # 518 | # Example: 519 | # 520 | # URI.encode_www_form({foo: 0, bar: 1, baz: 2}) 521 | # # => "foo=0&bar=1&baz=2" 522 | # 523 | # The elements of a Hash-like +enum+ may be mixture: 524 | # 525 | # URI.encode_www_form({foo: [0, 1], bar: 2}) 526 | # # => "foo=0&foo=1&bar=2" 527 | # 528 | def self.encode_www_form(enum, enc=nil) 529 | enum.map do |k,v| 530 | if v.nil? 531 | encode_www_form_component(k, enc) 532 | elsif v.respond_to?(:to_ary) 533 | v.to_ary.map do |w| 534 | str = encode_www_form_component(k, enc) 535 | unless w.nil? 536 | str << '=' 537 | str << encode_www_form_component(w, enc) 538 | end 539 | end.join('&') 540 | else 541 | str = encode_www_form_component(k, enc) 542 | str << '=' 543 | str << encode_www_form_component(v, enc) 544 | end 545 | end.join('&') 546 | end 547 | 548 | # Returns name/value pairs derived from the given string +str+, 549 | # which must be an ASCII string. 550 | # 551 | # The method may be used to decode the body of Net::HTTPResponse object +res+ 552 | # for which res['Content-Type'] is 'application/x-www-form-urlencoded'. 553 | # 554 | # The returned data is an array of 2-element subarrays; 555 | # each subarray is a name/value pair (both are strings). 556 | # Each returned string has encoding +enc+, 557 | # and has had invalid characters removed via 558 | # {String#scrub}[https://docs.ruby-lang.org/en/master/String.html#method-i-scrub]. 559 | # 560 | # A simple example: 561 | # 562 | # URI.decode_www_form('foo=0&bar=1&baz') 563 | # # => [["foo", "0"], ["bar", "1"], ["baz", ""]] 564 | # 565 | # The returned strings have certain conversions, 566 | # similar to those performed in URI.decode_www_form_component: 567 | # 568 | # URI.decode_www_form('f%23o=%2F&b-r=%24&b+z=%40') 569 | # # => [["f#o", "/"], ["b-r", "$"], ["b z", "@"]] 570 | # 571 | # The given string may contain consecutive separators: 572 | # 573 | # URI.decode_www_form('foo=0&&bar=1&&baz=2') 574 | # # => [["foo", "0"], ["", ""], ["bar", "1"], ["", ""], ["baz", "2"]] 575 | # 576 | # A different separator may be specified: 577 | # 578 | # URI.decode_www_form('foo=0--bar=1--baz', separator: '--') 579 | # # => [["foo", "0"], ["bar", "1"], ["baz", ""]] 580 | # 581 | def self.decode_www_form(str, enc=Encoding::UTF_8, separator: '&', use__charset_: false, isindex: false) 582 | raise ArgumentError, "the input of #{self.name}.#{__method__} must be ASCII only string" unless str.ascii_only? 583 | ary = [] 584 | return ary if str.empty? 585 | enc = Encoding.find(enc) 586 | str.b.each_line(separator) do |string| 587 | string.chomp!(separator) 588 | key, sep, val = string.partition('=') 589 | if isindex 590 | if sep.empty? 591 | val = key 592 | key = +'' 593 | end 594 | isindex = false 595 | end 596 | 597 | if use__charset_ and key == '_charset_' and e = get_encoding(val) 598 | enc = e 599 | use__charset_ = false 600 | end 601 | 602 | key.gsub!(/\+|%\h\h/, TBLDECWWWCOMP_) 603 | if val 604 | val.gsub!(/\+|%\h\h/, TBLDECWWWCOMP_) 605 | else 606 | val = +'' 607 | end 608 | 609 | ary << [key, val] 610 | end 611 | ary.each do |k, v| 612 | k.force_encoding(enc) 613 | k.scrub! 614 | v.force_encoding(enc) 615 | v.scrub! 616 | end 617 | ary 618 | end 619 | 620 | private 621 | =begin command for WEB_ENCODINGS_ 622 | curl https://encoding.spec.whatwg.org/encodings.json| 623 | ruby -rjson -e 'H={} 624 | h={ 625 | "shift_jis"=>"Windows-31J", 626 | "euc-jp"=>"cp51932", 627 | "iso-2022-jp"=>"cp50221", 628 | "x-mac-cyrillic"=>"macCyrillic", 629 | } 630 | JSON($<.read).map{|x|x["encodings"]}.flatten.each{|x| 631 | Encoding.find(n=h.fetch(n=x["name"].downcase,n))rescue next 632 | x["labels"].each{|y|H[y]=n} 633 | } 634 | puts "{" 635 | H.each{|k,v|puts %[ #{k.dump}=>#{v.dump},]} 636 | puts "}" 637 | ' 638 | =end 639 | WEB_ENCODINGS_ = { 640 | "unicode-1-1-utf-8"=>"utf-8", 641 | "utf-8"=>"utf-8", 642 | "utf8"=>"utf-8", 643 | "866"=>"ibm866", 644 | "cp866"=>"ibm866", 645 | "csibm866"=>"ibm866", 646 | "ibm866"=>"ibm866", 647 | "csisolatin2"=>"iso-8859-2", 648 | "iso-8859-2"=>"iso-8859-2", 649 | "iso-ir-101"=>"iso-8859-2", 650 | "iso8859-2"=>"iso-8859-2", 651 | "iso88592"=>"iso-8859-2", 652 | "iso_8859-2"=>"iso-8859-2", 653 | "iso_8859-2:1987"=>"iso-8859-2", 654 | "l2"=>"iso-8859-2", 655 | "latin2"=>"iso-8859-2", 656 | "csisolatin3"=>"iso-8859-3", 657 | "iso-8859-3"=>"iso-8859-3", 658 | "iso-ir-109"=>"iso-8859-3", 659 | "iso8859-3"=>"iso-8859-3", 660 | "iso88593"=>"iso-8859-3", 661 | "iso_8859-3"=>"iso-8859-3", 662 | "iso_8859-3:1988"=>"iso-8859-3", 663 | "l3"=>"iso-8859-3", 664 | "latin3"=>"iso-8859-3", 665 | "csisolatin4"=>"iso-8859-4", 666 | "iso-8859-4"=>"iso-8859-4", 667 | "iso-ir-110"=>"iso-8859-4", 668 | "iso8859-4"=>"iso-8859-4", 669 | "iso88594"=>"iso-8859-4", 670 | "iso_8859-4"=>"iso-8859-4", 671 | "iso_8859-4:1988"=>"iso-8859-4", 672 | "l4"=>"iso-8859-4", 673 | "latin4"=>"iso-8859-4", 674 | "csisolatincyrillic"=>"iso-8859-5", 675 | "cyrillic"=>"iso-8859-5", 676 | "iso-8859-5"=>"iso-8859-5", 677 | "iso-ir-144"=>"iso-8859-5", 678 | "iso8859-5"=>"iso-8859-5", 679 | "iso88595"=>"iso-8859-5", 680 | "iso_8859-5"=>"iso-8859-5", 681 | "iso_8859-5:1988"=>"iso-8859-5", 682 | "arabic"=>"iso-8859-6", 683 | "asmo-708"=>"iso-8859-6", 684 | "csiso88596e"=>"iso-8859-6", 685 | "csiso88596i"=>"iso-8859-6", 686 | "csisolatinarabic"=>"iso-8859-6", 687 | "ecma-114"=>"iso-8859-6", 688 | "iso-8859-6"=>"iso-8859-6", 689 | "iso-8859-6-e"=>"iso-8859-6", 690 | "iso-8859-6-i"=>"iso-8859-6", 691 | "iso-ir-127"=>"iso-8859-6", 692 | "iso8859-6"=>"iso-8859-6", 693 | "iso88596"=>"iso-8859-6", 694 | "iso_8859-6"=>"iso-8859-6", 695 | "iso_8859-6:1987"=>"iso-8859-6", 696 | "csisolatingreek"=>"iso-8859-7", 697 | "ecma-118"=>"iso-8859-7", 698 | "elot_928"=>"iso-8859-7", 699 | "greek"=>"iso-8859-7", 700 | "greek8"=>"iso-8859-7", 701 | "iso-8859-7"=>"iso-8859-7", 702 | "iso-ir-126"=>"iso-8859-7", 703 | "iso8859-7"=>"iso-8859-7", 704 | "iso88597"=>"iso-8859-7", 705 | "iso_8859-7"=>"iso-8859-7", 706 | "iso_8859-7:1987"=>"iso-8859-7", 707 | "sun_eu_greek"=>"iso-8859-7", 708 | "csiso88598e"=>"iso-8859-8", 709 | "csisolatinhebrew"=>"iso-8859-8", 710 | "hebrew"=>"iso-8859-8", 711 | "iso-8859-8"=>"iso-8859-8", 712 | "iso-8859-8-e"=>"iso-8859-8", 713 | "iso-ir-138"=>"iso-8859-8", 714 | "iso8859-8"=>"iso-8859-8", 715 | "iso88598"=>"iso-8859-8", 716 | "iso_8859-8"=>"iso-8859-8", 717 | "iso_8859-8:1988"=>"iso-8859-8", 718 | "visual"=>"iso-8859-8", 719 | "csisolatin6"=>"iso-8859-10", 720 | "iso-8859-10"=>"iso-8859-10", 721 | "iso-ir-157"=>"iso-8859-10", 722 | "iso8859-10"=>"iso-8859-10", 723 | "iso885910"=>"iso-8859-10", 724 | "l6"=>"iso-8859-10", 725 | "latin6"=>"iso-8859-10", 726 | "iso-8859-13"=>"iso-8859-13", 727 | "iso8859-13"=>"iso-8859-13", 728 | "iso885913"=>"iso-8859-13", 729 | "iso-8859-14"=>"iso-8859-14", 730 | "iso8859-14"=>"iso-8859-14", 731 | "iso885914"=>"iso-8859-14", 732 | "csisolatin9"=>"iso-8859-15", 733 | "iso-8859-15"=>"iso-8859-15", 734 | "iso8859-15"=>"iso-8859-15", 735 | "iso885915"=>"iso-8859-15", 736 | "iso_8859-15"=>"iso-8859-15", 737 | "l9"=>"iso-8859-15", 738 | "iso-8859-16"=>"iso-8859-16", 739 | "cskoi8r"=>"koi8-r", 740 | "koi"=>"koi8-r", 741 | "koi8"=>"koi8-r", 742 | "koi8-r"=>"koi8-r", 743 | "koi8_r"=>"koi8-r", 744 | "koi8-ru"=>"koi8-u", 745 | "koi8-u"=>"koi8-u", 746 | "dos-874"=>"windows-874", 747 | "iso-8859-11"=>"windows-874", 748 | "iso8859-11"=>"windows-874", 749 | "iso885911"=>"windows-874", 750 | "tis-620"=>"windows-874", 751 | "windows-874"=>"windows-874", 752 | "cp1250"=>"windows-1250", 753 | "windows-1250"=>"windows-1250", 754 | "x-cp1250"=>"windows-1250", 755 | "cp1251"=>"windows-1251", 756 | "windows-1251"=>"windows-1251", 757 | "x-cp1251"=>"windows-1251", 758 | "ansi_x3.4-1968"=>"windows-1252", 759 | "ascii"=>"windows-1252", 760 | "cp1252"=>"windows-1252", 761 | "cp819"=>"windows-1252", 762 | "csisolatin1"=>"windows-1252", 763 | "ibm819"=>"windows-1252", 764 | "iso-8859-1"=>"windows-1252", 765 | "iso-ir-100"=>"windows-1252", 766 | "iso8859-1"=>"windows-1252", 767 | "iso88591"=>"windows-1252", 768 | "iso_8859-1"=>"windows-1252", 769 | "iso_8859-1:1987"=>"windows-1252", 770 | "l1"=>"windows-1252", 771 | "latin1"=>"windows-1252", 772 | "us-ascii"=>"windows-1252", 773 | "windows-1252"=>"windows-1252", 774 | "x-cp1252"=>"windows-1252", 775 | "cp1253"=>"windows-1253", 776 | "windows-1253"=>"windows-1253", 777 | "x-cp1253"=>"windows-1253", 778 | "cp1254"=>"windows-1254", 779 | "csisolatin5"=>"windows-1254", 780 | "iso-8859-9"=>"windows-1254", 781 | "iso-ir-148"=>"windows-1254", 782 | "iso8859-9"=>"windows-1254", 783 | "iso88599"=>"windows-1254", 784 | "iso_8859-9"=>"windows-1254", 785 | "iso_8859-9:1989"=>"windows-1254", 786 | "l5"=>"windows-1254", 787 | "latin5"=>"windows-1254", 788 | "windows-1254"=>"windows-1254", 789 | "x-cp1254"=>"windows-1254", 790 | "cp1255"=>"windows-1255", 791 | "windows-1255"=>"windows-1255", 792 | "x-cp1255"=>"windows-1255", 793 | "cp1256"=>"windows-1256", 794 | "windows-1256"=>"windows-1256", 795 | "x-cp1256"=>"windows-1256", 796 | "cp1257"=>"windows-1257", 797 | "windows-1257"=>"windows-1257", 798 | "x-cp1257"=>"windows-1257", 799 | "cp1258"=>"windows-1258", 800 | "windows-1258"=>"windows-1258", 801 | "x-cp1258"=>"windows-1258", 802 | "x-mac-cyrillic"=>"macCyrillic", 803 | "x-mac-ukrainian"=>"macCyrillic", 804 | "chinese"=>"gbk", 805 | "csgb2312"=>"gbk", 806 | "csiso58gb231280"=>"gbk", 807 | "gb2312"=>"gbk", 808 | "gb_2312"=>"gbk", 809 | "gb_2312-80"=>"gbk", 810 | "gbk"=>"gbk", 811 | "iso-ir-58"=>"gbk", 812 | "x-gbk"=>"gbk", 813 | "gb18030"=>"gb18030", 814 | "big5"=>"big5", 815 | "big5-hkscs"=>"big5", 816 | "cn-big5"=>"big5", 817 | "csbig5"=>"big5", 818 | "x-x-big5"=>"big5", 819 | "cseucpkdfmtjapanese"=>"cp51932", 820 | "euc-jp"=>"cp51932", 821 | "x-euc-jp"=>"cp51932", 822 | "csiso2022jp"=>"cp50221", 823 | "iso-2022-jp"=>"cp50221", 824 | "csshiftjis"=>"Windows-31J", 825 | "ms932"=>"Windows-31J", 826 | "ms_kanji"=>"Windows-31J", 827 | "shift-jis"=>"Windows-31J", 828 | "shift_jis"=>"Windows-31J", 829 | "sjis"=>"Windows-31J", 830 | "windows-31j"=>"Windows-31J", 831 | "x-sjis"=>"Windows-31J", 832 | "cseuckr"=>"euc-kr", 833 | "csksc56011987"=>"euc-kr", 834 | "euc-kr"=>"euc-kr", 835 | "iso-ir-149"=>"euc-kr", 836 | "korean"=>"euc-kr", 837 | "ks_c_5601-1987"=>"euc-kr", 838 | "ks_c_5601-1989"=>"euc-kr", 839 | "ksc5601"=>"euc-kr", 840 | "ksc_5601"=>"euc-kr", 841 | "windows-949"=>"euc-kr", 842 | "utf-16be"=>"utf-16be", 843 | "utf-16"=>"utf-16le", 844 | "utf-16le"=>"utf-16le", 845 | } # :nodoc: 846 | Ractor.make_shareable(WEB_ENCODINGS_) if defined?(Ractor) 847 | 848 | # :nodoc: 849 | # return encoding or nil 850 | # http://encoding.spec.whatwg.org/#concept-encoding-get 851 | def self.get_encoding(label) 852 | Encoding.find(WEB_ENCODINGS_[label.to_str.strip.downcase]) rescue nil 853 | end 854 | end # module URI 855 | 856 | module Kernel 857 | 858 | # 859 | # Returns a \URI object derived from the given +uri+, 860 | # which may be a \URI string or an existing \URI object: 861 | # 862 | # # Returns a new URI. 863 | # uri = URI('http://github.com/ruby/ruby') 864 | # # => # 865 | # # Returns the given URI. 866 | # URI(uri) 867 | # # => # 868 | # 869 | def URI(uri) 870 | if uri.is_a?(URI::Generic) 871 | uri 872 | elsif uri = String.try_convert(uri) 873 | URI.parse(uri) 874 | else 875 | raise ArgumentError, 876 | "bad argument (expected URI object or URI string)" 877 | end 878 | end 879 | module_function :URI 880 | end 881 | -------------------------------------------------------------------------------- /lib/uri/file.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'generic' 4 | 5 | module URI 6 | 7 | # 8 | # The "file" URI is defined by RFC8089. 9 | # 10 | class File < Generic 11 | # A Default port of nil for URI::File. 12 | DEFAULT_PORT = nil 13 | 14 | # 15 | # An Array of the available components for URI::File. 16 | # 17 | COMPONENT = [ 18 | :scheme, 19 | :host, 20 | :path 21 | ].freeze 22 | 23 | # 24 | # == Description 25 | # 26 | # Creates a new URI::File object from components, with syntax checking. 27 | # 28 | # The components accepted are +host+ and +path+. 29 | # 30 | # The components should be provided either as an Array, or as a Hash 31 | # with keys formed by preceding the component names with a colon. 32 | # 33 | # If an Array is used, the components must be passed in the 34 | # order [host, path]. 35 | # 36 | # A path from e.g. the File class should be escaped before 37 | # being passed. 38 | # 39 | # Examples: 40 | # 41 | # require 'uri' 42 | # 43 | # uri1 = URI::File.build(['host.example.com', '/path/file.zip']) 44 | # uri1.to_s # => "file://host.example.com/path/file.zip" 45 | # 46 | # uri2 = URI::File.build({:host => 'host.example.com', 47 | # :path => '/ruby/src'}) 48 | # uri2.to_s # => "file://host.example.com/ruby/src" 49 | # 50 | # uri3 = URI::File.build({:path => URI::RFC2396_PARSER.escape('/path/my file.txt')}) 51 | # uri3.to_s # => "file:///path/my%20file.txt" 52 | # 53 | def self.build(args) 54 | tmp = Util::make_components_hash(self, args) 55 | super(tmp) 56 | end 57 | 58 | # Protected setter for the host component +v+. 59 | # 60 | # See also URI::Generic.host=. 61 | # 62 | def set_host(v) 63 | v = "" if v.nil? || v == "localhost" 64 | @host = v 65 | end 66 | 67 | # do nothing 68 | def set_port(v) 69 | end 70 | 71 | # raise InvalidURIError 72 | def check_userinfo(user) 73 | raise URI::InvalidURIError, "cannot set userinfo for file URI" 74 | end 75 | 76 | # raise InvalidURIError 77 | def check_user(user) 78 | raise URI::InvalidURIError, "cannot set user for file URI" 79 | end 80 | 81 | # raise InvalidURIError 82 | def check_password(user) 83 | raise URI::InvalidURIError, "cannot set password for file URI" 84 | end 85 | 86 | # do nothing 87 | def set_userinfo(v) 88 | end 89 | 90 | # do nothing 91 | def set_user(v) 92 | end 93 | 94 | # do nothing 95 | def set_password(v) 96 | end 97 | end 98 | 99 | register_scheme 'FILE', File 100 | end 101 | -------------------------------------------------------------------------------- /lib/uri/ftp.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # = uri/ftp.rb 3 | # 4 | # Author:: Akira Yamada 5 | # License:: You can redistribute it and/or modify it under the same term as Ruby. 6 | # 7 | # See URI for general documentation 8 | # 9 | 10 | require_relative 'generic' 11 | 12 | module URI 13 | 14 | # 15 | # FTP URI syntax is defined by RFC1738 section 3.2. 16 | # 17 | # This class will be redesigned because of difference of implementations; 18 | # the structure of its path. draft-hoffman-ftp-uri-04 is a draft but it 19 | # is a good summary about the de facto spec. 20 | # https://datatracker.ietf.org/doc/html/draft-hoffman-ftp-uri-04 21 | # 22 | class FTP < Generic 23 | # A Default port of 21 for URI::FTP. 24 | DEFAULT_PORT = 21 25 | 26 | # 27 | # An Array of the available components for URI::FTP. 28 | # 29 | COMPONENT = [ 30 | :scheme, 31 | :userinfo, :host, :port, 32 | :path, :typecode 33 | ].freeze 34 | 35 | # 36 | # Typecode is "a", "i", or "d". 37 | # 38 | # * "a" indicates a text file (the FTP command was ASCII) 39 | # * "i" indicates a binary file (FTP command IMAGE) 40 | # * "d" indicates the contents of a directory should be displayed 41 | # 42 | TYPECODE = ['a', 'i', 'd'].freeze 43 | 44 | # Typecode prefix ";type=". 45 | TYPECODE_PREFIX = ';type='.freeze 46 | 47 | def self.new2(user, password, host, port, path, 48 | typecode = nil, arg_check = true) # :nodoc: 49 | # Do not use this method! Not tested. [Bug #7301] 50 | # This methods remains just for compatibility, 51 | # Keep it undocumented until the active maintainer is assigned. 52 | typecode = nil if typecode.size == 0 53 | if typecode && !TYPECODE.include?(typecode) 54 | raise ArgumentError, 55 | "bad typecode is specified: #{typecode}" 56 | end 57 | 58 | # do escape 59 | 60 | self.new('ftp', 61 | [user, password], 62 | host, port, nil, 63 | typecode ? path + TYPECODE_PREFIX + typecode : path, 64 | nil, nil, nil, arg_check) 65 | end 66 | 67 | # 68 | # == Description 69 | # 70 | # Creates a new URI::FTP object from components, with syntax checking. 71 | # 72 | # The components accepted are +userinfo+, +host+, +port+, +path+, and 73 | # +typecode+. 74 | # 75 | # The components should be provided either as an Array, or as a Hash 76 | # with keys formed by preceding the component names with a colon. 77 | # 78 | # If an Array is used, the components must be passed in the 79 | # order [userinfo, host, port, path, typecode]. 80 | # 81 | # If the path supplied is absolute, it will be escaped in order to 82 | # make it absolute in the URI. 83 | # 84 | # Examples: 85 | # 86 | # require 'uri' 87 | # 88 | # uri1 = URI::FTP.build(['user:password', 'ftp.example.com', nil, 89 | # '/path/file.zip', 'i']) 90 | # uri1.to_s # => "ftp://user:password@ftp.example.com/%2Fpath/file.zip;type=i" 91 | # 92 | # uri2 = URI::FTP.build({:host => 'ftp.example.com', 93 | # :path => 'ruby/src'}) 94 | # uri2.to_s # => "ftp://ftp.example.com/ruby/src" 95 | # 96 | def self.build(args) 97 | 98 | # Fix the incoming path to be generic URL syntax 99 | # FTP path -> URL path 100 | # foo/bar /foo/bar 101 | # /foo/bar /%2Ffoo/bar 102 | # 103 | if args.kind_of?(Array) 104 | args[3] = '/' + args[3].sub(/^\//, '%2F') 105 | else 106 | args[:path] = '/' + args[:path].sub(/^\//, '%2F') 107 | end 108 | 109 | tmp = Util::make_components_hash(self, args) 110 | 111 | if tmp[:typecode] 112 | if tmp[:typecode].size == 1 113 | tmp[:typecode] = TYPECODE_PREFIX + tmp[:typecode] 114 | end 115 | tmp[:path] << tmp[:typecode] 116 | end 117 | 118 | return super(tmp) 119 | end 120 | 121 | # 122 | # == Description 123 | # 124 | # Creates a new URI::FTP object from generic URL components with no 125 | # syntax checking. 126 | # 127 | # Unlike build(), this method does not escape the path component as 128 | # required by RFC1738; instead it is treated as per RFC2396. 129 | # 130 | # Arguments are +scheme+, +userinfo+, +host+, +port+, +registry+, +path+, 131 | # +opaque+, +query+, and +fragment+, in that order. 132 | # 133 | def initialize(scheme, 134 | userinfo, host, port, registry, 135 | path, opaque, 136 | query, 137 | fragment, 138 | parser = nil, 139 | arg_check = false) 140 | raise InvalidURIError unless path 141 | path = path.sub(/^\//,'') 142 | path.sub!(/^%2F/,'/') 143 | super(scheme, userinfo, host, port, registry, path, opaque, 144 | query, fragment, parser, arg_check) 145 | @typecode = nil 146 | if tmp = @path.index(TYPECODE_PREFIX) 147 | typecode = @path[tmp + TYPECODE_PREFIX.size..-1] 148 | @path = @path[0..tmp - 1] 149 | 150 | if arg_check 151 | self.typecode = typecode 152 | else 153 | self.set_typecode(typecode) 154 | end 155 | end 156 | end 157 | 158 | # typecode accessor. 159 | # 160 | # See URI::FTP::COMPONENT. 161 | attr_reader :typecode 162 | 163 | # Validates typecode +v+, 164 | # returns +true+ or +false+. 165 | # 166 | def check_typecode(v) 167 | if TYPECODE.include?(v) 168 | return true 169 | else 170 | raise InvalidComponentError, 171 | "bad typecode(expected #{TYPECODE.join(', ')}): #{v}" 172 | end 173 | end 174 | private :check_typecode 175 | 176 | # Private setter for the typecode +v+. 177 | # 178 | # See also URI::FTP.typecode=. 179 | # 180 | def set_typecode(v) 181 | @typecode = v 182 | end 183 | protected :set_typecode 184 | 185 | # 186 | # == Args 187 | # 188 | # +v+:: 189 | # String 190 | # 191 | # == Description 192 | # 193 | # Public setter for the typecode +v+ 194 | # (with validation). 195 | # 196 | # See also URI::FTP.check_typecode. 197 | # 198 | # == Usage 199 | # 200 | # require 'uri' 201 | # 202 | # uri = URI.parse("ftp://john@ftp.example.com/my_file.img") 203 | # #=> # 204 | # uri.typecode = "i" 205 | # uri 206 | # #=> # 207 | # 208 | def typecode=(typecode) 209 | check_typecode(typecode) 210 | set_typecode(typecode) 211 | typecode 212 | end 213 | 214 | def merge(oth) # :nodoc: 215 | tmp = super(oth) 216 | if self != tmp 217 | tmp.set_typecode(oth.typecode) 218 | end 219 | 220 | return tmp 221 | end 222 | 223 | # Returns the path from an FTP URI. 224 | # 225 | # RFC 1738 specifically states that the path for an FTP URI does not 226 | # include the / which separates the URI path from the URI host. Example: 227 | # 228 | # ftp://ftp.example.com/pub/ruby 229 | # 230 | # The above URI indicates that the client should connect to 231 | # ftp.example.com then cd to pub/ruby from the initial login directory. 232 | # 233 | # If you want to cd to an absolute directory, you must include an 234 | # escaped / (%2F) in the path. Example: 235 | # 236 | # ftp://ftp.example.com/%2Fpub/ruby 237 | # 238 | # This method will then return "/pub/ruby". 239 | # 240 | def path 241 | return @path.sub(/^\//,'').sub(/^%2F/,'/') 242 | end 243 | 244 | # Private setter for the path of the URI::FTP. 245 | def set_path(v) 246 | super("/" + v.sub(/^\//, "%2F")) 247 | end 248 | protected :set_path 249 | 250 | # Returns a String representation of the URI::FTP. 251 | def to_s 252 | save_path = nil 253 | if @typecode 254 | save_path = @path 255 | @path = @path + TYPECODE_PREFIX + @typecode 256 | end 257 | str = super 258 | if @typecode 259 | @path = save_path 260 | end 261 | 262 | return str 263 | end 264 | end 265 | 266 | register_scheme 'FTP', FTP 267 | end 268 | -------------------------------------------------------------------------------- /lib/uri/http.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # = uri/http.rb 3 | # 4 | # Author:: Akira Yamada 5 | # License:: You can redistribute it and/or modify it under the same term as Ruby. 6 | # 7 | # See URI for general documentation 8 | # 9 | 10 | require_relative 'generic' 11 | 12 | module URI 13 | 14 | # 15 | # The syntax of HTTP URIs is defined in RFC1738 section 3.3. 16 | # 17 | # Note that the Ruby URI library allows HTTP URLs containing usernames and 18 | # passwords. This is not legal as per the RFC, but used to be 19 | # supported in Internet Explorer 5 and 6, before the MS04-004 security 20 | # update. See . 21 | # 22 | class HTTP < Generic 23 | # A Default port of 80 for URI::HTTP. 24 | DEFAULT_PORT = 80 25 | 26 | # An Array of the available components for URI::HTTP. 27 | COMPONENT = %i[ 28 | scheme 29 | userinfo host port 30 | path 31 | query 32 | fragment 33 | ].freeze 34 | 35 | # 36 | # == Description 37 | # 38 | # Creates a new URI::HTTP object from components, with syntax checking. 39 | # 40 | # The components accepted are userinfo, host, port, path, query, and 41 | # fragment. 42 | # 43 | # The components should be provided either as an Array, or as a Hash 44 | # with keys formed by preceding the component names with a colon. 45 | # 46 | # If an Array is used, the components must be passed in the 47 | # order [userinfo, host, port, path, query, fragment]. 48 | # 49 | # Example: 50 | # 51 | # uri = URI::HTTP.build(host: 'www.example.com', path: '/foo/bar') 52 | # 53 | # uri = URI::HTTP.build([nil, "www.example.com", nil, "/path", 54 | # "query", 'fragment']) 55 | # 56 | # Currently, if passed userinfo components this method generates 57 | # invalid HTTP URIs as per RFC 1738. 58 | # 59 | def self.build(args) 60 | tmp = Util.make_components_hash(self, args) 61 | super(tmp) 62 | end 63 | 64 | # 65 | # == Description 66 | # 67 | # Returns the full path for an HTTP request, as required by Net::HTTP::Get. 68 | # 69 | # If the URI contains a query, the full path is URI#path + '?' + URI#query. 70 | # Otherwise, the path is simply URI#path. 71 | # 72 | # Example: 73 | # 74 | # uri = URI::HTTP.build(path: '/foo/bar', query: 'test=true') 75 | # uri.request_uri # => "/foo/bar?test=true" 76 | # 77 | def request_uri 78 | return unless @path 79 | 80 | url = @query ? "#@path?#@query" : @path.dup 81 | url.start_with?(?/.freeze) ? url : ?/ + url 82 | end 83 | 84 | # 85 | # == Description 86 | # 87 | # Returns the authority for an HTTP uri, as defined in 88 | # https://www.rfc-editor.org/rfc/rfc3986#section-3.2. 89 | # 90 | # 91 | # Example: 92 | # 93 | # URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').authority #=> "www.example.com" 94 | # URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').authority #=> "www.example.com:8000" 95 | # URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').authority #=> "www.example.com" 96 | # 97 | def authority 98 | if port == default_port 99 | host 100 | else 101 | "#{host}:#{port}" 102 | end 103 | end 104 | 105 | # 106 | # == Description 107 | # 108 | # Returns the origin for an HTTP uri, as defined in 109 | # https://www.rfc-editor.org/rfc/rfc6454. 110 | # 111 | # 112 | # Example: 113 | # 114 | # URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').origin #=> "http://www.example.com" 115 | # URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').origin #=> "http://www.example.com:8000" 116 | # URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').origin #=> "http://www.example.com" 117 | # URI::HTTPS.build(host: 'www.example.com', path: '/foo/bar').origin #=> "https://www.example.com" 118 | # 119 | def origin 120 | "#{scheme}://#{authority}" 121 | end 122 | end 123 | 124 | register_scheme 'HTTP', HTTP 125 | end 126 | -------------------------------------------------------------------------------- /lib/uri/https.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # = uri/https.rb 3 | # 4 | # Author:: Akira Yamada 5 | # License:: You can redistribute it and/or modify it under the same term as Ruby. 6 | # 7 | # See URI for general documentation 8 | # 9 | 10 | require_relative 'http' 11 | 12 | module URI 13 | 14 | # The default port for HTTPS URIs is 443, and the scheme is 'https:' rather 15 | # than 'http:'. Other than that, HTTPS URIs are identical to HTTP URIs; 16 | # see URI::HTTP. 17 | class HTTPS < HTTP 18 | # A Default port of 443 for URI::HTTPS 19 | DEFAULT_PORT = 443 20 | end 21 | 22 | register_scheme 'HTTPS', HTTPS 23 | end 24 | -------------------------------------------------------------------------------- /lib/uri/ldap.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # = uri/ldap.rb 3 | # 4 | # Author:: 5 | # Takaaki Tateishi 6 | # Akira Yamada 7 | # License:: 8 | # URI::LDAP is copyrighted free software by Takaaki Tateishi and Akira Yamada. 9 | # You can redistribute it and/or modify it under the same term as Ruby. 10 | # 11 | # See URI for general documentation 12 | # 13 | 14 | require_relative 'generic' 15 | 16 | module URI 17 | 18 | # 19 | # LDAP URI SCHEMA (described in RFC2255). 20 | #-- 21 | # ldap:///[?[?[?[?]]]] 22 | #++ 23 | class LDAP < Generic 24 | 25 | # A Default port of 389 for URI::LDAP. 26 | DEFAULT_PORT = 389 27 | 28 | # An Array of the available components for URI::LDAP. 29 | COMPONENT = [ 30 | :scheme, 31 | :host, :port, 32 | :dn, 33 | :attributes, 34 | :scope, 35 | :filter, 36 | :extensions, 37 | ].freeze 38 | 39 | # Scopes available for the starting point. 40 | # 41 | # * SCOPE_BASE - the Base DN 42 | # * SCOPE_ONE - one level under the Base DN, not including the base DN and 43 | # not including any entries under this 44 | # * SCOPE_SUB - subtrees, all entries at all levels 45 | # 46 | SCOPE = [ 47 | SCOPE_ONE = 'one', 48 | SCOPE_SUB = 'sub', 49 | SCOPE_BASE = 'base', 50 | ].freeze 51 | 52 | # 53 | # == Description 54 | # 55 | # Creates a new URI::LDAP object from components, with syntax checking. 56 | # 57 | # The components accepted are host, port, dn, attributes, 58 | # scope, filter, and extensions. 59 | # 60 | # The components should be provided either as an Array, or as a Hash 61 | # with keys formed by preceding the component names with a colon. 62 | # 63 | # If an Array is used, the components must be passed in the 64 | # order [host, port, dn, attributes, scope, filter, extensions]. 65 | # 66 | # Example: 67 | # 68 | # uri = URI::LDAP.build({:host => 'ldap.example.com', 69 | # :dn => '/dc=example'}) 70 | # 71 | # uri = URI::LDAP.build(["ldap.example.com", nil, 72 | # "/dc=example;dc=com", "query", nil, nil, nil]) 73 | # 74 | def self.build(args) 75 | tmp = Util::make_components_hash(self, args) 76 | 77 | if tmp[:dn] 78 | tmp[:path] = tmp[:dn] 79 | end 80 | 81 | query = [] 82 | [:extensions, :filter, :scope, :attributes].collect do |x| 83 | next if !tmp[x] && query.size == 0 84 | query.unshift(tmp[x]) 85 | end 86 | 87 | tmp[:query] = query.join('?') 88 | 89 | return super(tmp) 90 | end 91 | 92 | # 93 | # == Description 94 | # 95 | # Creates a new URI::LDAP object from generic URI components as per 96 | # RFC 2396. No LDAP-specific syntax checking is performed. 97 | # 98 | # Arguments are +scheme+, +userinfo+, +host+, +port+, +registry+, +path+, 99 | # +opaque+, +query+, and +fragment+, in that order. 100 | # 101 | # Example: 102 | # 103 | # uri = URI::LDAP.new("ldap", nil, "ldap.example.com", nil, nil, 104 | # "/dc=example;dc=com", nil, "query", nil) 105 | # 106 | # See also URI::Generic.new. 107 | # 108 | def initialize(*arg) 109 | super(*arg) 110 | 111 | if @fragment 112 | raise InvalidURIError, 'bad LDAP URL' 113 | end 114 | 115 | parse_dn 116 | parse_query 117 | end 118 | 119 | # Private method to cleanup +dn+ from using the +path+ component attribute. 120 | def parse_dn 121 | raise InvalidURIError, 'bad LDAP URL' unless @path 122 | @dn = @path[1..-1] 123 | end 124 | private :parse_dn 125 | 126 | # Private method to cleanup +attributes+, +scope+, +filter+, and +extensions+ 127 | # from using the +query+ component attribute. 128 | def parse_query 129 | @attributes = nil 130 | @scope = nil 131 | @filter = nil 132 | @extensions = nil 133 | 134 | if @query 135 | attrs, scope, filter, extensions = @query.split('?') 136 | 137 | @attributes = attrs if attrs && attrs.size > 0 138 | @scope = scope if scope && scope.size > 0 139 | @filter = filter if filter && filter.size > 0 140 | @extensions = extensions if extensions && extensions.size > 0 141 | end 142 | end 143 | private :parse_query 144 | 145 | # Private method to assemble +query+ from +attributes+, +scope+, +filter+, and +extensions+. 146 | def build_path_query 147 | @path = '/' + @dn 148 | 149 | query = [] 150 | [@extensions, @filter, @scope, @attributes].each do |x| 151 | next if !x && query.size == 0 152 | query.unshift(x) 153 | end 154 | @query = query.join('?') 155 | end 156 | private :build_path_query 157 | 158 | # Returns dn. 159 | def dn 160 | @dn 161 | end 162 | 163 | # Private setter for dn +val+. 164 | def set_dn(val) 165 | @dn = val 166 | build_path_query 167 | @dn 168 | end 169 | protected :set_dn 170 | 171 | # Setter for dn +val+. 172 | def dn=(val) 173 | set_dn(val) 174 | val 175 | end 176 | 177 | # Returns attributes. 178 | def attributes 179 | @attributes 180 | end 181 | 182 | # Private setter for attributes +val+. 183 | def set_attributes(val) 184 | @attributes = val 185 | build_path_query 186 | @attributes 187 | end 188 | protected :set_attributes 189 | 190 | # Setter for attributes +val+. 191 | def attributes=(val) 192 | set_attributes(val) 193 | val 194 | end 195 | 196 | # Returns scope. 197 | def scope 198 | @scope 199 | end 200 | 201 | # Private setter for scope +val+. 202 | def set_scope(val) 203 | @scope = val 204 | build_path_query 205 | @scope 206 | end 207 | protected :set_scope 208 | 209 | # Setter for scope +val+. 210 | def scope=(val) 211 | set_scope(val) 212 | val 213 | end 214 | 215 | # Returns filter. 216 | def filter 217 | @filter 218 | end 219 | 220 | # Private setter for filter +val+. 221 | def set_filter(val) 222 | @filter = val 223 | build_path_query 224 | @filter 225 | end 226 | protected :set_filter 227 | 228 | # Setter for filter +val+. 229 | def filter=(val) 230 | set_filter(val) 231 | val 232 | end 233 | 234 | # Returns extensions. 235 | def extensions 236 | @extensions 237 | end 238 | 239 | # Private setter for extensions +val+. 240 | def set_extensions(val) 241 | @extensions = val 242 | build_path_query 243 | @extensions 244 | end 245 | protected :set_extensions 246 | 247 | # Setter for extensions +val+. 248 | def extensions=(val) 249 | set_extensions(val) 250 | val 251 | end 252 | 253 | # Checks if URI has a path. 254 | # For URI::LDAP this will return +false+. 255 | def hierarchical? 256 | false 257 | end 258 | end 259 | 260 | register_scheme 'LDAP', LDAP 261 | end 262 | -------------------------------------------------------------------------------- /lib/uri/ldaps.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # = uri/ldap.rb 3 | # 4 | # License:: You can redistribute it and/or modify it under the same term as Ruby. 5 | # 6 | # See URI for general documentation 7 | # 8 | 9 | require_relative 'ldap' 10 | 11 | module URI 12 | 13 | # The default port for LDAPS URIs is 636, and the scheme is 'ldaps:' rather 14 | # than 'ldap:'. Other than that, LDAPS URIs are identical to LDAP URIs; 15 | # see URI::LDAP. 16 | class LDAPS < LDAP 17 | # A Default port of 636 for URI::LDAPS 18 | DEFAULT_PORT = 636 19 | end 20 | 21 | register_scheme 'LDAPS', LDAPS 22 | end 23 | -------------------------------------------------------------------------------- /lib/uri/mailto.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # = uri/mailto.rb 3 | # 4 | # Author:: Akira Yamada 5 | # License:: You can redistribute it and/or modify it under the same term as Ruby. 6 | # 7 | # See URI for general documentation 8 | # 9 | 10 | require_relative 'generic' 11 | 12 | module URI 13 | 14 | # 15 | # RFC6068, the mailto URL scheme. 16 | # 17 | class MailTo < Generic 18 | include RFC2396_REGEXP 19 | 20 | # A Default port of nil for URI::MailTo. 21 | DEFAULT_PORT = nil 22 | 23 | # An Array of the available components for URI::MailTo. 24 | COMPONENT = [ :scheme, :to, :headers ].freeze 25 | 26 | # :stopdoc: 27 | # "hname" and "hvalue" are encodings of an RFC 822 header name and 28 | # value, respectively. As with "to", all URL reserved characters must 29 | # be encoded. 30 | # 31 | # "#mailbox" is as specified in RFC 822 [RFC822]. This means that it 32 | # consists of zero or more comma-separated mail addresses, possibly 33 | # including "phrase" and "comment" components. Note that all URL 34 | # reserved characters in "to" must be encoded: in particular, 35 | # parentheses, commas, and the percent sign ("%"), which commonly occur 36 | # in the "mailbox" syntax. 37 | # 38 | # Within mailto URLs, the characters "?", "=", "&" are reserved. 39 | 40 | # ; RFC 6068 41 | # hfields = "?" hfield *( "&" hfield ) 42 | # hfield = hfname "=" hfvalue 43 | # hfname = *qchar 44 | # hfvalue = *qchar 45 | # qchar = unreserved / pct-encoded / some-delims 46 | # some-delims = "!" / "$" / "'" / "(" / ")" / "*" 47 | # / "+" / "," / ";" / ":" / "@" 48 | # 49 | # ; RFC3986 50 | # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 51 | # pct-encoded = "%" HEXDIG HEXDIG 52 | HEADER_REGEXP = /\A(?(?:%\h\h|[!$'-.0-;@-Z_a-z~])*=(?:%\h\h|[!$'-.0-;@-Z_a-z~])*)(?:&\g)*\z/ 53 | # practical regexp for email address 54 | # https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address 55 | 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/ 56 | # :startdoc: 57 | 58 | # 59 | # == Description 60 | # 61 | # Creates a new URI::MailTo object from components, with syntax checking. 62 | # 63 | # Components can be provided as an Array or Hash. If an Array is used, 64 | # the components must be supplied as [to, headers]. 65 | # 66 | # If a Hash is used, the keys are the component names preceded by colons. 67 | # 68 | # The headers can be supplied as a pre-encoded string, such as 69 | # "subject=subscribe&cc=address", or as an Array of Arrays 70 | # like [['subject', 'subscribe'], ['cc', 'address']]. 71 | # 72 | # Examples: 73 | # 74 | # require 'uri' 75 | # 76 | # m1 = URI::MailTo.build(['joe@example.com', 'subject=Ruby']) 77 | # m1.to_s # => "mailto:joe@example.com?subject=Ruby" 78 | # 79 | # m2 = URI::MailTo.build(['john@example.com', [['Subject', 'Ruby'], ['Cc', 'jack@example.com']]]) 80 | # m2.to_s # => "mailto:john@example.com?Subject=Ruby&Cc=jack@example.com" 81 | # 82 | # m3 = URI::MailTo.build({:to => 'listman@example.com', :headers => [['subject', 'subscribe']]}) 83 | # m3.to_s # => "mailto:listman@example.com?subject=subscribe" 84 | # 85 | def self.build(args) 86 | tmp = Util.make_components_hash(self, args) 87 | 88 | case tmp[:to] 89 | when Array 90 | tmp[:opaque] = tmp[:to].join(',') 91 | when String 92 | tmp[:opaque] = tmp[:to].dup 93 | else 94 | tmp[:opaque] = '' 95 | end 96 | 97 | if tmp[:headers] 98 | query = 99 | case tmp[:headers] 100 | when Array 101 | tmp[:headers].collect { |x| 102 | if x.kind_of?(Array) 103 | x[0] + '=' + x[1..-1].join 104 | else 105 | x.to_s 106 | end 107 | }.join('&') 108 | when Hash 109 | tmp[:headers].collect { |h,v| 110 | h + '=' + v 111 | }.join('&') 112 | else 113 | tmp[:headers].to_s 114 | end 115 | unless query.empty? 116 | tmp[:opaque] << '?' << query 117 | end 118 | end 119 | 120 | super(tmp) 121 | end 122 | 123 | # 124 | # == Description 125 | # 126 | # Creates a new URI::MailTo object from generic URL components with 127 | # no syntax checking. 128 | # 129 | # This method is usually called from URI::parse, which checks 130 | # the validity of each component. 131 | # 132 | def initialize(*arg) 133 | super(*arg) 134 | 135 | @to = nil 136 | @headers = [] 137 | 138 | # The RFC3986 parser does not normally populate opaque 139 | @opaque = "?#{@query}" if @query && !@opaque 140 | 141 | unless @opaque 142 | raise InvalidComponentError, 143 | "missing opaque part for mailto URL" 144 | end 145 | to, header = @opaque.split('?', 2) 146 | # allow semicolon as a addr-spec separator 147 | # http://support.microsoft.com/kb/820868 148 | unless /\A(?:[^@,;]+@[^@,;]+(?:\z|[,;]))*\z/ =~ to 149 | raise InvalidComponentError, 150 | "unrecognised opaque part for mailtoURL: #{@opaque}" 151 | end 152 | 153 | if arg[10] # arg_check 154 | self.to = to 155 | self.headers = header 156 | else 157 | set_to(to) 158 | set_headers(header) 159 | end 160 | end 161 | 162 | # The primary e-mail address of the URL, as a String. 163 | attr_reader :to 164 | 165 | # E-mail headers set by the URL, as an Array of Arrays. 166 | attr_reader :headers 167 | 168 | # Checks the to +v+ component. 169 | def check_to(v) 170 | return true unless v 171 | return true if v.size == 0 172 | 173 | v.split(/[,;]/).each do |addr| 174 | # check url safety as path-rootless 175 | if /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*\z/ !~ addr 176 | raise InvalidComponentError, 177 | "an address in 'to' is invalid as URI #{addr.dump}" 178 | end 179 | 180 | # check addr-spec 181 | # don't s/\+/ /g 182 | addr.gsub!(/%\h\h/, URI::TBLDECWWWCOMP_) 183 | if EMAIL_REGEXP !~ addr 184 | raise InvalidComponentError, 185 | "an address in 'to' is invalid as uri-escaped addr-spec #{addr.dump}" 186 | end 187 | end 188 | 189 | true 190 | end 191 | private :check_to 192 | 193 | # Private setter for to +v+. 194 | def set_to(v) 195 | @to = v 196 | end 197 | protected :set_to 198 | 199 | # Setter for to +v+. 200 | def to=(v) 201 | check_to(v) 202 | set_to(v) 203 | v 204 | end 205 | 206 | # Checks the headers +v+ component against either 207 | # * HEADER_REGEXP 208 | def check_headers(v) 209 | return true unless v 210 | return true if v.size == 0 211 | if HEADER_REGEXP !~ v 212 | raise InvalidComponentError, 213 | "bad component(expected opaque component): #{v}" 214 | end 215 | 216 | true 217 | end 218 | private :check_headers 219 | 220 | # Private setter for headers +v+. 221 | def set_headers(v) 222 | @headers = [] 223 | if v 224 | v.split('&').each do |x| 225 | @headers << x.split(/=/, 2) 226 | end 227 | end 228 | end 229 | protected :set_headers 230 | 231 | # Setter for headers +v+. 232 | def headers=(v) 233 | check_headers(v) 234 | set_headers(v) 235 | v 236 | end 237 | 238 | # Constructs String from URI. 239 | def to_s 240 | @scheme + ':' + 241 | if @to 242 | @to 243 | else 244 | '' 245 | end + 246 | if @headers.size > 0 247 | '?' + @headers.collect{|x| x.join('=')}.join('&') 248 | else 249 | '' 250 | end + 251 | if @fragment 252 | '#' + @fragment 253 | else 254 | '' 255 | end 256 | end 257 | 258 | # Returns the RFC822 e-mail text equivalent of the URL, as a String. 259 | # 260 | # Example: 261 | # 262 | # require 'uri' 263 | # 264 | # uri = URI.parse("mailto:ruby-list@ruby-lang.org?Subject=subscribe&cc=myaddr") 265 | # uri.to_mailtext 266 | # # => "To: ruby-list@ruby-lang.org\nSubject: subscribe\nCc: myaddr\n\n\n" 267 | # 268 | def to_mailtext 269 | to = URI.decode_www_form_component(@to) 270 | head = '' 271 | body = '' 272 | @headers.each do |x| 273 | case x[0] 274 | when 'body' 275 | body = URI.decode_www_form_component(x[1]) 276 | when 'to' 277 | to << ', ' + URI.decode_www_form_component(x[1]) 278 | else 279 | head << URI.decode_www_form_component(x[0]).capitalize + ': ' + 280 | URI.decode_www_form_component(x[1]) + "\n" 281 | end 282 | end 283 | 284 | "To: #{to} 285 | #{head} 286 | #{body} 287 | " 288 | end 289 | alias to_rfc822text to_mailtext 290 | end 291 | 292 | register_scheme 'MAILTO', MailTo 293 | end 294 | -------------------------------------------------------------------------------- /lib/uri/rfc2396_parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | #-- 3 | # = uri/common.rb 4 | # 5 | # Author:: Akira Yamada 6 | # License:: 7 | # You can redistribute it and/or modify it under the same term as Ruby. 8 | # 9 | # See URI for general documentation 10 | # 11 | 12 | module URI 13 | # 14 | # Includes URI::REGEXP::PATTERN 15 | # 16 | module RFC2396_REGEXP 17 | # 18 | # Patterns used to parse URI's 19 | # 20 | module PATTERN 21 | # :stopdoc: 22 | 23 | # RFC 2396 (URI Generic Syntax) 24 | # RFC 2732 (IPv6 Literal Addresses in URL's) 25 | # RFC 2373 (IPv6 Addressing Architecture) 26 | 27 | # alpha = lowalpha | upalpha 28 | ALPHA = "a-zA-Z" 29 | # alphanum = alpha | digit 30 | ALNUM = "#{ALPHA}\\d" 31 | 32 | # hex = digit | "A" | "B" | "C" | "D" | "E" | "F" | 33 | # "a" | "b" | "c" | "d" | "e" | "f" 34 | HEX = "a-fA-F\\d" 35 | # escaped = "%" hex hex 36 | ESCAPED = "%[#{HEX}]{2}" 37 | # mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | 38 | # "(" | ")" 39 | # unreserved = alphanum | mark 40 | UNRESERVED = "\\-_.!~*'()#{ALNUM}" 41 | # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | 42 | # "$" | "," 43 | # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | 44 | # "$" | "," | "[" | "]" (RFC 2732) 45 | RESERVED = ";/?:@&=+$,\\[\\]" 46 | 47 | # domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum 48 | DOMLABEL = "(?:[#{ALNUM}](?:[-#{ALNUM}]*[#{ALNUM}])?)" 49 | # toplabel = alpha | alpha *( alphanum | "-" ) alphanum 50 | TOPLABEL = "(?:[#{ALPHA}](?:[-#{ALNUM}]*[#{ALNUM}])?)" 51 | # hostname = *( domainlabel "." ) toplabel [ "." ] 52 | HOSTNAME = "(?:#{DOMLABEL}\\.)*#{TOPLABEL}\\.?" 53 | 54 | # :startdoc: 55 | end # PATTERN 56 | 57 | # :startdoc: 58 | end # REGEXP 59 | 60 | # Class that parses String's into URI's. 61 | # 62 | # It contains a Hash set of patterns and Regexp's that match and validate. 63 | # 64 | class RFC2396_Parser 65 | include RFC2396_REGEXP 66 | 67 | # 68 | # == Synopsis 69 | # 70 | # URI::Parser.new([opts]) 71 | # 72 | # == Args 73 | # 74 | # The constructor accepts a hash as options for parser. 75 | # Keys of options are pattern names of URI components 76 | # and values of options are pattern strings. 77 | # The constructor generates set of regexps for parsing URIs. 78 | # 79 | # You can use the following keys: 80 | # 81 | # * :ESCAPED (URI::PATTERN::ESCAPED in default) 82 | # * :UNRESERVED (URI::PATTERN::UNRESERVED in default) 83 | # * :DOMLABEL (URI::PATTERN::DOMLABEL in default) 84 | # * :TOPLABEL (URI::PATTERN::TOPLABEL in default) 85 | # * :HOSTNAME (URI::PATTERN::HOSTNAME in default) 86 | # 87 | # == Examples 88 | # 89 | # p = URI::Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})") 90 | # u = p.parse("http://example.jp/%uABCD") #=> # 91 | # URI.parse(u.to_s) #=> raises URI::InvalidURIError 92 | # 93 | # s = "http://example.com/ABCD" 94 | # u1 = p.parse(s) #=> # 95 | # u2 = URI.parse(s) #=> # 96 | # u1 == u2 #=> true 97 | # u1.eql?(u2) #=> false 98 | # 99 | def initialize(opts = {}) 100 | @pattern = initialize_pattern(opts) 101 | @pattern.each_value(&:freeze) 102 | @pattern.freeze 103 | 104 | @regexp = initialize_regexp(@pattern) 105 | @regexp.each_value(&:freeze) 106 | @regexp.freeze 107 | end 108 | 109 | # The Hash of patterns. 110 | # 111 | # See also URI::Parser.initialize_pattern. 112 | attr_reader :pattern 113 | 114 | # The Hash of Regexp. 115 | # 116 | # See also URI::Parser.initialize_regexp. 117 | attr_reader :regexp 118 | 119 | # Returns a split URI against +regexp[:ABS_URI]+. 120 | def split(uri) 121 | case uri 122 | when '' 123 | # null uri 124 | 125 | when @regexp[:ABS_URI] 126 | scheme, opaque, userinfo, host, port, 127 | registry, path, query, fragment = $~[1..-1] 128 | 129 | # URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] 130 | 131 | # absoluteURI = scheme ":" ( hier_part | opaque_part ) 132 | # hier_part = ( net_path | abs_path ) [ "?" query ] 133 | # opaque_part = uric_no_slash *uric 134 | 135 | # abs_path = "/" path_segments 136 | # net_path = "//" authority [ abs_path ] 137 | 138 | # authority = server | reg_name 139 | # server = [ [ userinfo "@" ] hostport ] 140 | 141 | if !scheme 142 | raise InvalidURIError, 143 | "bad URI (absolute but no scheme): #{uri}" 144 | end 145 | if !opaque && (!path && (!host && !registry)) 146 | raise InvalidURIError, 147 | "bad URI (absolute but no path): #{uri}" 148 | end 149 | 150 | when @regexp[:REL_URI] 151 | scheme = nil 152 | opaque = nil 153 | 154 | userinfo, host, port, registry, 155 | rel_segment, abs_path, query, fragment = $~[1..-1] 156 | if rel_segment && abs_path 157 | path = rel_segment + abs_path 158 | elsif rel_segment 159 | path = rel_segment 160 | elsif abs_path 161 | path = abs_path 162 | end 163 | 164 | # URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] 165 | 166 | # relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ] 167 | 168 | # net_path = "//" authority [ abs_path ] 169 | # abs_path = "/" path_segments 170 | # rel_path = rel_segment [ abs_path ] 171 | 172 | # authority = server | reg_name 173 | # server = [ [ userinfo "@" ] hostport ] 174 | 175 | else 176 | raise InvalidURIError, "bad URI (is not URI?): #{uri}" 177 | end 178 | 179 | path = '' if !path && !opaque # (see RFC2396 Section 5.2) 180 | ret = [ 181 | scheme, 182 | userinfo, host, port, # X 183 | registry, # X 184 | path, # Y 185 | opaque, # Y 186 | query, 187 | fragment 188 | ] 189 | return ret 190 | end 191 | 192 | # 193 | # == Args 194 | # 195 | # +uri+:: 196 | # String 197 | # 198 | # == Description 199 | # 200 | # Parses +uri+ and constructs either matching URI scheme object 201 | # (File, FTP, HTTP, HTTPS, LDAP, LDAPS, or MailTo) or URI::Generic. 202 | # 203 | # == Usage 204 | # 205 | # p = URI::Parser.new 206 | # p.parse("ldap://ldap.example.com/dc=example?user=john") 207 | # #=> # 208 | # 209 | def parse(uri) 210 | URI.for(*self.split(uri), self) 211 | end 212 | 213 | # 214 | # == Args 215 | # 216 | # +uris+:: 217 | # an Array of Strings 218 | # 219 | # == Description 220 | # 221 | # Attempts to parse and merge a set of URIs. 222 | # 223 | def join(*uris) 224 | uris[0] = convert_to_uri(uris[0]) 225 | uris.inject :merge 226 | end 227 | 228 | # 229 | # :call-seq: 230 | # extract( str ) 231 | # extract( str, schemes ) 232 | # extract( str, schemes ) {|item| block } 233 | # 234 | # == Args 235 | # 236 | # +str+:: 237 | # String to search 238 | # +schemes+:: 239 | # Patterns to apply to +str+ 240 | # 241 | # == Description 242 | # 243 | # Attempts to parse and merge a set of URIs. 244 | # If no +block+ given, then returns the result, 245 | # else it calls +block+ for each element in result. 246 | # 247 | # See also URI::Parser.make_regexp. 248 | # 249 | def extract(str, schemes = nil) 250 | if block_given? 251 | str.scan(make_regexp(schemes)) { yield $& } 252 | nil 253 | else 254 | result = [] 255 | str.scan(make_regexp(schemes)) { result.push $& } 256 | result 257 | end 258 | end 259 | 260 | # Returns Regexp that is default +self.regexp[:ABS_URI_REF]+, 261 | # unless +schemes+ is provided. Then it is a Regexp.union with +self.pattern[:X_ABS_URI]+. 262 | def make_regexp(schemes = nil) 263 | unless schemes 264 | @regexp[:ABS_URI_REF] 265 | else 266 | /(?=#{Regexp.union(*schemes)}:)#{@pattern[:X_ABS_URI]}/x 267 | end 268 | end 269 | 270 | # 271 | # :call-seq: 272 | # escape( str ) 273 | # escape( str, unsafe ) 274 | # 275 | # == Args 276 | # 277 | # +str+:: 278 | # String to make safe 279 | # +unsafe+:: 280 | # Regexp to apply. Defaults to +self.regexp[:UNSAFE]+ 281 | # 282 | # == Description 283 | # 284 | # Constructs a safe String from +str+, removing unsafe characters, 285 | # replacing them with codes. 286 | # 287 | def escape(str, unsafe = @regexp[:UNSAFE]) 288 | unless unsafe.kind_of?(Regexp) 289 | # perhaps unsafe is String object 290 | unsafe = Regexp.new("[#{Regexp.quote(unsafe)}]", false) 291 | end 292 | str.gsub(unsafe) do 293 | us = $& 294 | tmp = '' 295 | us.each_byte do |uc| 296 | tmp << sprintf('%%%02X', uc) 297 | end 298 | tmp 299 | end.force_encoding(Encoding::US_ASCII) 300 | end 301 | 302 | # 303 | # :call-seq: 304 | # unescape( str ) 305 | # unescape( str, escaped ) 306 | # 307 | # == Args 308 | # 309 | # +str+:: 310 | # String to remove escapes from 311 | # +escaped+:: 312 | # Regexp to apply. Defaults to +self.regexp[:ESCAPED]+ 313 | # 314 | # == Description 315 | # 316 | # Removes escapes from +str+. 317 | # 318 | def unescape(str, escaped = @regexp[:ESCAPED]) 319 | enc = str.encoding 320 | enc = Encoding::UTF_8 if enc == Encoding::US_ASCII 321 | str.gsub(escaped) { [$&[1, 2]].pack('H2').force_encoding(enc) } 322 | end 323 | 324 | TO_S = Kernel.instance_method(:to_s) # :nodoc: 325 | if TO_S.respond_to?(:bind_call) 326 | def inspect # :nodoc: 327 | TO_S.bind_call(self) 328 | end 329 | else 330 | def inspect # :nodoc: 331 | TO_S.bind(self).call 332 | end 333 | end 334 | 335 | private 336 | 337 | # Constructs the default Hash of patterns. 338 | def initialize_pattern(opts = {}) 339 | ret = {} 340 | ret[:ESCAPED] = escaped = (opts.delete(:ESCAPED) || PATTERN::ESCAPED) 341 | ret[:UNRESERVED] = unreserved = opts.delete(:UNRESERVED) || PATTERN::UNRESERVED 342 | ret[:RESERVED] = reserved = opts.delete(:RESERVED) || PATTERN::RESERVED 343 | ret[:DOMLABEL] = opts.delete(:DOMLABEL) || PATTERN::DOMLABEL 344 | ret[:TOPLABEL] = opts.delete(:TOPLABEL) || PATTERN::TOPLABEL 345 | ret[:HOSTNAME] = hostname = opts.delete(:HOSTNAME) 346 | 347 | # RFC 2396 (URI Generic Syntax) 348 | # RFC 2732 (IPv6 Literal Addresses in URL's) 349 | # RFC 2373 (IPv6 Addressing Architecture) 350 | 351 | # uric = reserved | unreserved | escaped 352 | ret[:URIC] = uric = "(?:[#{unreserved}#{reserved}]|#{escaped})" 353 | # uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" | 354 | # "&" | "=" | "+" | "$" | "," 355 | ret[:URIC_NO_SLASH] = uric_no_slash = "(?:[#{unreserved};?:@&=+$,]|#{escaped})" 356 | # query = *uric 357 | ret[:QUERY] = query = "#{uric}*" 358 | # fragment = *uric 359 | ret[:FRAGMENT] = fragment = "#{uric}*" 360 | 361 | # hostname = *( domainlabel "." ) toplabel [ "." ] 362 | # reg-name = *( unreserved / pct-encoded / sub-delims ) # RFC3986 363 | unless hostname 364 | ret[:HOSTNAME] = hostname = "(?:[a-zA-Z0-9\\-.]|%\\h\\h)+" 365 | end 366 | 367 | # RFC 2373, APPENDIX B: 368 | # IPv6address = hexpart [ ":" IPv4address ] 369 | # IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT 370 | # hexpart = hexseq | hexseq "::" [ hexseq ] | "::" [ hexseq ] 371 | # hexseq = hex4 *( ":" hex4) 372 | # hex4 = 1*4HEXDIG 373 | # 374 | # XXX: This definition has a flaw. "::" + IPv4address must be 375 | # allowed too. Here is a replacement. 376 | # 377 | # IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT 378 | ret[:IPV4ADDR] = ipv4addr = "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}" 379 | # hex4 = 1*4HEXDIG 380 | hex4 = "[#{PATTERN::HEX}]{1,4}" 381 | # lastpart = hex4 | IPv4address 382 | lastpart = "(?:#{hex4}|#{ipv4addr})" 383 | # hexseq1 = *( hex4 ":" ) hex4 384 | hexseq1 = "(?:#{hex4}:)*#{hex4}" 385 | # hexseq2 = *( hex4 ":" ) lastpart 386 | hexseq2 = "(?:#{hex4}:)*#{lastpart}" 387 | # IPv6address = hexseq2 | [ hexseq1 ] "::" [ hexseq2 ] 388 | ret[:IPV6ADDR] = ipv6addr = "(?:#{hexseq2}|(?:#{hexseq1})?::(?:#{hexseq2})?)" 389 | 390 | # IPv6prefix = ( hexseq1 | [ hexseq1 ] "::" [ hexseq1 ] ) "/" 1*2DIGIT 391 | # unused 392 | 393 | # ipv6reference = "[" IPv6address "]" (RFC 2732) 394 | ret[:IPV6REF] = ipv6ref = "\\[#{ipv6addr}\\]" 395 | 396 | # host = hostname | IPv4address 397 | # host = hostname | IPv4address | IPv6reference (RFC 2732) 398 | ret[:HOST] = host = "(?:#{hostname}|#{ipv4addr}|#{ipv6ref})" 399 | # port = *digit 400 | ret[:PORT] = port = '\d*' 401 | # hostport = host [ ":" port ] 402 | ret[:HOSTPORT] = hostport = "#{host}(?::#{port})?" 403 | 404 | # userinfo = *( unreserved | escaped | 405 | # ";" | ":" | "&" | "=" | "+" | "$" | "," ) 406 | ret[:USERINFO] = userinfo = "(?:[#{unreserved};:&=+$,]|#{escaped})*" 407 | 408 | # pchar = unreserved | escaped | 409 | # ":" | "@" | "&" | "=" | "+" | "$" | "," 410 | pchar = "(?:[#{unreserved}:@&=+$,]|#{escaped})" 411 | # param = *pchar 412 | param = "#{pchar}*" 413 | # segment = *pchar *( ";" param ) 414 | segment = "#{pchar}*(?:;#{param})*" 415 | # path_segments = segment *( "/" segment ) 416 | ret[:PATH_SEGMENTS] = path_segments = "#{segment}(?:/#{segment})*" 417 | 418 | # server = [ [ userinfo "@" ] hostport ] 419 | server = "(?:#{userinfo}@)?#{hostport}" 420 | # reg_name = 1*( unreserved | escaped | "$" | "," | 421 | # ";" | ":" | "@" | "&" | "=" | "+" ) 422 | ret[:REG_NAME] = reg_name = "(?:[#{unreserved}$,;:@&=+]|#{escaped})+" 423 | # authority = server | reg_name 424 | authority = "(?:#{server}|#{reg_name})" 425 | 426 | # rel_segment = 1*( unreserved | escaped | 427 | # ";" | "@" | "&" | "=" | "+" | "$" | "," ) 428 | ret[:REL_SEGMENT] = rel_segment = "(?:[#{unreserved};@&=+$,]|#{escaped})+" 429 | 430 | # scheme = alpha *( alpha | digit | "+" | "-" | "." ) 431 | ret[:SCHEME] = scheme = "[#{PATTERN::ALPHA}][\\-+.#{PATTERN::ALPHA}\\d]*" 432 | 433 | # abs_path = "/" path_segments 434 | ret[:ABS_PATH] = abs_path = "/#{path_segments}" 435 | # rel_path = rel_segment [ abs_path ] 436 | ret[:REL_PATH] = rel_path = "#{rel_segment}(?:#{abs_path})?" 437 | # net_path = "//" authority [ abs_path ] 438 | ret[:NET_PATH] = net_path = "//#{authority}(?:#{abs_path})?" 439 | 440 | # hier_part = ( net_path | abs_path ) [ "?" query ] 441 | ret[:HIER_PART] = hier_part = "(?:#{net_path}|#{abs_path})(?:\\?(?:#{query}))?" 442 | # opaque_part = uric_no_slash *uric 443 | ret[:OPAQUE_PART] = opaque_part = "#{uric_no_slash}#{uric}*" 444 | 445 | # absoluteURI = scheme ":" ( hier_part | opaque_part ) 446 | ret[:ABS_URI] = abs_uri = "#{scheme}:(?:#{hier_part}|#{opaque_part})" 447 | # relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ] 448 | ret[:REL_URI] = rel_uri = "(?:#{net_path}|#{abs_path}|#{rel_path})(?:\\?#{query})?" 449 | 450 | # URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] 451 | ret[:URI_REF] = "(?:#{abs_uri}|#{rel_uri})?(?:##{fragment})?" 452 | 453 | ret[:X_ABS_URI] = " 454 | (#{scheme}): (?# 1: scheme) 455 | (?: 456 | (#{opaque_part}) (?# 2: opaque) 457 | | 458 | (?:(?: 459 | //(?: 460 | (?:(?:(#{userinfo})@)? (?# 3: userinfo) 461 | (?:(#{host})(?::(\\d*))?))? (?# 4: host, 5: port) 462 | | 463 | (#{reg_name}) (?# 6: registry) 464 | ) 465 | | 466 | (?!//)) (?# XXX: '//' is the mark for hostport) 467 | (#{abs_path})? (?# 7: path) 468 | )(?:\\?(#{query}))? (?# 8: query) 469 | ) 470 | (?:\\#(#{fragment}))? (?# 9: fragment) 471 | " 472 | 473 | ret[:X_REL_URI] = " 474 | (?: 475 | (?: 476 | // 477 | (?: 478 | (?:(#{userinfo})@)? (?# 1: userinfo) 479 | (#{host})?(?::(\\d*))? (?# 2: host, 3: port) 480 | | 481 | (#{reg_name}) (?# 4: registry) 482 | ) 483 | ) 484 | | 485 | (#{rel_segment}) (?# 5: rel_segment) 486 | )? 487 | (#{abs_path})? (?# 6: abs_path) 488 | (?:\\?(#{query}))? (?# 7: query) 489 | (?:\\#(#{fragment}))? (?# 8: fragment) 490 | " 491 | 492 | ret 493 | end 494 | 495 | # Constructs the default Hash of Regexp's. 496 | def initialize_regexp(pattern) 497 | ret = {} 498 | 499 | # for URI::split 500 | ret[:ABS_URI] = Regexp.new('\A\s*+' + pattern[:X_ABS_URI] + '\s*\z', Regexp::EXTENDED) 501 | ret[:REL_URI] = Regexp.new('\A\s*+' + pattern[:X_REL_URI] + '\s*\z', Regexp::EXTENDED) 502 | 503 | # for URI::extract 504 | ret[:URI_REF] = Regexp.new(pattern[:URI_REF]) 505 | ret[:ABS_URI_REF] = Regexp.new(pattern[:X_ABS_URI], Regexp::EXTENDED) 506 | ret[:REL_URI_REF] = Regexp.new(pattern[:X_REL_URI], Regexp::EXTENDED) 507 | 508 | # for URI::escape/unescape 509 | ret[:ESCAPED] = Regexp.new(pattern[:ESCAPED]) 510 | ret[:UNSAFE] = Regexp.new("[^#{pattern[:UNRESERVED]}#{pattern[:RESERVED]}]") 511 | 512 | # for Generic#initialize 513 | ret[:SCHEME] = Regexp.new("\\A#{pattern[:SCHEME]}\\z") 514 | ret[:USERINFO] = Regexp.new("\\A#{pattern[:USERINFO]}\\z") 515 | ret[:HOST] = Regexp.new("\\A#{pattern[:HOST]}\\z") 516 | ret[:PORT] = Regexp.new("\\A#{pattern[:PORT]}\\z") 517 | ret[:OPAQUE] = Regexp.new("\\A#{pattern[:OPAQUE_PART]}\\z") 518 | ret[:REGISTRY] = Regexp.new("\\A#{pattern[:REG_NAME]}\\z") 519 | ret[:ABS_PATH] = Regexp.new("\\A#{pattern[:ABS_PATH]}\\z") 520 | ret[:REL_PATH] = Regexp.new("\\A#{pattern[:REL_PATH]}\\z") 521 | ret[:QUERY] = Regexp.new("\\A#{pattern[:QUERY]}\\z") 522 | ret[:FRAGMENT] = Regexp.new("\\A#{pattern[:FRAGMENT]}\\z") 523 | 524 | ret 525 | end 526 | 527 | def convert_to_uri(uri) 528 | if uri.is_a?(URI::Generic) 529 | uri 530 | elsif uri = String.try_convert(uri) 531 | parse(uri) 532 | else 533 | raise ArgumentError, 534 | "bad argument (expected URI object or URI string)" 535 | end 536 | end 537 | 538 | end # class Parser 539 | 540 | # Backward compatibility for URI::REGEXP::PATTERN::* 541 | RFC2396_Parser.new.pattern.each_pair do |sym, str| 542 | unless RFC2396_REGEXP::PATTERN.const_defined?(sym, false) 543 | RFC2396_REGEXP::PATTERN.const_set(sym, str) 544 | end 545 | end 546 | end # module URI 547 | -------------------------------------------------------------------------------- /lib/uri/rfc3986_parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module URI 3 | class RFC3986_Parser # :nodoc: 4 | # URI defined in RFC3986 5 | HOST = %r[ 6 | (?\[(?: 7 | (? 8 | (?:\h{1,4}:){6} 9 | (?\h{1,4}:\h{1,4} 10 | | (?(?[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d) 11 | \.\g\.\g\.\g) 12 | ) 13 | | ::(?:\h{1,4}:){5}\g 14 | | \h{1,4}?::(?:\h{1,4}:){4}\g 15 | | (?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g 16 | | (?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g 17 | | (?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g 18 | | (?:(?:\h{1,4}:){,4}\h{1,4})?::\g 19 | | (?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4} 20 | | (?:(?:\h{1,4}:){,6}\h{1,4})?:: 21 | ) 22 | | (?v\h++\.[!$&-.0-9:;=A-Z_a-z~]++) 23 | )\]) 24 | | \g 25 | | (?(?:%\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}){0} 37 | (? 38 | (?#{SCHEME}): 39 | (?// 40 | (? 41 | (?:(?#{USERINFO.source})@)? 42 | (?#{HOST.source.delete(" \n")}) 43 | (?::(?\d*+))? 44 | ) 45 | (?(?:/\g*+)?) 46 | | (?/((?!/)\g++)?) 47 | | (?(?!/)\g++) 48 | | (?) 49 | ) 50 | (?:\?(?[^\#]*+))? 51 | (?:\#(?#{FRAGMENT}))? 52 | )\z]x 53 | 54 | RFC3986_relative_ref = %r[\A 55 | (?#{SEG}){0} 56 | (? 57 | (?// 58 | (? 59 | (?:(?#{USERINFO.source})@)? 60 | (?#{HOST.source.delete(" \n")}(?\d*+))? 62 | ) 63 | (?(?:/\g*+)?) 64 | | (?/\g*+) 65 | | (?#{SEG_NC}++(?:/\g*+)?) 66 | | (?) 67 | ) 68 | (?:\?(?[^#]*+))? 69 | (?:\#(?#{FRAGMENT}))? 70 | )\z]x 71 | attr_reader :regexp 72 | 73 | def initialize 74 | @regexp = default_regexp.each_value(&:freeze).freeze 75 | end 76 | 77 | def split(uri) #:nodoc: 78 | begin 79 | uri = uri.to_str 80 | rescue NoMethodError 81 | raise InvalidURIError, "bad URI (is not URI?): #{uri.inspect}" 82 | end 83 | uri.ascii_only? or 84 | raise InvalidURIError, "URI must be ascii only #{uri.dump}" 85 | if m = RFC3986_URI.match(uri) 86 | query = m["query"] 87 | scheme = m["scheme"] 88 | opaque = m["path-rootless"] 89 | if opaque 90 | opaque << "?#{query}" if query 91 | [ scheme, 92 | nil, # userinfo 93 | nil, # host 94 | nil, # port 95 | nil, # registry 96 | nil, # path 97 | opaque, 98 | nil, # query 99 | m["fragment"] 100 | ] 101 | else # normal 102 | [ scheme, 103 | m["userinfo"], 104 | m["host"], 105 | m["port"], 106 | nil, # registry 107 | (m["path-abempty"] || 108 | m["path-absolute"] || 109 | m["path-empty"]), 110 | nil, # opaque 111 | query, 112 | m["fragment"] 113 | ] 114 | end 115 | elsif m = RFC3986_relative_ref.match(uri) 116 | [ nil, # scheme 117 | m["userinfo"], 118 | m["host"], 119 | m["port"], 120 | nil, # registry, 121 | (m["path-abempty"] || 122 | m["path-absolute"] || 123 | m["path-noscheme"] || 124 | m["path-empty"]), 125 | nil, # opaque 126 | m["query"], 127 | m["fragment"] 128 | ] 129 | else 130 | raise InvalidURIError, "bad URI (is not URI?): #{uri.inspect}" 131 | end 132 | end 133 | 134 | def parse(uri) # :nodoc: 135 | URI.for(*self.split(uri), self) 136 | end 137 | 138 | def join(*uris) # :nodoc: 139 | uris[0] = convert_to_uri(uris[0]) 140 | uris.inject :merge 141 | end 142 | 143 | # Compatibility for RFC2396 parser 144 | def extract(str, schemes = nil, &block) # :nodoc: 145 | warn "URI::RFC3986_PARSER.extract is obsolete. Use URI::RFC2396_PARSER.extract explicitly.", uplevel: 1 if $VERBOSE 146 | RFC2396_PARSER.extract(str, schemes, &block) 147 | end 148 | 149 | # Compatibility for RFC2396 parser 150 | def make_regexp(schemes = nil) # :nodoc: 151 | warn "URI::RFC3986_PARSER.make_regexp is obsolete. Use URI::RFC2396_PARSER.make_regexp explicitly.", uplevel: 1 if $VERBOSE 152 | RFC2396_PARSER.make_regexp(schemes) 153 | end 154 | 155 | # Compatibility for RFC2396 parser 156 | def escape(str, unsafe = nil) # :nodoc: 157 | warn "URI::RFC3986_PARSER.escape is obsolete. Use URI::RFC2396_PARSER.escape explicitly.", uplevel: 1 if $VERBOSE 158 | unsafe ? RFC2396_PARSER.escape(str, unsafe) : RFC2396_PARSER.escape(str) 159 | end 160 | 161 | # Compatibility for RFC2396 parser 162 | def unescape(str, escaped = nil) # :nodoc: 163 | warn "URI::RFC3986_PARSER.unescape is obsolete. Use URI::RFC2396_PARSER.unescape explicitly.", uplevel: 1 if $VERBOSE 164 | escaped ? RFC2396_PARSER.unescape(str, escaped) : RFC2396_PARSER.unescape(str) 165 | end 166 | 167 | @@to_s = Kernel.instance_method(:to_s) 168 | if @@to_s.respond_to?(:bind_call) 169 | def inspect 170 | @@to_s.bind_call(self) 171 | end 172 | else 173 | def inspect 174 | @@to_s.bind(self).call 175 | end 176 | end 177 | 178 | private 179 | 180 | def default_regexp # :nodoc: 181 | { 182 | SCHEME: %r[\A#{SCHEME}\z]o, 183 | USERINFO: %r[\A#{USERINFO}\z]o, 184 | HOST: %r[\A#{HOST}\z]o, 185 | ABS_PATH: %r[\A/#{SEG}*+\z]o, 186 | REL_PATH: %r[\A(?!/)#{SEG}++\z]o, 187 | QUERY: %r[\A(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/?])*+\z], 188 | FRAGMENT: %r[\A#{FRAGMENT}\z]o, 189 | OPAQUE: %r[\A(?:[^/].*)?\z], 190 | PORT: /\A[\x09\x0a\x0c\x0d ]*+\d*[\x09\x0a\x0c\x0d ]*\z/, 191 | } 192 | end 193 | 194 | def convert_to_uri(uri) 195 | if uri.is_a?(URI::Generic) 196 | uri 197 | elsif uri = String.try_convert(uri) 198 | parse(uri) 199 | else 200 | raise ArgumentError, 201 | "bad argument (expected URI object or URI string)" 202 | end 203 | end 204 | 205 | end # class Parser 206 | end # module URI 207 | -------------------------------------------------------------------------------- /lib/uri/version.rb: -------------------------------------------------------------------------------- 1 | module URI 2 | # :stopdoc: 3 | VERSION_CODE = '010003'.freeze 4 | VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze 5 | # :startdoc: 6 | end 7 | -------------------------------------------------------------------------------- /lib/uri/ws.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # = uri/ws.rb 3 | # 4 | # Author:: Matt Muller 5 | # License:: You can redistribute it and/or modify it under the same term as Ruby. 6 | # 7 | # See URI for general documentation 8 | # 9 | 10 | require_relative 'generic' 11 | 12 | module URI 13 | 14 | # 15 | # The syntax of WS URIs is defined in RFC6455 section 3. 16 | # 17 | # Note that the Ruby URI library allows WS URLs containing usernames and 18 | # passwords. This is not legal as per the RFC, but used to be 19 | # supported in Internet Explorer 5 and 6, before the MS04-004 security 20 | # update. See . 21 | # 22 | class WS < Generic 23 | # A Default port of 80 for URI::WS. 24 | DEFAULT_PORT = 80 25 | 26 | # An Array of the available components for URI::WS. 27 | COMPONENT = %i[ 28 | scheme 29 | userinfo host port 30 | path 31 | query 32 | ].freeze 33 | 34 | # 35 | # == Description 36 | # 37 | # Creates a new URI::WS object from components, with syntax checking. 38 | # 39 | # The components accepted are userinfo, host, port, path, and query. 40 | # 41 | # The components should be provided either as an Array, or as a Hash 42 | # with keys formed by preceding the component names with a colon. 43 | # 44 | # If an Array is used, the components must be passed in the 45 | # order [userinfo, host, port, path, query]. 46 | # 47 | # Example: 48 | # 49 | # uri = URI::WS.build(host: 'www.example.com', path: '/foo/bar') 50 | # 51 | # uri = URI::WS.build([nil, "www.example.com", nil, "/path", "query"]) 52 | # 53 | # Currently, if passed userinfo components this method generates 54 | # invalid WS URIs as per RFC 1738. 55 | # 56 | def self.build(args) 57 | tmp = Util.make_components_hash(self, args) 58 | super(tmp) 59 | end 60 | 61 | # 62 | # == Description 63 | # 64 | # Returns the full path for a WS URI, as required by Net::HTTP::Get. 65 | # 66 | # If the URI contains a query, the full path is URI#path + '?' + URI#query. 67 | # Otherwise, the path is simply URI#path. 68 | # 69 | # Example: 70 | # 71 | # uri = URI::WS.build(path: '/foo/bar', query: 'test=true') 72 | # uri.request_uri # => "/foo/bar?test=true" 73 | # 74 | def request_uri 75 | return unless @path 76 | 77 | url = @query ? "#@path?#@query" : @path.dup 78 | url.start_with?(?/.freeze) ? url : ?/ + url 79 | end 80 | end 81 | 82 | register_scheme 'WS', WS 83 | end 84 | -------------------------------------------------------------------------------- /lib/uri/wss.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # = uri/wss.rb 3 | # 4 | # Author:: Matt Muller 5 | # License:: You can redistribute it and/or modify it under the same term as Ruby. 6 | # 7 | # See URI for general documentation 8 | # 9 | 10 | require_relative 'ws' 11 | 12 | module URI 13 | 14 | # The default port for WSS URIs is 443, and the scheme is 'wss:' rather 15 | # than 'ws:'. Other than that, WSS URIs are identical to WS URIs; 16 | # see URI::WS. 17 | class WSS < WS 18 | # A Default port of 443 for URI::WSS 19 | DEFAULT_PORT = 443 20 | end 21 | 22 | register_scheme 'WSS', WSS 23 | end 24 | -------------------------------------------------------------------------------- /rakelib/sync_tool.rake: -------------------------------------------------------------------------------- 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 18 | -------------------------------------------------------------------------------- /test/lib/helper.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require "core_assertions" 3 | 4 | Test::Unit::TestCase.include Test::Unit::CoreAssertions 5 | -------------------------------------------------------------------------------- /test/uri/test_common.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'test/unit' 3 | require 'envutil' 4 | require 'uri' 5 | 6 | class URI::TestCommon < Test::Unit::TestCase 7 | def setup 8 | end 9 | 10 | def teardown 11 | end 12 | 13 | EnvUtil.suppress_warning do 14 | class Foo 15 | # Intentionally use `URI::REGEXP`, which is for the compatibility 16 | include URI::REGEXP::PATTERN 17 | end 18 | end 19 | 20 | def test_fallback_constants 21 | EnvUtil.suppress_warning do 22 | assert_raise(NameError) { URI::FOO } 23 | 24 | assert_equal URI::ABS_URI, URI::RFC2396_PARSER.regexp[:ABS_URI] 25 | assert_equal URI::PATTERN, URI::RFC2396_Parser::PATTERN 26 | assert_equal URI::REGEXP, URI::RFC2396_REGEXP 27 | assert_equal URI::REGEXP::PATTERN, URI::RFC2396_REGEXP::PATTERN 28 | assert_equal Foo::IPV4ADDR, URI::RFC2396_REGEXP::PATTERN::IPV4ADDR 29 | end 30 | end 31 | 32 | def test_parser_switch 33 | assert_equal(URI::Parser, URI::RFC3986_Parser) 34 | refute defined?(URI::REGEXP) 35 | refute defined?(URI::PATTERN) 36 | 37 | URI.parser = URI::RFC2396_PARSER 38 | 39 | assert_equal(URI::Parser, URI::RFC2396_Parser) 40 | assert defined?(URI::REGEXP) 41 | assert defined?(URI::PATTERN) 42 | assert defined?(URI::PATTERN::ESCAPED) 43 | assert defined?(URI::REGEXP::PATTERN::IPV6ADDR) 44 | 45 | URI.parser = URI::RFC3986_PARSER 46 | 47 | assert_equal(URI::Parser, URI::RFC3986_Parser) 48 | refute defined?(URI::REGEXP) 49 | refute defined?(URI::PATTERN) 50 | ensure 51 | URI.parser = URI::RFC3986_PARSER 52 | end 53 | 54 | def test_extract 55 | EnvUtil.suppress_warning do 56 | assert_equal(['http://example.com'], 57 | URI.extract('http://example.com')) 58 | assert_equal(['http://example.com'], 59 | URI.extract('(http://example.com)')) 60 | assert_equal(['http://example.com/foo)'], 61 | URI.extract('(http://example.com/foo)')) 62 | assert_equal(['http://example.jphttp://example.jp'], 63 | URI.extract('http://example.jphttp://example.jp'), "[ruby-list:36086]") 64 | assert_equal(['http://example.jphttp://example.jp'], 65 | URI.extract('http://example.jphttp://example.jp', ['http']), "[ruby-list:36086]") 66 | assert_equal(['http://', 'mailto:'].sort, 67 | URI.extract('ftp:// http:// mailto: https://', ['http', 'mailto']).sort) 68 | # reported by Doug Kearns 69 | assert_equal(['From:', 'mailto:xxx@xxx.xxx.xxx]'].sort, 70 | URI.extract('From: XXX [mailto:xxx@xxx.xxx.xxx]').sort) 71 | end 72 | end 73 | 74 | def test_ractor 75 | return unless defined?(Ractor) 76 | assert_ractor(<<~RUBY, require: 'uri') 77 | r = Ractor.new { URI.parse("https://ruby-lang.org/").inspect } 78 | assert_equal(URI.parse("https://ruby-lang.org/").inspect, r.value) 79 | RUBY 80 | end 81 | 82 | DEFAULT_SCHEMES = ["FILE", "FTP", "HTTP", "HTTPS", "LDAP", "LDAPS", "MAILTO", "WS", "WSS"].sort.freeze 83 | 84 | def test_register_scheme 85 | assert_equal(DEFAULT_SCHEMES, URI.scheme_list.keys.sort) 86 | 87 | foobar = Class.new(URI::Generic) 88 | URI.register_scheme 'FOOBAR', foobar 89 | begin 90 | assert_include(URI.scheme_list.keys, "FOOBAR") 91 | assert_equal foobar, URI.parse('foobar://localhost').class 92 | ensure 93 | URI.const_get(:Schemes).send(:remove_const, :FOOBAR) 94 | end 95 | 96 | assert_equal(DEFAULT_SCHEMES, URI.scheme_list.keys.sort) 97 | end 98 | 99 | def test_register_scheme_lowercase 100 | assert_equal(DEFAULT_SCHEMES, URI.scheme_list.keys.sort) 101 | 102 | foobar = Class.new(URI::Generic) 103 | URI.register_scheme 'foobarlower', foobar 104 | begin 105 | assert_include(URI.scheme_list.keys, "FOOBARLOWER") 106 | assert_equal foobar, URI.parse('foobarlower://localhost').class 107 | ensure 108 | URI.const_get(:Schemes).send(:remove_const, :FOOBARLOWER) 109 | end 110 | 111 | assert_equal(DEFAULT_SCHEMES, URI.scheme_list.keys.sort) 112 | end 113 | 114 | def test_register_scheme_with_symbols 115 | # Valid schemes from https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml 116 | some_uri_class = Class.new(URI::Generic) 117 | assert_raise(NameError) { URI.register_scheme 'ms-search', some_uri_class } 118 | assert_raise(NameError) { URI.register_scheme 'microsoft.windows.camera', some_uri_class } 119 | assert_raise(NameError) { URI.register_scheme 'coaps+ws', some_uri_class } 120 | 121 | ms_search_class = Class.new(URI::Generic) 122 | URI.register_scheme 'MS_SEARCH', ms_search_class 123 | begin 124 | assert_equal URI::Generic, URI.parse('ms-search://localhost').class 125 | ensure 126 | URI.const_get(:Schemes).send(:remove_const, :MS_SEARCH) 127 | end 128 | end 129 | 130 | def test_regexp 131 | EnvUtil.suppress_warning do 132 | assert_instance_of Regexp, URI.regexp 133 | assert_instance_of Regexp, URI.regexp(['http']) 134 | assert_equal URI.regexp, URI.regexp 135 | assert_equal 'http://', 'x http:// x'.slice(URI.regexp) 136 | assert_equal 'http://', 'x http:// x'.slice(URI.regexp(['http'])) 137 | assert_equal 'http://', 'x http:// x ftp://'.slice(URI.regexp(['http'])) 138 | assert_equal nil, 'http://'.slice(URI.regexp([])) 139 | assert_equal nil, ''.slice(URI.regexp) 140 | assert_equal nil, 'xxxx'.slice(URI.regexp) 141 | assert_equal nil, ':'.slice(URI.regexp) 142 | assert_equal 'From:', 'From:'.slice(URI.regexp) 143 | end 144 | end 145 | 146 | def test_kernel_uri 147 | expected = URI.parse("http://www.ruby-lang.org/") 148 | assert_equal(expected, URI("http://www.ruby-lang.org/")) 149 | assert_equal(expected, Kernel::URI("http://www.ruby-lang.org/")) 150 | assert_raise(NoMethodError) { Object.new.URI("http://www.ruby-lang.org/") } 151 | end 152 | 153 | def test_parse_timeout 154 | pre = ->(n) { 155 | 'https://example.com/dir/' + 'a' * (n * 100) + '/##.jpg' 156 | } 157 | assert_linear_performance((1..3).map {|i| 10**i}, rehearsal: 1000, pre: pre) do |uri| 158 | assert_raise(URI::InvalidURIError) do 159 | URI.parse(uri) 160 | end 161 | end 162 | end 163 | 164 | def test_encode_www_form_component 165 | assert_equal("%00+%21%22%23%24%25%26%27%28%29*%2B%2C-.%2F09%3A%3B%3C%3D%3E%3F%40" \ 166 | "AZ%5B%5C%5D%5E_%60az%7B%7C%7D%7E", 167 | URI.encode_www_form_component("\x00 !\"\#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~")) 168 | assert_equal("%95A", URI.encode_www_form_component( 169 | "\x95\x41".force_encoding(Encoding::Shift_JIS))) 170 | assert_equal("0B", URI.encode_www_form_component( 171 | "\x30\x42".force_encoding(Encoding::UTF_16BE))) 172 | assert_equal("%1B%24B%24%22%1B%28B", URI.encode_www_form_component( 173 | "\e$B$\"\e(B".force_encoding(Encoding::ISO_2022_JP))) 174 | 175 | assert_equal("%E3%81%82", URI.encode_www_form_component( 176 | "\u3042", Encoding::ASCII_8BIT)) 177 | assert_equal("%82%A0", URI.encode_www_form_component( 178 | "\u3042", Encoding::Windows_31J)) 179 | assert_equal("%E3%81%82", URI.encode_www_form_component( 180 | "\u3042", Encoding::UTF_8)) 181 | 182 | assert_equal("%82%A0", URI.encode_www_form_component( 183 | "\u3042".encode("sjis"), Encoding::ASCII_8BIT)) 184 | assert_equal("%A4%A2", URI.encode_www_form_component( 185 | "\u3042".encode("sjis"), Encoding::EUC_JP)) 186 | assert_equal("%E3%81%82", URI.encode_www_form_component( 187 | "\u3042".encode("sjis"), Encoding::UTF_8)) 188 | assert_equal("B0", URI.encode_www_form_component( 189 | "\u3042".encode("sjis"), Encoding::UTF_16LE)) 190 | assert_equal("%26%23730%3B", URI.encode_www_form_component( 191 | "\u02DA", Encoding::WINDOWS_1252)) 192 | 193 | # invalid 194 | assert_equal("%EF%BF%BD%EF%BF%BD", URI.encode_www_form_component( 195 | "\xE3\x81\xFF", "utf-8")) 196 | assert_equal("%E6%9F%8A%EF%BF%BD%EF%BF%BD", URI.encode_www_form_component( 197 | "\x95\x41\xff\xff".force_encoding(Encoding::Shift_JIS), "utf-8")) 198 | end 199 | 200 | def test_decode_www_form_component 201 | assert_equal(" !\"\#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~", 202 | URI.decode_www_form_component( 203 | "%20+%21%22%23%24%25%26%27%28%29*%2B%2C-.%2F09%3A%3B%3C%3D%3E%3F%40" \ 204 | "AZ%5B%5C%5D%5E_%60az%7B%7C%7D%7E")) 205 | assert_equal("\xA1\xA2".force_encoding(Encoding::EUC_JP), 206 | URI.decode_www_form_component("%A1%A2", "EUC-JP")) 207 | assert_equal("\xE3\x81\x82\xE3\x81\x82".force_encoding("UTF-8"), 208 | URI.decode_www_form_component("\xE3\x81\x82%E3%81%82".force_encoding("UTF-8"))) 209 | 210 | assert_raise(ArgumentError){URI.decode_www_form_component("%")} 211 | assert_raise(ArgumentError){URI.decode_www_form_component("%a")} 212 | assert_raise(ArgumentError){URI.decode_www_form_component("x%a_")} 213 | assert_nothing_raised(ArgumentError){URI.decode_www_form_component("x"*(1024*1024))} 214 | end 215 | 216 | def test_encode_uri_component 217 | assert_equal("%00%20%21%22%23%24%25%26%27%28%29*%2B%2C-.%2F09%3A%3B%3C%3D%3E%3F%40" \ 218 | "AZ%5B%5C%5D%5E_%60az%7B%7C%7D%7E", 219 | URI.encode_uri_component("\x00 !\"\#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~")) 220 | assert_equal("%95A", URI.encode_uri_component( 221 | "\x95\x41".force_encoding(Encoding::Shift_JIS))) 222 | assert_equal("0B", URI.encode_uri_component( 223 | "\x30\x42".force_encoding(Encoding::UTF_16BE))) 224 | assert_equal("%1B%24B%24%22%1B%28B", URI.encode_uri_component( 225 | "\e$B$\"\e(B".force_encoding(Encoding::ISO_2022_JP))) 226 | 227 | assert_equal("%E3%81%82", URI.encode_uri_component( 228 | "\u3042", Encoding::ASCII_8BIT)) 229 | assert_equal("%82%A0", URI.encode_uri_component( 230 | "\u3042", Encoding::Windows_31J)) 231 | assert_equal("%E3%81%82", URI.encode_uri_component( 232 | "\u3042", Encoding::UTF_8)) 233 | 234 | assert_equal("%82%A0", URI.encode_uri_component( 235 | "\u3042".encode("sjis"), Encoding::ASCII_8BIT)) 236 | assert_equal("%A4%A2", URI.encode_uri_component( 237 | "\u3042".encode("sjis"), Encoding::EUC_JP)) 238 | assert_equal("%E3%81%82", URI.encode_uri_component( 239 | "\u3042".encode("sjis"), Encoding::UTF_8)) 240 | assert_equal("B0", URI.encode_uri_component( 241 | "\u3042".encode("sjis"), Encoding::UTF_16LE)) 242 | assert_equal("%26%23730%3B", URI.encode_uri_component( 243 | "\u02DA", Encoding::WINDOWS_1252)) 244 | 245 | # invalid 246 | assert_equal("%EF%BF%BD%EF%BF%BD", URI.encode_uri_component( 247 | "\xE3\x81\xFF", "utf-8")) 248 | assert_equal("%E6%9F%8A%EF%BF%BD%EF%BF%BD", URI.encode_uri_component( 249 | "\x95\x41\xff\xff".force_encoding(Encoding::Shift_JIS), "utf-8")) 250 | end 251 | 252 | def test_decode_uri_component 253 | assert_equal(" +!\"\#$%&'()*+,-./09:;<=>?@AZ[\\]^_`az{|}~", 254 | URI.decode_uri_component( 255 | "%20+%21%22%23%24%25%26%27%28%29*%2B%2C-.%2F09%3A%3B%3C%3D%3E%3F%40" \ 256 | "AZ%5B%5C%5D%5E_%60az%7B%7C%7D%7E")) 257 | assert_equal("\xA1\xA2".force_encoding(Encoding::EUC_JP), 258 | URI.decode_uri_component("%A1%A2", "EUC-JP")) 259 | assert_equal("\xE3\x81\x82\xE3\x81\x82".force_encoding("UTF-8"), 260 | URI.decode_uri_component("\xE3\x81\x82%E3%81%82".force_encoding("UTF-8"))) 261 | 262 | assert_raise(ArgumentError){URI.decode_uri_component("%")} 263 | assert_raise(ArgumentError){URI.decode_uri_component("%a")} 264 | assert_raise(ArgumentError){URI.decode_uri_component("x%a_")} 265 | assert_nothing_raised(ArgumentError){URI.decode_uri_component("x"*(1024*1024))} 266 | end 267 | 268 | def test_encode_www_form 269 | assert_equal("a=1", URI.encode_www_form("a" => "1")) 270 | assert_equal("a=1", URI.encode_www_form(a: 1)) 271 | assert_equal("a=1", URI.encode_www_form([["a", "1"]])) 272 | assert_equal("a=1", URI.encode_www_form([[:a, 1]])) 273 | expected = "a=1&%E3%81%82=%E6%BC%A2" 274 | assert_equal(expected, URI.encode_www_form("a" => "1", "\u3042" => "\u6F22")) 275 | assert_equal(expected, URI.encode_www_form(a: 1, :"\u3042" => "\u6F22")) 276 | assert_equal(expected, URI.encode_www_form([["a", "1"], ["\u3042", "\u6F22"]])) 277 | assert_equal(expected, URI.encode_www_form([[:a, 1], [:"\u3042", "\u6F22"]])) 278 | assert_equal("a=1&%82%A0=%8A%BF", 279 | URI.encode_www_form({"a" => "1", "\u3042" => "\u6F22"}, "sjis")) 280 | 281 | assert_equal('+a+=+1+', URI.encode_www_form([[' a ', ' 1 ']])) 282 | assert_equal('text=x%0Ay', URI.encode_www_form([['text', "x\u000Ay"]])) 283 | assert_equal('constellation=Bo%C3%B6tes', URI.encode_www_form([['constellation', "Bo\u00F6tes"]])) 284 | assert_equal('name=%00value', URI.encode_www_form([['name', "\u0000value"]])) 285 | assert_equal('Cipher=c%3D%28m%5Ee%29%25n', URI.encode_www_form([['Cipher', 'c=(m^e)%n']])) 286 | assert_equal('&', URI.encode_www_form([['', nil], ['', nil]])) 287 | assert_equal('&=', URI.encode_www_form([['', nil], ['', '']])) 288 | assert_equal('=&', URI.encode_www_form([['', ''], ['', nil]])) 289 | assert_equal('=&=', URI.encode_www_form([['', ''], ['', '']])) 290 | assert_equal('', URI.encode_www_form([['', nil]])) 291 | assert_equal('', URI.encode_www_form([])) 292 | assert_equal('=', URI.encode_www_form([['', '']])) 293 | assert_equal('a%26b=1&c=2%3B3&e=4', URI.encode_www_form([['a&b', '1'], ['c', '2;3'], ['e', '4']])) 294 | assert_equal('image&title&price', URI.encode_www_form([['image', nil], ['title', nil], ['price', nil]])) 295 | 296 | assert_equal("q=ruby&lang=en", URI.encode_www_form([["q", "ruby"], ["lang", "en"]])) 297 | assert_equal("q=ruby&lang=en", URI.encode_www_form("q" => "ruby", "lang" => "en")) 298 | assert_equal("q=ruby&q=perl&lang=en", URI.encode_www_form("q" => ["ruby", "perl"], "lang" => "en")) 299 | assert_equal("q=ruby&q=perl&lang=en", URI.encode_www_form([["q", "ruby"], ["q", "perl"], ["lang", "en"]])) 300 | end 301 | 302 | def test_decode_www_form 303 | assert_equal([%w[a 1], %w[a 2]], URI.decode_www_form("a=1&a=2")) 304 | assert_equal([%w[a 1;a=2]], URI.decode_www_form("a=1;a=2")) 305 | assert_equal([%w[a 1], ['', ''], %w[a 2]], URI.decode_www_form("a=1&&a=2")) 306 | assert_raise(ArgumentError){URI.decode_www_form("\u3042")} 307 | assert_equal([%w[a 1], ["\u3042", "\u6F22"]], 308 | URI.decode_www_form("a=1&%E3%81%82=%E6%BC%A2")) 309 | assert_equal([%w[a 1], ["\uFFFD%8", "\uFFFD"]], 310 | URI.decode_www_form("a=1&%E3%81%8=%E6%BC")) 311 | assert_equal([%w[?a 1], %w[a 2]], URI.decode_www_form("?a=1&a=2")) 312 | assert_equal([], URI.decode_www_form("")) 313 | assert_equal([%w[% 1]], URI.decode_www_form("%=1")) 314 | assert_equal([%w[a %]], URI.decode_www_form("a=%")) 315 | assert_equal([%w[a 1], %w[% 2]], URI.decode_www_form("a=1&%=2")) 316 | assert_equal([%w[a 1], %w[b %]], URI.decode_www_form("a=1&b=%")) 317 | assert_equal([['a', ''], ['b', '']], URI.decode_www_form("a&b")) 318 | bug4098 = '[ruby-core:33464]' 319 | assert_equal([['a', 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'], ['b', '']], URI.decode_www_form("a=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&b"), bug4098) 320 | 321 | assert_raise(ArgumentError){ URI.decode_www_form("a=1&%82%A0=%8A%BF", "x-sjis") } 322 | assert_equal([["a", "1"], [s("\x82\xA0"), s("\x8a\xBF")]], 323 | URI.decode_www_form("a=1&%82%A0=%8A%BF", "sjis")) 324 | assert_equal([["a", "1"], [s("\x82\xA0"), s("\x8a\xBF")], %w[_charset_ sjis], [s("\x82\xA1"), s("\x8a\xC0")]], 325 | URI.decode_www_form("a=1&%82%A0=%8A%BF&_charset_=sjis&%82%A1=%8A%C0", use__charset_: true)) 326 | assert_equal([["", "isindex"], ["a", "1"]], 327 | URI.decode_www_form("isindex&a=1", isindex: true)) 328 | end 329 | 330 | private 331 | def s(str) str.force_encoding(Encoding::Windows_31J); end 332 | end 333 | -------------------------------------------------------------------------------- /test/uri/test_file.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'test/unit' 3 | require 'uri/file' 4 | 5 | class URI::TestFile < Test::Unit::TestCase 6 | def test_parse 7 | u = URI("file://example.com/file") 8 | assert_equal "/file", u.path 9 | 10 | u = URI("file://localhost/file") 11 | assert_equal "/file", u.path 12 | assert_equal "file:///file", u.to_s 13 | 14 | u = URI("file://localhost:30/file") 15 | assert_equal "", u.host 16 | assert_equal nil, u.port 17 | assert_equal "/file", u.path 18 | assert_equal "file:///file", u.to_s 19 | 20 | u = URI("file:///file") 21 | assert_equal "/file", u.path 22 | assert_equal "file:///file", u.to_s 23 | 24 | u = URI("file:/file") 25 | assert_equal "/file", u.path 26 | assert_equal "file:///file", u.to_s 27 | 28 | u = URI("file://foo:pass@example.com/file") 29 | assert_equal "/file", u.path 30 | assert_equal nil, u.user 31 | assert_equal nil, u.password 32 | 33 | u = URI("file:///c:/path/to/file") 34 | assert_equal "/c:/path/to/file", u.path 35 | 36 | # this form is not supported 37 | u = URI("file:c:/path/to/file") 38 | assert_equal "c:/path/to/file", u.opaque 39 | 40 | end 41 | 42 | def test_build 43 | u = URI::File.build(scheme: "file", host: "example.com", path:"/file") 44 | assert_equal "/file", u.path 45 | assert_equal "file://example.com/file", u.to_s 46 | assert_raise(URI::InvalidURIError){ u.user = "foo" } 47 | assert_raise(URI::InvalidURIError){ u.password = "foo" } 48 | assert_raise(URI::InvalidURIError){ u.userinfo = "foo" } 49 | assert_raise(URI::InvalidURIError){ URI::File.build(scheme: "file", userinfo: "foo", host: "example.com", path:"/file") } 50 | 51 | u = URI::File.build(scheme: "file", path:"/file") 52 | assert_equal "", u.host 53 | assert_equal "/file", u.path 54 | assert_equal "file:///file", u.to_s 55 | 56 | u = URI::File.build(scheme: "file", host: "localhost", path:"/file") 57 | assert_equal "", u.host 58 | assert_equal "/file", u.path 59 | assert_equal "file:///file", u.to_s 60 | 61 | u = URI::File.build(scheme: "file", path:"/file", port: 30) 62 | assert_equal "", u.host 63 | assert_equal nil, u.port 64 | assert_equal "/file", u.path 65 | assert_equal "file:///file", u.to_s 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /test/uri/test_ftp.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'test/unit' 3 | require 'uri/ftp' 4 | 5 | class URI::TestFTP < Test::Unit::TestCase 6 | def setup 7 | end 8 | 9 | def test_parse 10 | url = URI.parse('ftp://user:pass@host.com/abc/def') 11 | assert_kind_of(URI::FTP, url) 12 | 13 | exp = [ 14 | 'ftp', 15 | 'user:pass', 'host.com', URI::FTP.default_port, 16 | 'abc/def', nil, 17 | ] 18 | ary = [ 19 | url.scheme, url.userinfo, url.host, url.port, 20 | url.path, url.opaque 21 | ] 22 | assert_equal(exp, ary) 23 | 24 | assert_equal('user', url.user) 25 | assert_equal('pass', url.password) 26 | end 27 | 28 | def test_parse_invalid 29 | assert_raise(URI::InvalidURIError) {URI.parse('ftp:example')} 30 | end 31 | 32 | def test_paths 33 | # If you think what's below is wrong, please read RubyForge bug 2055, 34 | # RFC 1738 section 3.2.2, and RFC 2396. 35 | u = URI.parse('ftp://ftp.example.com/foo/bar/file.ext') 36 | assert(u.path == 'foo/bar/file.ext') 37 | u = URI.parse('ftp://ftp.example.com//foo/bar/file.ext') 38 | assert(u.path == '/foo/bar/file.ext') 39 | u = URI.parse('ftp://ftp.example.com/%2Ffoo/bar/file.ext') 40 | assert(u.path == '/foo/bar/file.ext') 41 | end 42 | 43 | def test_assemble 44 | # uri/ftp is conservative and uses the older RFC 1738 rules, rather than 45 | # assuming everyone else has implemented RFC 2396. 46 | uri = URI::FTP.build(['user:password', 'ftp.example.com', nil, 47 | '/path/file.zip', 'i']) 48 | assert(uri.to_s == 49 | 'ftp://user:password@ftp.example.com/%2Fpath/file.zip;type=i') 50 | end 51 | 52 | def test_select 53 | assert_equal(['ftp', 'a.b.c', 21], URI.parse('ftp://a.b.c/').select(:scheme, :host, :port)) 54 | u = URI.parse('ftp://a.b.c/') 55 | ary = u.component.collect {|c| u.send(c)} 56 | assert_equal(ary, u.select(*u.component)) 57 | assert_raise(ArgumentError) do 58 | u.select(:scheme, :host, :not_exist, :port) 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /test/uri/test_generic.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'test/unit' 3 | require 'envutil' 4 | require 'uri' 5 | 6 | class URI::TestGeneric < Test::Unit::TestCase 7 | def setup 8 | @url = 'http://a/b/c/d;p?q' 9 | @base_url = URI.parse(@url) 10 | end 11 | 12 | def teardown 13 | end 14 | 15 | def uri_to_ary(uri) 16 | uri.class.component.collect {|c| uri.send(c)} 17 | end 18 | 19 | def test_to_s 20 | exp = 'http://example.com/'.freeze 21 | str = URI(exp).to_s 22 | assert_equal exp, str 23 | assert_not_predicate str, :frozen?, '[ruby-core:71785] [Bug #11759]' 24 | 25 | assert_equal "file:///foo", URI("file:///foo").to_s 26 | assert_equal "postgres:///foo", URI("postgres:///foo").to_s 27 | assert_equal "http:///foo", URI("http:///foo").to_s 28 | assert_equal "http:/foo", URI("http:/foo").to_s 29 | 30 | uri = URI('rel_path') 31 | assert_equal "rel_path", uri.to_s 32 | uri.scheme = 'http' 33 | assert_equal "http:rel_path", uri.to_s 34 | uri.host = 'h' 35 | assert_equal "http://h/rel_path", uri.to_s 36 | uri.port = 8080 37 | assert_equal "http://h:8080/rel_path", uri.to_s 38 | uri.host = nil 39 | assert_equal "http::8080/rel_path", uri.to_s 40 | end 41 | 42 | def test_parse 43 | # 0 44 | assert_kind_of(URI::HTTP, @base_url) 45 | 46 | exp = [ 47 | 'http', 48 | nil, 'a', URI::HTTP.default_port, 49 | '/b/c/d;p', 50 | 'q', 51 | nil 52 | ] 53 | ary = uri_to_ary(@base_url) 54 | assert_equal(exp, ary) 55 | 56 | # 1 57 | url = URI.parse('ftp://ftp.is.co.za/rfc/rfc1808.txt') 58 | assert_kind_of(URI::FTP, url) 59 | 60 | exp = [ 61 | 'ftp', 62 | nil, 'ftp.is.co.za', URI::FTP.default_port, 63 | 'rfc/rfc1808.txt', nil, 64 | ] 65 | ary = uri_to_ary(url) 66 | assert_equal(exp, ary) 67 | # 1' 68 | url = URI.parse('ftp://ftp.is.co.za/%2Frfc/rfc1808.txt') 69 | assert_kind_of(URI::FTP, url) 70 | 71 | exp = [ 72 | 'ftp', 73 | nil, 'ftp.is.co.za', URI::FTP.default_port, 74 | '/rfc/rfc1808.txt', nil, 75 | ] 76 | ary = uri_to_ary(url) 77 | assert_equal(exp, ary) 78 | 79 | # 2 80 | url = URI.parse('gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles') 81 | assert_kind_of(URI::Generic, url) 82 | 83 | exp = [ 84 | 'gopher', 85 | nil, 'spinaltap.micro.umn.edu', nil, nil, 86 | '/00/Weather/California/Los%20Angeles', nil, 87 | nil, 88 | nil 89 | ] 90 | ary = uri_to_ary(url) 91 | assert_equal(exp, ary) 92 | 93 | # 3 94 | url = URI.parse('http://www.math.uio.no/faq/compression-faq/part1.html') 95 | assert_kind_of(URI::HTTP, url) 96 | 97 | exp = [ 98 | 'http', 99 | nil, 'www.math.uio.no', URI::HTTP.default_port, 100 | '/faq/compression-faq/part1.html', 101 | nil, 102 | nil 103 | ] 104 | ary = uri_to_ary(url) 105 | assert_equal(exp, ary) 106 | 107 | # 4 108 | url = URI.parse('mailto:mduerst@ifi.unizh.ch') 109 | assert_kind_of(URI::Generic, url) 110 | 111 | exp = [ 112 | 'mailto', 113 | 'mduerst@ifi.unizh.ch', 114 | [] 115 | ] 116 | ary = uri_to_ary(url) 117 | assert_equal(exp, ary) 118 | 119 | # 5 120 | url = URI.parse('news:comp.infosystems.www.servers.unix') 121 | assert_kind_of(URI::Generic, url) 122 | 123 | exp = [ 124 | 'news', 125 | nil, nil, nil, nil, 126 | nil, 'comp.infosystems.www.servers.unix', 127 | nil, 128 | nil 129 | ] 130 | ary = uri_to_ary(url) 131 | assert_equal(exp, ary) 132 | 133 | # 6 134 | url = URI.parse('telnet://melvyl.ucop.edu/') 135 | assert_kind_of(URI::Generic, url) 136 | 137 | exp = [ 138 | 'telnet', 139 | nil, 'melvyl.ucop.edu', nil, nil, 140 | '/', nil, 141 | nil, 142 | nil 143 | ] 144 | ary = uri_to_ary(url) 145 | assert_equal(exp, ary) 146 | 147 | # 7 148 | # reported by Mr. Kubota 149 | assert_nothing_raised(URI::InvalidURIError) { URI.parse('http://a_b:80/') } 150 | assert_nothing_raised(URI::InvalidURIError) { URI.parse('http://a_b/') } 151 | 152 | # 8 153 | # reported by m_seki 154 | url = URI.parse('file:///foo/bar.txt') 155 | assert_kind_of(URI::Generic, url) 156 | url = URI.parse('file:/foo/bar.txt') 157 | assert_kind_of(URI::Generic, url) 158 | 159 | # 9 160 | url = URI.parse('ftp://:pass@localhost/') 161 | assert_equal('', url.user, "[ruby-dev:25667]") 162 | assert_equal('pass', url.password) 163 | assert_equal(':pass', url.userinfo, "[ruby-dev:25667]") 164 | url = URI.parse('ftp://user@localhost/') 165 | assert_equal('user', url.user) 166 | assert_equal(nil, url.password) 167 | assert_equal('user', url.userinfo) 168 | url = URI.parse('ftp://localhost/') 169 | assert_equal(nil, url.user) 170 | assert_equal(nil, url.password) 171 | assert_equal(nil, url.userinfo) 172 | 173 | # sec-156615 174 | url = URI.parse('http:////example.com') 175 | # must be empty string to identify as path-abempty, not path-absolute 176 | assert_equal('', url.host) 177 | assert_equal('http:////example.com', url.to_s) 178 | 179 | # sec-2957667 180 | url = URI.parse('http://user:pass@example.com').merge('//example.net') 181 | assert_equal('http://example.net', url.to_s) 182 | assert_nil(url.userinfo) 183 | url = URI.join('http://user:pass@example.com', '//example.net') 184 | assert_equal('http://example.net', url.to_s) 185 | assert_nil(url.userinfo) 186 | url = URI.parse('http://user:pass@example.com') + '//example.net' 187 | assert_equal('http://example.net', url.to_s) 188 | assert_nil(url.userinfo) 189 | end 190 | 191 | def test_parse_scheme_with_symbols 192 | # Valid schemes from https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml 193 | assert_equal 'ms-search', URI.parse('ms-search://localhost').scheme 194 | assert_equal 'microsoft.windows.camera', URI.parse('microsoft.windows.camera://localhost').scheme 195 | assert_equal 'coaps+ws', URI.parse('coaps+ws:localhost').scheme 196 | end 197 | 198 | def test_merge 199 | u1 = URI.parse('http://foo') 200 | u2 = URI.parse('http://foo/') 201 | u3 = URI.parse('http://foo/bar') 202 | u4 = URI.parse('http://foo/bar/') 203 | 204 | { 205 | u1 => { 206 | 'baz' => 'http://foo/baz', 207 | '/baz' => 'http://foo/baz', 208 | }, 209 | u2 => { 210 | 'baz' => 'http://foo/baz', 211 | '/baz' => 'http://foo/baz', 212 | }, 213 | u3 => { 214 | 'baz' => 'http://foo/baz', 215 | '/baz' => 'http://foo/baz', 216 | }, 217 | u4 => { 218 | 'baz' => 'http://foo/bar/baz', 219 | '/baz' => 'http://foo/baz', 220 | }, 221 | }.each { |base, map| 222 | map.each { |url, result| 223 | expected = URI.parse(result) 224 | uri = URI.parse(url) 225 | assert_equal expected, base + url, "<#{base}> + #{url.inspect} to become <#{expected}>" 226 | assert_equal expected, base + uri, "<#{base}> + <#{uri}> to become <#{expected}>" 227 | } 228 | } 229 | 230 | url = URI.parse('http://hoge/a.html') + 'b.html' 231 | assert_equal('http://hoge/b.html', url.to_s, "[ruby-dev:11508]") 232 | 233 | # reported by Mr. Kubota 234 | url = URI.parse('http://a/b') + 'http://x/y' 235 | assert_equal('http://x/y', url.to_s) 236 | assert_equal(url, URI.parse('') + 'http://x/y') 237 | assert_equal(url, URI.parse('').normalize + 'http://x/y') 238 | assert_equal(url, URI.parse('http://a/b').normalize + 'http://x/y') 239 | 240 | u = URI.parse('http://foo/bar/baz') 241 | assert_equal(nil, u.merge!("")) 242 | assert_equal(nil, u.merge!(u)) 243 | assert(nil != u.merge!(".")) 244 | assert_equal('http://foo/bar/', u.to_s) 245 | assert(nil != u.merge!("../baz")) 246 | assert_equal('http://foo/baz', u.to_s) 247 | 248 | url = URI.parse('http://a/b//c') + 'd//e' 249 | assert_equal('http://a/b//d//e', url.to_s) 250 | 251 | u0 = URI.parse('mailto:foo@example.com') 252 | u1 = URI.parse('mailto:foo@example.com#bar') 253 | assert_equal(uri_to_ary(u0 + '#bar'), uri_to_ary(u1), "[ruby-dev:23628]") 254 | 255 | u0 = URI.parse('http://www.example.com/') 256 | u1 = URI.parse('http://www.example.com/foo/..') + './' 257 | assert_equal(u0, u1, "[ruby-list:39838]") 258 | u0 = URI.parse('http://www.example.com/foo/') 259 | u1 = URI.parse('http://www.example.com/foo/bar/..') + './' 260 | assert_equal(u0, u1) 261 | u0 = URI.parse('http://www.example.com/foo/bar/') 262 | u1 = URI.parse('http://www.example.com/foo/bar/baz/..') + './' 263 | assert_equal(u0, u1) 264 | u0 = URI.parse('http://www.example.com/') 265 | u1 = URI.parse('http://www.example.com/foo/bar/../..') + './' 266 | assert_equal(u0, u1) 267 | u0 = URI.parse('http://www.example.com/foo/') 268 | u1 = URI.parse('http://www.example.com/foo/bar/baz/../..') + './' 269 | assert_equal(u0, u1) 270 | 271 | u = URI.parse('http://www.example.com/') 272 | u0 = u + './foo/' 273 | u1 = u + './foo/bar/..' 274 | assert_equal(u0, u1, "[ruby-list:39844]") 275 | u = URI.parse('http://www.example.com/') 276 | u0 = u + './' 277 | u1 = u + './foo/bar/../..' 278 | assert_equal(u0, u1) 279 | end 280 | 281 | def test_merge_authority 282 | u = URI.parse('http://user:pass@example.com:8080') 283 | u0 = URI.parse('http://new.example.org/path') 284 | u1 = u.merge('//new.example.org/path') 285 | assert_equal(u0, u1) 286 | end 287 | 288 | def test_route 289 | url = URI.parse('http://hoge/a.html').route_to('http://hoge/b.html') 290 | assert_equal('b.html', url.to_s) 291 | 292 | url = URI.parse('http://hoge/a/').route_to('http://hoge/b/') 293 | assert_equal('../b/', url.to_s) 294 | url = URI.parse('http://hoge/a/b').route_to('http://hoge/b/') 295 | assert_equal('../b/', url.to_s) 296 | 297 | url = URI.parse('http://hoge/a/b/').route_to('http://hoge/b/') 298 | assert_equal('../../b/', url.to_s) 299 | 300 | url = URI.parse('http://hoge/a/b/').route_to('http://HOGE/b/') 301 | assert_equal('../../b/', url.to_s) 302 | 303 | url = URI.parse('http://hoge/a/b/').route_to('http://MOGE/b/') 304 | assert_equal('//MOGE/b/', url.to_s) 305 | 306 | url = URI.parse('http://hoge/b').route_to('http://hoge/b/') 307 | assert_equal('b/', url.to_s) 308 | url = URI.parse('http://hoge/b/a').route_to('http://hoge/b/') 309 | assert_equal('./', url.to_s) 310 | url = URI.parse('http://hoge/b/').route_to('http://hoge/b') 311 | assert_equal('../b', url.to_s) 312 | url = URI.parse('http://hoge/b').route_to('http://hoge/b:c') 313 | assert_equal('./b:c', url.to_s) 314 | 315 | url = URI.parse('http://hoge/b//c').route_to('http://hoge/b/c') 316 | assert_equal('../c', url.to_s) 317 | 318 | url = URI.parse('file:///a/b/').route_to('file:///a/b/') 319 | assert_equal('', url.to_s) 320 | url = URI.parse('file:///a/b/').route_to('file:///a/b') 321 | assert_equal('../b', url.to_s) 322 | 323 | url = URI.parse('mailto:foo@example.com').route_to('mailto:foo@example.com#bar') 324 | assert_equal('#bar', url.to_s) 325 | 326 | url = URI.parse('mailto:foo@example.com#bar').route_to('mailto:foo@example.com') 327 | assert_equal('', url.to_s) 328 | 329 | url = URI.parse('mailto:foo@example.com').route_to('mailto:foo@example.com') 330 | assert_equal('', url.to_s) 331 | end 332 | 333 | def test_rfc3986_examples 334 | # http://a/b/c/d;p?q 335 | # g:h = g:h 336 | url = @base_url.merge('g:h') 337 | assert_kind_of(URI::Generic, url) 338 | assert_equal('g:h', url.to_s) 339 | url = @base_url.route_to('g:h') 340 | assert_kind_of(URI::Generic, url) 341 | assert_equal('g:h', url.to_s) 342 | 343 | # http://a/b/c/d;p?q 344 | # g = http://a/b/c/g 345 | url = @base_url.merge('g') 346 | assert_kind_of(URI::HTTP, url) 347 | assert_equal('http://a/b/c/g', url.to_s) 348 | url = @base_url.route_to('http://a/b/c/g') 349 | assert_kind_of(URI::Generic, url) 350 | assert_equal('g', url.to_s) 351 | 352 | # http://a/b/c/d;p?q 353 | # ./g = http://a/b/c/g 354 | url = @base_url.merge('./g') 355 | assert_kind_of(URI::HTTP, url) 356 | assert_equal('http://a/b/c/g', url.to_s) 357 | url = @base_url.route_to('http://a/b/c/g') 358 | assert_kind_of(URI::Generic, url) 359 | assert('./g' != url.to_s) # ok 360 | assert_equal('g', url.to_s) 361 | 362 | # http://a/b/c/d;p?q 363 | # g/ = http://a/b/c/g/ 364 | url = @base_url.merge('g/') 365 | assert_kind_of(URI::HTTP, url) 366 | assert_equal('http://a/b/c/g/', url.to_s) 367 | url = @base_url.route_to('http://a/b/c/g/') 368 | assert_kind_of(URI::Generic, url) 369 | assert_equal('g/', url.to_s) 370 | 371 | # http://a/b/c/d;p?q 372 | # /g = http://a/g 373 | url = @base_url.merge('/g') 374 | assert_kind_of(URI::HTTP, url) 375 | assert_equal('http://a/g', url.to_s) 376 | url = @base_url.route_to('http://a/g') 377 | assert_kind_of(URI::Generic, url) 378 | assert('/g' != url.to_s) # ok 379 | assert_equal('../../g', url.to_s) 380 | 381 | # http://a/b/c/d;p?q 382 | # //g = http://g 383 | url = @base_url.merge('//g') 384 | assert_kind_of(URI::HTTP, url) 385 | assert_equal('http://g', url.to_s) 386 | url = @base_url.route_to('http://g') 387 | assert_kind_of(URI::Generic, url) 388 | assert_equal('//g', url.to_s) 389 | 390 | # http://a/b/c/d;p?q 391 | # ?y = http://a/b/c/d;p?y 392 | url = @base_url.merge('?y') 393 | assert_kind_of(URI::HTTP, url) 394 | assert_equal('http://a/b/c/d;p?y', url.to_s) 395 | url = @base_url.route_to('http://a/b/c/d;p?y') 396 | assert_kind_of(URI::Generic, url) 397 | assert_equal('?y', url.to_s) 398 | 399 | # http://a/b/c/d;p?q 400 | # g?y = http://a/b/c/g?y 401 | url = @base_url.merge('g?y') 402 | assert_kind_of(URI::HTTP, url) 403 | assert_equal('http://a/b/c/g?y', url.to_s) 404 | url = @base_url.route_to('http://a/b/c/g?y') 405 | assert_kind_of(URI::Generic, url) 406 | assert_equal('g?y', url.to_s) 407 | 408 | # http://a/b/c/d;p?q 409 | # #s = http://a/b/c/d;p?q#s 410 | url = @base_url.merge('#s') 411 | assert_kind_of(URI::HTTP, url) 412 | assert_equal('http://a/b/c/d;p?q#s', url.to_s) 413 | url = @base_url.route_to('http://a/b/c/d;p?q#s') 414 | assert_kind_of(URI::Generic, url) 415 | assert_equal('#s', url.to_s) 416 | 417 | # http://a/b/c/d;p?q 418 | # g#s = http://a/b/c/g#s 419 | url = @base_url.merge('g#s') 420 | assert_kind_of(URI::HTTP, url) 421 | assert_equal('http://a/b/c/g#s', url.to_s) 422 | url = @base_url.route_to('http://a/b/c/g#s') 423 | assert_kind_of(URI::Generic, url) 424 | assert_equal('g#s', url.to_s) 425 | 426 | # http://a/b/c/d;p?q 427 | # g?y#s = http://a/b/c/g?y#s 428 | url = @base_url.merge('g?y#s') 429 | assert_kind_of(URI::HTTP, url) 430 | assert_equal('http://a/b/c/g?y#s', url.to_s) 431 | url = @base_url.route_to('http://a/b/c/g?y#s') 432 | assert_kind_of(URI::Generic, url) 433 | assert_equal('g?y#s', url.to_s) 434 | 435 | # http://a/b/c/d;p?q 436 | # ;x = http://a/b/c/;x 437 | url = @base_url.merge(';x') 438 | assert_kind_of(URI::HTTP, url) 439 | assert_equal('http://a/b/c/;x', url.to_s) 440 | url = @base_url.route_to('http://a/b/c/;x') 441 | assert_kind_of(URI::Generic, url) 442 | assert_equal(';x', url.to_s) 443 | 444 | # http://a/b/c/d;p?q 445 | # g;x = http://a/b/c/g;x 446 | url = @base_url.merge('g;x') 447 | assert_kind_of(URI::HTTP, url) 448 | assert_equal('http://a/b/c/g;x', url.to_s) 449 | url = @base_url.route_to('http://a/b/c/g;x') 450 | assert_kind_of(URI::Generic, url) 451 | assert_equal('g;x', url.to_s) 452 | 453 | # http://a/b/c/d;p?q 454 | # g;x?y#s = http://a/b/c/g;x?y#s 455 | url = @base_url.merge('g;x?y#s') 456 | assert_kind_of(URI::HTTP, url) 457 | assert_equal('http://a/b/c/g;x?y#s', url.to_s) 458 | url = @base_url.route_to('http://a/b/c/g;x?y#s') 459 | assert_kind_of(URI::Generic, url) 460 | assert_equal('g;x?y#s', url.to_s) 461 | 462 | # http://a/b/c/d;p?q 463 | # . = http://a/b/c/ 464 | url = @base_url.merge('.') 465 | assert_kind_of(URI::HTTP, url) 466 | assert_equal('http://a/b/c/', url.to_s) 467 | url = @base_url.route_to('http://a/b/c/') 468 | assert_kind_of(URI::Generic, url) 469 | assert('.' != url.to_s) # ok 470 | assert_equal('./', url.to_s) 471 | 472 | # http://a/b/c/d;p?q 473 | # ./ = http://a/b/c/ 474 | url = @base_url.merge('./') 475 | assert_kind_of(URI::HTTP, url) 476 | assert_equal('http://a/b/c/', url.to_s) 477 | url = @base_url.route_to('http://a/b/c/') 478 | assert_kind_of(URI::Generic, url) 479 | assert_equal('./', url.to_s) 480 | 481 | # http://a/b/c/d;p?q 482 | # .. = http://a/b/ 483 | url = @base_url.merge('..') 484 | assert_kind_of(URI::HTTP, url) 485 | assert_equal('http://a/b/', url.to_s) 486 | url = @base_url.route_to('http://a/b/') 487 | assert_kind_of(URI::Generic, url) 488 | assert('..' != url.to_s) # ok 489 | assert_equal('../', url.to_s) 490 | 491 | # http://a/b/c/d;p?q 492 | # ../ = http://a/b/ 493 | url = @base_url.merge('../') 494 | assert_kind_of(URI::HTTP, url) 495 | assert_equal('http://a/b/', url.to_s) 496 | url = @base_url.route_to('http://a/b/') 497 | assert_kind_of(URI::Generic, url) 498 | assert_equal('../', url.to_s) 499 | 500 | # http://a/b/c/d;p?q 501 | # ../g = http://a/b/g 502 | url = @base_url.merge('../g') 503 | assert_kind_of(URI::HTTP, url) 504 | assert_equal('http://a/b/g', url.to_s) 505 | url = @base_url.route_to('http://a/b/g') 506 | assert_kind_of(URI::Generic, url) 507 | assert_equal('../g', url.to_s) 508 | 509 | # http://a/b/c/d;p?q 510 | # ../.. = http://a/ 511 | url = @base_url.merge('../..') 512 | assert_kind_of(URI::HTTP, url) 513 | assert_equal('http://a/', url.to_s) 514 | url = @base_url.route_to('http://a/') 515 | assert_kind_of(URI::Generic, url) 516 | assert('../..' != url.to_s) # ok 517 | assert_equal('../../', url.to_s) 518 | 519 | # http://a/b/c/d;p?q 520 | # ../../ = http://a/ 521 | url = @base_url.merge('../../') 522 | assert_kind_of(URI::HTTP, url) 523 | assert_equal('http://a/', url.to_s) 524 | url = @base_url.route_to('http://a/') 525 | assert_kind_of(URI::Generic, url) 526 | assert_equal('../../', url.to_s) 527 | 528 | # http://a/b/c/d;p?q 529 | # ../../g = http://a/g 530 | url = @base_url.merge('../../g') 531 | assert_kind_of(URI::HTTP, url) 532 | assert_equal('http://a/g', url.to_s) 533 | url = @base_url.route_to('http://a/g') 534 | assert_kind_of(URI::Generic, url) 535 | assert_equal('../../g', url.to_s) 536 | 537 | # http://a/b/c/d;p?q 538 | # <> = (current document) 539 | url = @base_url.merge('') 540 | assert_kind_of(URI::HTTP, url) 541 | assert_equal('http://a/b/c/d;p?q', url.to_s) 542 | url = @base_url.route_to('http://a/b/c/d;p?q') 543 | assert_kind_of(URI::Generic, url) 544 | assert_equal('', url.to_s) 545 | 546 | # http://a/b/c/d;p?q 547 | # /./g = http://a/g 548 | url = @base_url.merge('/./g') 549 | assert_kind_of(URI::HTTP, url) 550 | assert_equal('http://a/g', url.to_s) 551 | # url = @base_url.route_to('http://a/./g') 552 | # assert_kind_of(URI::Generic, url) 553 | # assert_equal('/./g', url.to_s) 554 | 555 | # http://a/b/c/d;p?q 556 | # /../g = http://a/g 557 | url = @base_url.merge('/../g') 558 | assert_kind_of(URI::HTTP, url) 559 | assert_equal('http://a/g', url.to_s) 560 | # url = @base_url.route_to('http://a/../g') 561 | # assert_kind_of(URI::Generic, url) 562 | # assert_equal('/../g', url.to_s) 563 | 564 | # http://a/b/c/d;p?q 565 | # g. = http://a/b/c/g. 566 | url = @base_url.merge('g.') 567 | assert_kind_of(URI::HTTP, url) 568 | assert_equal('http://a/b/c/g.', url.to_s) 569 | url = @base_url.route_to('http://a/b/c/g.') 570 | assert_kind_of(URI::Generic, url) 571 | assert_equal('g.', url.to_s) 572 | 573 | # http://a/b/c/d;p?q 574 | # .g = http://a/b/c/.g 575 | url = @base_url.merge('.g') 576 | assert_kind_of(URI::HTTP, url) 577 | assert_equal('http://a/b/c/.g', url.to_s) 578 | url = @base_url.route_to('http://a/b/c/.g') 579 | assert_kind_of(URI::Generic, url) 580 | assert_equal('.g', url.to_s) 581 | 582 | # http://a/b/c/d;p?q 583 | # g.. = http://a/b/c/g.. 584 | url = @base_url.merge('g..') 585 | assert_kind_of(URI::HTTP, url) 586 | assert_equal('http://a/b/c/g..', url.to_s) 587 | url = @base_url.route_to('http://a/b/c/g..') 588 | assert_kind_of(URI::Generic, url) 589 | assert_equal('g..', url.to_s) 590 | 591 | # http://a/b/c/d;p?q 592 | # ..g = http://a/b/c/..g 593 | url = @base_url.merge('..g') 594 | assert_kind_of(URI::HTTP, url) 595 | assert_equal('http://a/b/c/..g', url.to_s) 596 | url = @base_url.route_to('http://a/b/c/..g') 597 | assert_kind_of(URI::Generic, url) 598 | assert_equal('..g', url.to_s) 599 | 600 | # http://a/b/c/d;p?q 601 | # ../../../g = http://a/g 602 | url = @base_url.merge('../../../g') 603 | assert_kind_of(URI::HTTP, url) 604 | assert_equal('http://a/g', url.to_s) 605 | url = @base_url.route_to('http://a/g') 606 | assert_kind_of(URI::Generic, url) 607 | assert('../../../g' != url.to_s) # ok? yes, it confuses you 608 | assert_equal('../../g', url.to_s) # and it is clearly 609 | 610 | # http://a/b/c/d;p?q 611 | # ../../../../g = http://a/g 612 | url = @base_url.merge('../../../../g') 613 | assert_kind_of(URI::HTTP, url) 614 | assert_equal('http://a/g', url.to_s) 615 | url = @base_url.route_to('http://a/g') 616 | assert_kind_of(URI::Generic, url) 617 | assert('../../../../g' != url.to_s) # ok? yes, it confuses you 618 | assert_equal('../../g', url.to_s) # and it is clearly 619 | 620 | # http://a/b/c/d;p?q 621 | # ./../g = http://a/b/g 622 | url = @base_url.merge('./../g') 623 | assert_kind_of(URI::HTTP, url) 624 | assert_equal('http://a/b/g', url.to_s) 625 | url = @base_url.route_to('http://a/b/g') 626 | assert_kind_of(URI::Generic, url) 627 | assert('./../g' != url.to_s) # ok 628 | assert_equal('../g', url.to_s) 629 | 630 | # http://a/b/c/d;p?q 631 | # ./g/. = http://a/b/c/g/ 632 | url = @base_url.merge('./g/.') 633 | assert_kind_of(URI::HTTP, url) 634 | assert_equal('http://a/b/c/g/', url.to_s) 635 | url = @base_url.route_to('http://a/b/c/g/') 636 | assert_kind_of(URI::Generic, url) 637 | assert('./g/.' != url.to_s) # ok 638 | assert_equal('g/', url.to_s) 639 | 640 | # http://a/b/c/d;p?q 641 | # g/./h = http://a/b/c/g/h 642 | url = @base_url.merge('g/./h') 643 | assert_kind_of(URI::HTTP, url) 644 | assert_equal('http://a/b/c/g/h', url.to_s) 645 | url = @base_url.route_to('http://a/b/c/g/h') 646 | assert_kind_of(URI::Generic, url) 647 | assert('g/./h' != url.to_s) # ok 648 | assert_equal('g/h', url.to_s) 649 | 650 | # http://a/b/c/d;p?q 651 | # g/../h = http://a/b/c/h 652 | url = @base_url.merge('g/../h') 653 | assert_kind_of(URI::HTTP, url) 654 | assert_equal('http://a/b/c/h', url.to_s) 655 | url = @base_url.route_to('http://a/b/c/h') 656 | assert_kind_of(URI::Generic, url) 657 | assert('g/../h' != url.to_s) # ok 658 | assert_equal('h', url.to_s) 659 | 660 | # http://a/b/c/d;p?q 661 | # g;x=1/./y = http://a/b/c/g;x=1/y 662 | url = @base_url.merge('g;x=1/./y') 663 | assert_kind_of(URI::HTTP, url) 664 | assert_equal('http://a/b/c/g;x=1/y', url.to_s) 665 | url = @base_url.route_to('http://a/b/c/g;x=1/y') 666 | assert_kind_of(URI::Generic, url) 667 | assert('g;x=1/./y' != url.to_s) # ok 668 | assert_equal('g;x=1/y', url.to_s) 669 | 670 | # http://a/b/c/d;p?q 671 | # g;x=1/../y = http://a/b/c/y 672 | url = @base_url.merge('g;x=1/../y') 673 | assert_kind_of(URI::HTTP, url) 674 | assert_equal('http://a/b/c/y', url.to_s) 675 | url = @base_url.route_to('http://a/b/c/y') 676 | assert_kind_of(URI::Generic, url) 677 | assert('g;x=1/../y' != url.to_s) # ok 678 | assert_equal('y', url.to_s) 679 | 680 | # http://a/b/c/d;p?q 681 | # g?y/./x = http://a/b/c/g?y/./x 682 | url = @base_url.merge('g?y/./x') 683 | assert_kind_of(URI::HTTP, url) 684 | assert_equal('http://a/b/c/g?y/./x', url.to_s) 685 | url = @base_url.route_to('http://a/b/c/g?y/./x') 686 | assert_kind_of(URI::Generic, url) 687 | assert_equal('g?y/./x', url.to_s) 688 | 689 | # http://a/b/c/d;p?q 690 | # g?y/../x = http://a/b/c/g?y/../x 691 | url = @base_url.merge('g?y/../x') 692 | assert_kind_of(URI::HTTP, url) 693 | assert_equal('http://a/b/c/g?y/../x', url.to_s) 694 | url = @base_url.route_to('http://a/b/c/g?y/../x') 695 | assert_kind_of(URI::Generic, url) 696 | assert_equal('g?y/../x', url.to_s) 697 | 698 | # http://a/b/c/d;p?q 699 | # g#s/./x = http://a/b/c/g#s/./x 700 | url = @base_url.merge('g#s/./x') 701 | assert_kind_of(URI::HTTP, url) 702 | assert_equal('http://a/b/c/g#s/./x', url.to_s) 703 | url = @base_url.route_to('http://a/b/c/g#s/./x') 704 | assert_kind_of(URI::Generic, url) 705 | assert_equal('g#s/./x', url.to_s) 706 | 707 | # http://a/b/c/d;p?q 708 | # g#s/../x = http://a/b/c/g#s/../x 709 | url = @base_url.merge('g#s/../x') 710 | assert_kind_of(URI::HTTP, url) 711 | assert_equal('http://a/b/c/g#s/../x', url.to_s) 712 | url = @base_url.route_to('http://a/b/c/g#s/../x') 713 | assert_kind_of(URI::Generic, url) 714 | assert_equal('g#s/../x', url.to_s) 715 | 716 | # http://a/b/c/d;p?q 717 | # http:g = http:g ; for validating parsers 718 | # | http://a/b/c/g ; for backwards compatibility 719 | url = @base_url.merge('http:g') 720 | assert_kind_of(URI::HTTP, url) 721 | assert_equal('http:g', url.to_s) 722 | url = @base_url.route_to('http:g') 723 | assert_kind_of(URI::Generic, url) 724 | assert_equal('http:g', url.to_s) 725 | end 726 | 727 | def test_join 728 | assert_equal(URI.parse('http://foo/bar'), URI.join('http://foo/bar')) 729 | assert_equal(URI.parse('http://foo/bar'), URI.join('http://foo', 'bar')) 730 | assert_equal(URI.parse('http://foo/bar/'), URI.join('http://foo', 'bar/')) 731 | 732 | assert_equal(URI.parse('http://foo/baz'), URI.join('http://foo', 'bar', 'baz')) 733 | assert_equal(URI.parse('http://foo/baz'), URI.join('http://foo', 'bar', '/baz')) 734 | assert_equal(URI.parse('http://foo/baz/'), URI.join('http://foo', 'bar', '/baz/')) 735 | assert_equal(URI.parse('http://foo/bar/baz'), URI.join('http://foo', 'bar/', 'baz')) 736 | assert_equal(URI.parse('http://foo/hoge'), URI.join('http://foo', 'bar', 'baz', 'hoge')) 737 | 738 | assert_equal(URI.parse('http://foo/bar/baz'), URI.join('http://foo', 'bar/baz')) 739 | assert_equal(URI.parse('http://foo/bar/hoge'), URI.join('http://foo', 'bar/baz', 'hoge')) 740 | assert_equal(URI.parse('http://foo/bar/baz/hoge'), URI.join('http://foo', 'bar/baz/', 'hoge')) 741 | assert_equal(URI.parse('http://foo/hoge'), URI.join('http://foo', 'bar/baz', '/hoge')) 742 | assert_equal(URI.parse('http://foo/bar/hoge'), URI.join('http://foo', 'bar/baz', 'hoge')) 743 | assert_equal(URI.parse('http://foo/bar/baz/hoge'), URI.join('http://foo', 'bar/baz/', 'hoge')) 744 | assert_equal(URI.parse('http://foo/hoge'), URI.join('http://foo', 'bar/baz', '/hoge')) 745 | end 746 | 747 | # ruby-dev:16728 748 | def test_set_component 749 | uri = URI.parse('http://foo:bar@baz') 750 | assert_equal('oof', uri.user = 'oof') 751 | assert_equal('http://oof:bar@baz', uri.to_s) 752 | assert_equal('rab', uri.password = 'rab') 753 | assert_equal('http://oof:rab@baz', uri.to_s) 754 | assert_equal('foo', uri.userinfo = 'foo') 755 | assert_equal('http://foo:rab@baz', uri.to_s) 756 | assert_equal(['foo', 'bar'], uri.userinfo = ['foo', 'bar']) 757 | assert_equal('http://foo:bar@baz', uri.to_s) 758 | assert_equal(['foo'], uri.userinfo = ['foo']) 759 | assert_equal('http://foo:bar@baz', uri.to_s) 760 | assert_equal('zab', uri.host = 'zab') 761 | assert_equal('http://foo:bar@zab', uri.to_s) 762 | uri.port = "" 763 | assert_nil(uri.port) 764 | uri.port = "80" 765 | assert_equal(80, uri.port) 766 | uri.port = "080" 767 | assert_equal(80, uri.port) 768 | uri.port = " 080 " 769 | assert_equal(80, uri.port) 770 | assert_equal(8080, uri.port = 8080) 771 | assert_equal('http://foo:bar@zab:8080', uri.to_s) 772 | assert_equal('/', uri.path = '/') 773 | assert_equal('http://foo:bar@zab:8080/', uri.to_s) 774 | assert_equal('a=1', uri.query = 'a=1') 775 | assert_equal('http://foo:bar@zab:8080/?a=1', uri.to_s) 776 | assert_equal('b123', uri.fragment = 'b123') 777 | assert_equal('http://foo:bar@zab:8080/?a=1#b123', uri.to_s) 778 | assert_equal('a[]=1', uri.query = 'a[]=1') 779 | assert_equal('http://foo:bar@zab:8080/?a[]=1#b123', uri.to_s) 780 | uri = URI.parse('http://foo:bar@zab:8080/?a[]=1#b123') 781 | assert_equal('http://foo:bar@zab:8080/?a[]=1#b123', uri.to_s) 782 | 783 | uri = URI.parse('http://example.com') 784 | assert_raise(URI::InvalidURIError) { uri.password = 'bar' } 785 | assert_equal("foo\nbar", uri.query = "foo\nbar") 786 | uri.userinfo = 'foo:bar' 787 | assert_equal('http://foo:bar@example.com?foobar', uri.to_s) 788 | assert_raise(URI::InvalidURIError) { uri.registry = 'bar' } 789 | assert_raise(URI::InvalidURIError) { uri.opaque = 'bar' } 790 | 791 | uri = URI.parse('mailto:foo@example.com') 792 | assert_raise(URI::InvalidURIError) { uri.user = 'bar' } 793 | assert_raise(URI::InvalidURIError) { uri.password = 'bar' } 794 | assert_raise(URI::InvalidURIError) { uri.userinfo = ['bar', 'baz'] } 795 | assert_raise(URI::InvalidURIError) { uri.host = 'bar' } 796 | assert_raise(URI::InvalidURIError) { uri.port = 'bar' } 797 | assert_raise(URI::InvalidURIError) { uri.path = 'bar' } 798 | assert_raise(URI::InvalidURIError) { uri.query = 'bar' } 799 | 800 | uri = URI.parse('foo:bar') 801 | assert_raise(URI::InvalidComponentError) { uri.opaque = '/baz' } 802 | uri.opaque = 'xyzzy' 803 | assert_equal('foo:xyzzy', uri.to_s) 804 | end 805 | 806 | def test_bad_password_component 807 | uri = URI.parse('http://foo:bar@baz') 808 | password = 'foo@bar' 809 | e = assert_raise(URI::InvalidComponentError) do 810 | uri.password = password 811 | end 812 | refute_match Regexp.new(password), e.message 813 | end 814 | 815 | def test_set_scheme 816 | uri = URI.parse 'HTTP://example' 817 | 818 | assert_equal 'http://example', uri.to_s 819 | end 820 | 821 | def test_hierarchical 822 | hierarchical = URI.parse('http://a.b.c/example') 823 | opaque = URI.parse('mailto:mduerst@ifi.unizh.ch') 824 | 825 | assert hierarchical.hierarchical? 826 | refute opaque.hierarchical? 827 | end 828 | 829 | def test_absolute 830 | abs_uri = URI.parse('http://a.b.c/') 831 | not_abs = URI.parse('a.b.c') 832 | 833 | refute not_abs.absolute? 834 | 835 | assert abs_uri.absolute 836 | assert abs_uri.absolute? 837 | end 838 | 839 | def test_ipv6 840 | assert_equal("[::1]", URI("http://[::1]/bar/baz").host) 841 | assert_equal("::1", URI("http://[::1]/bar/baz").hostname) 842 | 843 | u = URI("http://foo/bar") 844 | assert_equal("http://foo/bar", u.to_s) 845 | u.hostname = "[::1]" 846 | assert_equal("http://[::1]/bar", u.to_s) 847 | u.hostname = "::1" 848 | assert_equal("http://[::1]/bar", u.to_s) 849 | u.hostname = "" 850 | assert_equal("http:///bar", u.to_s) 851 | end 852 | 853 | def test_build 854 | u = URI::Generic.build(['http', nil, 'example.com', 80, nil, '/foo', nil, nil, nil]) 855 | assert_equal('http://example.com:80/foo', u.to_s) 856 | assert_equal(Encoding::UTF_8, u.to_s.encoding) 857 | 858 | u = URI::Generic.build(:port => "5432") 859 | assert_equal(":5432", u.to_s) 860 | assert_equal(5432, u.port) 861 | 862 | u = URI::Generic.build(:scheme => "http", :host => "::1", :path => "/bar/baz") 863 | assert_equal("http://[::1]/bar/baz", u.to_s) 864 | assert_equal("[::1]", u.host) 865 | assert_equal("::1", u.hostname) 866 | 867 | u = URI::Generic.build(:scheme => "http", :host => "[::1]", :path => "/bar/baz") 868 | assert_equal("http://[::1]/bar/baz", u.to_s) 869 | assert_equal("[::1]", u.host) 870 | assert_equal("::1", u.hostname) 871 | end 872 | 873 | def test_build2 874 | u = URI::Generic.build2(path: "/foo bar/baz") 875 | assert_equal('/foo%20bar/baz', u.to_s) 876 | 877 | u = URI::Generic.build2(['http', nil, 'example.com', 80, nil, '/foo bar' , nil, nil, nil]) 878 | assert_equal('http://example.com:80/foo%20bar', u.to_s) 879 | end 880 | 881 | # 192.0.2.0/24 is TEST-NET. [RFC3330] 882 | 883 | def test_find_proxy_bad_uri 884 | assert_raise(URI::BadURIError){ URI("foo").find_proxy } 885 | end 886 | 887 | def test_find_proxy_no_env 888 | with_proxy_env({}) {|env| 889 | assert_nil(URI("http://192.0.2.1/").find_proxy(env)) 890 | assert_nil(URI("ftp://192.0.2.1/").find_proxy(env)) 891 | } 892 | end 893 | 894 | def test_find_proxy 895 | with_proxy_env('http_proxy'=>'http://127.0.0.1:8080') {|env| 896 | assert_equal(URI('http://127.0.0.1:8080'), URI("http://192.0.2.1/").find_proxy(env)) 897 | assert_nil(URI("ftp://192.0.2.1/").find_proxy(env)) 898 | } 899 | with_proxy_env('ftp_proxy'=>'http://127.0.0.1:8080') {|env| 900 | assert_nil(URI("http://192.0.2.1/").find_proxy(env)) 901 | assert_equal(URI('http://127.0.0.1:8080'), URI("ftp://192.0.2.1/").find_proxy(env)) 902 | } 903 | end 904 | 905 | def test_find_proxy_get 906 | with_proxy_env('REQUEST_METHOD'=>'GET') {|env| 907 | assert_nil(URI("http://192.0.2.1/").find_proxy(env)) 908 | } 909 | with_proxy_env('CGI_HTTP_PROXY'=>'http://127.0.0.1:8080', 'REQUEST_METHOD'=>'GET') {|env| 910 | assert_equal(URI('http://127.0.0.1:8080'), URI("http://192.0.2.1/").find_proxy(env)) 911 | } 912 | end 913 | 914 | def test_find_proxy_no_proxy 915 | getaddress = IPSocket.method(:getaddress) 916 | example_address = nil 917 | IPSocket.singleton_class.class_eval do 918 | undef getaddress 919 | define_method(:getaddress) do |host| 920 | case host 921 | when "example.org", "www.example.org" 922 | example_address 923 | when /\A\d+(?:\.\d+){3}\z/ 924 | host 925 | else 926 | raise host 927 | end 928 | end 929 | end 930 | 931 | with_proxy_env('http_proxy'=>'http://127.0.0.1:8080', 'no_proxy'=>'192.0.2.2') {|env| 932 | assert_equal(URI('http://127.0.0.1:8080'), URI("http://192.0.2.1/").find_proxy(env)) 933 | assert_nil(URI("http://192.0.2.2/").find_proxy(env)) 934 | 935 | example_address = "192.0.2.1" 936 | assert_equal(URI('http://127.0.0.1:8080'), URI.parse("http://example.org").find_proxy(env)) 937 | example_address = "192.0.2.2" 938 | assert_nil(URI.parse("http://example.org").find_proxy(env)) 939 | } 940 | with_proxy_env('http_proxy'=>'http://127.0.0.1:8080', 'no_proxy'=>'example.org') {|env| 941 | assert_nil(URI("http://example.org/").find_proxy(env)) 942 | assert_nil(URI("http://www.example.org/").find_proxy(env)) 943 | } 944 | with_proxy_env('http_proxy'=>'http://127.0.0.1:8080', 'no_proxy'=>'.example.org') {|env| 945 | assert_equal(URI('http://127.0.0.1:8080'), URI("http://example.org/").find_proxy(env)) 946 | assert_nil(URI("http://www.example.org/").find_proxy(env)) 947 | } 948 | ensure 949 | IPSocket.singleton_class.class_eval do 950 | undef getaddress 951 | define_method(:getaddress, getaddress) 952 | end 953 | end 954 | 955 | def test_find_proxy_no_proxy_cidr 956 | with_proxy_env('http_proxy'=>'http://127.0.0.1:8080', 'no_proxy'=>'192.0.2.0/24') {|env| 957 | assert_equal(URI('http://127.0.0.1:8080'), URI("http://192.0.1.1/").find_proxy(env)) 958 | assert_nil(URI("http://192.0.2.1/").find_proxy(env)) 959 | assert_nil(URI("http://192.0.2.2/").find_proxy(env)) 960 | } 961 | end 962 | 963 | def test_find_proxy_bad_value 964 | with_proxy_env('http_proxy'=>'') {|env| 965 | assert_nil(URI("http://192.0.2.1/").find_proxy(env)) 966 | assert_nil(URI("ftp://192.0.2.1/").find_proxy(env)) 967 | } 968 | with_proxy_env('ftp_proxy'=>'') {|env| 969 | assert_nil(URI("http://192.0.2.1/").find_proxy(env)) 970 | assert_nil(URI("ftp://192.0.2.1/").find_proxy(env)) 971 | } 972 | end 973 | 974 | def test_find_proxy_case_sensitive_env 975 | with_proxy_env_case_sensitive('http_proxy'=>'http://127.0.0.1:8080', 'REQUEST_METHOD'=>'GET') {|env| 976 | assert_equal(URI('http://127.0.0.1:8080'), URI("http://192.0.2.1/").find_proxy(env)) 977 | } 978 | with_proxy_env_case_sensitive('HTTP_PROXY'=>'http://127.0.0.1:8081', 'REQUEST_METHOD'=>'GET') {|env| 979 | assert_nil(URI("http://192.0.2.1/").find_proxy(env)) 980 | } 981 | with_proxy_env_case_sensitive('http_proxy'=>'http://127.0.0.1:8080', 'HTTP_PROXY'=>'http://127.0.0.1:8081', 'REQUEST_METHOD'=>'GET') {|env| 982 | assert_equal(URI('http://127.0.0.1:8080'), URI("http://192.0.2.1/").find_proxy(env)) 983 | } 984 | end 985 | 986 | def test_use_proxy_p 987 | [ 988 | ['example.com', nil, 80, '', true], 989 | ['example.com', nil, 80, 'example.com:80', false], 990 | ['example.com', nil, 80, 'example.org,example.com:80,example.net', false], 991 | ['foo.example.com', nil, 80, 'example.com', false], 992 | ['foo.example.com', nil, 80, '.example.com', false], 993 | ['example.com', nil, 80, '.example.com', true], 994 | ['xample.com', nil, 80, '.example.com', true], 995 | ['fooexample.com', nil, 80, '.example.com', true], 996 | ['foo.example.com', nil, 80, 'example.com:80', false], 997 | ['foo.eXample.com', nil, 80, 'example.com:80', false], 998 | ['foo.example.com', nil, 80, 'eXample.com:80', false], 999 | ['foo.example.com', nil, 80, 'example.com:443', true], 1000 | ['127.0.0.1', '127.0.0.1', 80, '10.224.0.0/22', true], 1001 | ['10.224.1.1', '10.224.1.1', 80, '10.224.1.1', false], 1002 | ['10.224.1.1', '10.224.1.1', 80, '10.224.0.0/22', false], 1003 | ].each do |hostname, addr, port, no_proxy, expected| 1004 | assert_equal expected, URI::Generic.use_proxy?(hostname, addr, port, no_proxy), 1005 | "use_proxy?('#{hostname}', '#{addr}', #{port}, '#{no_proxy}')" 1006 | end 1007 | end 1008 | 1009 | def test_split 1010 | assert_equal [nil, nil, nil, nil, nil, "", nil, nil, nil], URI.split("//") 1011 | end 1012 | 1013 | class CaseInsensitiveEnv 1014 | def initialize(h={}) 1015 | @h = {} 1016 | h.each {|k, v| self[k] = v } 1017 | end 1018 | 1019 | def []=(k, v) 1020 | if v 1021 | @h[k.downcase] = [k, v.to_s] 1022 | else 1023 | @h.delete [k.downcase] 1024 | end 1025 | v 1026 | end 1027 | 1028 | def [](k) 1029 | k = k.downcase 1030 | @h.has_key?(k) ? @h[k][1] : nil 1031 | end 1032 | 1033 | def length 1034 | @h.length 1035 | end 1036 | 1037 | def include?(k) 1038 | @h.include? k.downcase 1039 | end 1040 | 1041 | def shift 1042 | return nil if @h.empty? 1043 | _kd, (k, v) = @h.shift 1044 | [k, v] 1045 | end 1046 | 1047 | def each 1048 | @h.each {|kd, (k, v)| yield [k, v] } 1049 | end 1050 | 1051 | def reject 1052 | ret = CaseInsensitiveEnv.new 1053 | self.each {|k, v| 1054 | ret[k] = v unless yield [k, v] 1055 | } 1056 | ret 1057 | end 1058 | 1059 | def to_hash 1060 | ret = {} 1061 | self.each {|k, v| 1062 | ret[k] = v 1063 | } 1064 | ret 1065 | end 1066 | end 1067 | 1068 | def with_proxy_real_env(h) 1069 | h = h.dup 1070 | ['http', 'https', 'ftp'].each do |scheme| 1071 | name = "#{scheme}_proxy" 1072 | h[name] ||= nil 1073 | h["CGI_#{name.upcase}"] ||= nil 1074 | end 1075 | begin 1076 | old = {} 1077 | h.each_key {|k| old[k] = ENV[k] } 1078 | h.each {|k, v| ENV[k] = v } 1079 | yield ENV 1080 | ensure 1081 | h.each_key {|k| ENV[k] = old[k] } 1082 | end 1083 | h.reject! {|k, v| v.nil? } 1084 | end 1085 | 1086 | def with_proxy_env(h, &b) 1087 | with_proxy_real_env(h, &b) 1088 | h = h.reject {|k, v| v.nil? } 1089 | yield h 1090 | yield CaseInsensitiveEnv.new(h) 1091 | end 1092 | 1093 | def with_proxy_env_case_sensitive(h, &b) 1094 | with_proxy_real_env(h, &b) unless RUBY_PLATFORM =~ /mswin|mingw/ 1095 | h = h.reject {|k, v| v.nil? } 1096 | yield h 1097 | end 1098 | 1099 | end 1100 | -------------------------------------------------------------------------------- /test/uri/test_http.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'test/unit' 3 | require 'uri/http' 4 | require 'uri/https' 5 | 6 | class URI::TestHTTP < Test::Unit::TestCase 7 | def setup 8 | end 9 | 10 | def teardown 11 | end 12 | 13 | def uri_to_ary(uri) 14 | uri.class.component.collect {|c| uri.send(c)} 15 | end 16 | 17 | def test_build 18 | u = URI::HTTP.build(host: 'www.example.com', path: '/foo/bar') 19 | assert_kind_of(URI::HTTP, u) 20 | end 21 | 22 | def test_parse 23 | u = URI.parse('http://a') 24 | assert_kind_of(URI::HTTP, u) 25 | assert_equal([ 26 | 'http', 27 | nil, 'a', URI::HTTP.default_port, 28 | '', nil, nil 29 | ], uri_to_ary(u)) 30 | end 31 | 32 | def test_normalize 33 | host = 'aBcD' 34 | u1 = URI.parse('http://' + host + '/eFg?HiJ') 35 | u2 = URI.parse('http://' + host.downcase + '/eFg?HiJ') 36 | assert(u1.normalize.host == 'abcd') 37 | assert(u1.normalize.path == u1.path) 38 | assert(u1.normalize == u2.normalize) 39 | assert(!u1.normalize.host.equal?(u1.host)) 40 | assert( u2.normalize.host.equal?(u2.host)) 41 | 42 | assert_equal('http://abc/', URI.parse('http://abc').normalize.to_s) 43 | end 44 | 45 | def test_equal 46 | assert(URI.parse('http://abc') == URI.parse('http://ABC')) 47 | assert(URI.parse('http://abc/def') == URI.parse('http://ABC/def')) 48 | assert(URI.parse('http://abc/def') != URI.parse('http://ABC/DEF')) 49 | end 50 | 51 | def test_request_uri 52 | assert_equal('/', URI.parse('http://a.b.c/').request_uri) 53 | assert_equal('/?abc=def', URI.parse('http://a.b.c/?abc=def').request_uri) 54 | assert_equal('/', URI.parse('http://a.b.c').request_uri) 55 | assert_equal('/?abc=def', URI.parse('http://a.b.c?abc=def').request_uri) 56 | assert_equal(nil, URI.parse('http:foo').request_uri) 57 | end 58 | 59 | def test_select 60 | assert_equal(['http', 'a.b.c', 80], URI.parse('http://a.b.c/').select(:scheme, :host, :port)) 61 | u = URI.parse('http://a.b.c/') 62 | assert_equal(uri_to_ary(u), u.select(*u.component)) 63 | assert_raise(ArgumentError) do 64 | u.select(:scheme, :host, :not_exist, :port) 65 | end 66 | end 67 | 68 | def test_authority 69 | assert_equal('a.b.c', URI.parse('http://a.b.c/').authority) 70 | assert_equal('a.b.c:8081', URI.parse('http://a.b.c:8081/').authority) 71 | assert_equal('a.b.c', URI.parse('http://a.b.c:80/').authority) 72 | end 73 | 74 | 75 | def test_origin 76 | assert_equal('http://a.b.c', URI.parse('http://a.b.c/').origin) 77 | assert_equal('http://a.b.c:8081', URI.parse('http://a.b.c:8081/').origin) 78 | assert_equal('http://a.b.c', URI.parse('http://a.b.c:80/').origin) 79 | assert_equal('https://a.b.c', URI.parse('https://a.b.c/').origin) 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /test/uri/test_ldap.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'test/unit' 3 | require 'uri/ldap' 4 | 5 | class URI::TestLDAP < Test::Unit::TestCase 6 | def setup 7 | end 8 | 9 | def teardown 10 | end 11 | 12 | def uri_to_ary(uri) 13 | uri.class.component.collect {|c| uri.send(c)} 14 | end 15 | 16 | def test_parse 17 | url = 'ldap://ldap.jaist.ac.jp/o=JAIST,c=JP?sn?base?(sn=ttate*)' 18 | u = URI.parse(url) 19 | assert_kind_of(URI::LDAP, u) 20 | assert_equal(url, u.to_s) 21 | assert_equal('o=JAIST,c=JP', u.dn) 22 | assert_equal('sn', u.attributes) 23 | assert_equal('base', u.scope) 24 | assert_equal('(sn=ttate*)', u.filter) 25 | assert_equal(nil, u.extensions) 26 | 27 | u.scope = URI::LDAP::SCOPE_SUB 28 | u.attributes = 'sn,cn,mail' 29 | assert_equal('ldap://ldap.jaist.ac.jp/o=JAIST,c=JP?sn,cn,mail?sub?(sn=ttate*)', u.to_s) 30 | assert_equal('o=JAIST,c=JP', u.dn) 31 | assert_equal('sn,cn,mail', u.attributes) 32 | assert_equal('sub', u.scope) 33 | assert_equal('(sn=ttate*)', u.filter) 34 | assert_equal(nil, u.extensions) 35 | 36 | # from RFC2255, section 6. 37 | { 38 | 'ldap:///o=University%20of%20Michigan,c=US' => 39 | ['ldap', '', URI::LDAP::DEFAULT_PORT, 40 | 'o=University%20of%20Michigan,c=US', 41 | nil, nil, nil, nil], 42 | 43 | 'ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US' => 44 | ['ldap', 'ldap.itd.umich.edu', URI::LDAP::DEFAULT_PORT, 45 | 'o=University%20of%20Michigan,c=US', 46 | nil, nil, nil, nil], 47 | 48 | 'ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US?postalAddress' => 49 | ['ldap', 'ldap.itd.umich.edu', URI::LDAP::DEFAULT_PORT, 50 | 'o=University%20of%20Michigan,c=US', 51 | 'postalAddress', nil, nil, nil], 52 | 53 | 'ldap://host.com:6666/o=University%20of%20Michigan,c=US??sub?(cn=Babs%20Jensen)' => 54 | ['ldap', 'host.com', 6666, 55 | 'o=University%20of%20Michigan,c=US', 56 | nil, 'sub', '(cn=Babs%20Jensen)', nil], 57 | 58 | 'ldap://ldap.itd.umich.edu/c=GB?objectClass?one' => 59 | ['ldap', 'ldap.itd.umich.edu', URI::LDAP::DEFAULT_PORT, 60 | 'c=GB', 61 | 'objectClass', 'one', nil, nil], 62 | 63 | 'ldap://ldap.question.com/o=Question%3f,c=US?mail' => 64 | ['ldap', 'ldap.question.com', URI::LDAP::DEFAULT_PORT, 65 | 'o=Question%3f,c=US', 66 | 'mail', nil, nil, nil], 67 | 68 | 'ldap://ldap.netscape.com/o=Babsco,c=US??(int=%5c00%5c00%5c00%5c04)' => 69 | ['ldap', 'ldap.netscape.com', URI::LDAP::DEFAULT_PORT, 70 | 'o=Babsco,c=US', 71 | nil, '(int=%5c00%5c00%5c00%5c04)', nil, nil], 72 | 73 | 'ldap:///??sub??bindname=cn=Manager%2co=Foo' => 74 | ['ldap', '', URI::LDAP::DEFAULT_PORT, 75 | '', 76 | nil, 'sub', nil, 'bindname=cn=Manager%2co=Foo'], 77 | 78 | 'ldap:///??sub??!bindname=cn=Manager%2co=Foo' => 79 | ['ldap', '', URI::LDAP::DEFAULT_PORT, 80 | '', 81 | nil, 'sub', nil, '!bindname=cn=Manager%2co=Foo'], 82 | }.each do |url2, ary| 83 | u = URI.parse(url2) 84 | assert_equal(ary, uri_to_ary(u)) 85 | end 86 | end 87 | 88 | def test_select 89 | u = URI.parse('ldap:///??sub??!bindname=cn=Manager%2co=Foo') 90 | assert_equal(uri_to_ary(u), u.select(*u.component)) 91 | assert_raise(ArgumentError) do 92 | u.select(:scheme, :host, :not_exist, :port) 93 | end 94 | end 95 | 96 | def test_parse_invalid_uri 97 | assert_raise(URI::InvalidURIError) {URI.parse("ldap:https://example.com")} 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /test/uri/test_mailto.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'test/unit' 3 | require 'uri/mailto' 4 | 5 | class URI::TestMailTo < Test::Unit::TestCase 6 | def setup 7 | @u = URI::MailTo 8 | end 9 | 10 | def teardown 11 | end 12 | 13 | def uri_to_ary(uri) 14 | uri.class.component.collect {|c| uri.send(c)} 15 | end 16 | 17 | def test_build 18 | ok = [] 19 | bad = [] 20 | 21 | # RFC2368, 6. Examples 22 | # mailto:chris@example.com 23 | ok << ["mailto:chris@example.com"] 24 | ok[-1] << ["chris@example.com", nil] 25 | ok[-1] << {:to => "chris@example.com"} 26 | 27 | ok << ["mailto:foo+@example.com,bar@example.com"] 28 | ok[-1] << [["foo+@example.com", "bar@example.com"], nil] 29 | ok[-1] << {:to => "foo+@example.com,bar@example.com"} 30 | 31 | # mailto:infobot@example.com?subject=current-issue 32 | ok << ["mailto:infobot@example.com?subject=current-issue"] 33 | ok[-1] << ["infobot@example.com", ["subject=current-issue"]] 34 | ok[-1] << {:to => "infobot@example.com", 35 | :headers => ["subject=current-issue"]} 36 | 37 | # mailto:infobot@example.com?body=send%20current-issue 38 | ok << ["mailto:infobot@example.com?body=send%20current-issue"] 39 | ok[-1] << ["infobot@example.com", ["body=send%20current-issue"]] 40 | ok[-1] << {:to => "infobot@example.com", 41 | :headers => ["body=send%20current-issue"]} 42 | 43 | # mailto:infobot@example.com?body=send%20current-issue%0D%0Asend%20index 44 | ok << ["mailto:infobot@example.com?body=send%20current-issue%0D%0Asend%20index"] 45 | ok[-1] << ["infobot@example.com", 46 | ["body=send%20current-issue%0D%0Asend%20index"]] 47 | ok[-1] << {:to => "infobot@example.com", 48 | :headers => ["body=send%20current-issue%0D%0Asend%20index"]} 49 | 50 | # mailto:foobar@example.com?In-Reply-To=%3c3469A91.D10AF4C@example.com 51 | ok << ["mailto:foobar@example.com?In-Reply-To=%3c3469A91.D10AF4C@example.com"] 52 | ok[-1] << ["foobar@example.com", 53 | ["In-Reply-To=%3c3469A91.D10AF4C@example.com"]] 54 | ok[-1] << {:to => "foobar@example.com", 55 | :headers => ["In-Reply-To=%3c3469A91.D10AF4C@example.com"]} 56 | 57 | # mailto:majordomo@example.com?body=subscribe%20bamboo-l 58 | ok << ["mailto:majordomo@example.com?body=subscribe%20bamboo-l"] 59 | ok[-1] << ["majordomo@example.com", ["body=subscribe%20bamboo-l"]] 60 | ok[-1] << {:to => "majordomo@example.com", 61 | :headers => ["body=subscribe%20bamboo-l"]} 62 | 63 | # mailto:joe@example.com?cc=bob@example.com&body=hello 64 | ok << ["mailto:joe@example.com?cc=bob@example.com&body=hello"] 65 | ok[-1] << ["joe@example.com", ["cc=bob@example.com", "body=hello"]] 66 | ok[-1] << {:to => "joe@example.com", 67 | :headers => ["cc=bob@example.com", "body=hello"]} 68 | 69 | # mailto:?to=joe@example.com&cc=bob@example.com&body=hello 70 | ok << ["mailto:?to=joe@example.com&cc=bob@example.com&body=hello"] 71 | ok[-1] << [nil, 72 | ["to=joe@example.com", "cc=bob@example.com", "body=hello"]] 73 | ok[-1] << {:headers => ["to=joe@example.com", 74 | "cc=bob@example.com", "body=hello"]} 75 | 76 | # mailto:gorby%25kremvax@example.com 77 | ok << ["mailto:gorby%25kremvax@example.com"] 78 | ok[-1] << ["gorby%25kremvax@example.com", nil] 79 | ok[-1] << {:to => "gorby%25kremvax@example.com"} 80 | 81 | # mailto:unlikely%3Faddress@example.com?blat=foop 82 | ok << ["mailto:unlikely%3Faddress@example.com?blat=foop"] 83 | ok[-1] << ["unlikely%3Faddress@example.com", ["blat=foop"]] 84 | ok[-1] << {:to => "unlikely%3Faddress@example.com", 85 | :headers => ["blat=foop"]} 86 | 87 | # mailto:john@example.com?Subject=Ruby&Cc=jack@example.com 88 | ok << ["mailto:john@example.com?Subject=Ruby&Cc=jack@example.com"] 89 | ok[-1] << ['john@example.com', [['Subject', 'Ruby'], ['Cc', 'jack@example.com']]] 90 | ok[-1] << {:to=>"john@example.com", :headers=>[["Subject", "Ruby"], ["Cc", "jack@example.com"]]} 91 | 92 | # mailto:listman@example.com?subject=subscribe 93 | ok << ["mailto:listman@example.com?subject=subscribe"] 94 | ok[-1] << {:to => 'listman@example.com', :headers => [['subject', 'subscribe']]} 95 | ok[-1] << {:to => 'listman@example.com', :headers => [['subject', 'subscribe']]} 96 | 97 | # mailto:listman@example.com?subject=subscribe 98 | ok << ["mailto:listman@example.com?subject=subscribe"] 99 | ok[-1] << {:to => 'listman@example.com', :headers => { 'subject' => 'subscribe' }} 100 | ok[-1] << {:to => 'listman@example.com', :headers => 'subject=subscribe' } 101 | 102 | ok_all = ok.flatten.join("\0") 103 | 104 | # mailto:joe@example.com?cc=bob@example.com?body=hello ; WRONG! 105 | bad << ["joe@example.com", ["cc=bob@example.com?body=hello"]] 106 | 107 | # mailto:javascript:alert() 108 | bad << ["javascript:alert()", []] 109 | 110 | # mailto:/example.com/ ; WRONG, not a mail address 111 | bad << ["/example.com/", []] 112 | 113 | # '=' which is in hname or hvalue is wrong. 114 | bad << ["foo@example.jp?subject=1+1=2", []] 115 | 116 | ok.each do |x| 117 | assert_equal(x[0], URI.parse(x[0]).to_s) 118 | assert_equal(x[0], @u.build(x[1]).to_s) 119 | assert_equal(x[0], @u.build(x[2]).to_s) 120 | end 121 | 122 | bad.each do |x| 123 | assert_raise(URI::InvalidURIError) { 124 | URI.parse(x) 125 | } 126 | assert_raise(URI::InvalidComponentError) { 127 | @u.build(x) 128 | } 129 | end 130 | 131 | assert_equal(ok_all, ok.flatten.join("\0")) 132 | end 133 | 134 | def test_initializer 135 | assert_raise(URI::InvalidComponentError) do 136 | URI::MailTo.new('mailto', 'sdmitry:bla', 'localhost', '2000', nil, 137 | 'joe@example.com', nil, nil, 'subject=Ruby') 138 | end 139 | end 140 | 141 | def test_check_to 142 | u = URI::MailTo.build(['joe@example.com', 'subject=Ruby']) 143 | 144 | assert_raise(URI::InvalidComponentError) do 145 | u.to = '#1@mail.com' 146 | end 147 | 148 | assert_raise(URI::InvalidComponentError) do 149 | u.to = '@invalid.email' 150 | end 151 | end 152 | 153 | def test_to_s 154 | u = URI::MailTo.build([nil, 'subject=Ruby']) 155 | 156 | u.send(:set_to, nil) 157 | assert_equal('mailto:?subject=Ruby', u.to_s) 158 | 159 | u.fragment = 'test' 160 | assert_equal('mailto:?subject=Ruby#test', u.to_s) 161 | end 162 | 163 | def test_to_mailtext 164 | results = [] 165 | results << ["To: ruby-list@ruby-lang.org\nSubject: subscribe\n\n\n"] 166 | results[-1] << { to: 'ruby-list@ruby-lang.org', headers: { 'subject' => 'subscribe' } } 167 | 168 | results << ["To: ruby-list@ruby-lang.org\n\nBody\n"] 169 | results[-1] << { to: 'ruby-list@ruby-lang.org', headers: { 'body' => 'Body' } } 170 | 171 | results << ["To: ruby-list@ruby-lang.org, cc@ruby-lang.org\n\n\n"] 172 | results[-1] << { to: 'ruby-list@ruby-lang.org', headers: { 'to' => 'cc@ruby-lang.org' } } 173 | 174 | results.each do |expected, params| 175 | u = URI::MailTo.build(params) 176 | assert_equal(expected, u.to_mailtext) 177 | end 178 | 179 | u = URI.parse('mailto:ruby-list@ruby-lang.org?Subject=subscribe&cc=myaddr') 180 | assert_equal "To: ruby-list@ruby-lang.org\nSubject: subscribe\nCc: myaddr\n\n\n", 181 | u.to_mailtext 182 | end 183 | 184 | def test_select 185 | u = URI.parse('mailto:joe@example.com?cc=bob@example.com&body=hello') 186 | assert_equal(uri_to_ary(u), u.select(*u.component)) 187 | assert_raise(ArgumentError) do 188 | u.select(:scheme, :host, :not_exist, :port) 189 | end 190 | end 191 | end 192 | -------------------------------------------------------------------------------- /test/uri/test_parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'test/unit' 3 | require 'uri' 4 | 5 | class URI::TestParser < Test::Unit::TestCase 6 | def uri_to_ary(uri) 7 | uri.class.component.collect {|c| uri.send(c)} 8 | end 9 | 10 | def test_inspect 11 | assert_match(/URI::RFC2396_Parser/, URI::RFC2396_Parser.new.inspect) 12 | assert_match(/URI::RFC3986_Parser/, URI::Parser.new.inspect) 13 | end 14 | 15 | def test_compare 16 | url = 'http://a/b/c/d;p?q' 17 | u0 = URI.parse(url) 18 | u1 = URI.parse(url) 19 | p = URI::Parser.new 20 | u2 = p.parse(url) 21 | u3 = p.parse(url) 22 | 23 | assert(u0 == u1) 24 | assert(u0.eql?(u1)) 25 | assert(!u0.equal?(u1)) 26 | 27 | assert(u1 == u2) 28 | assert(!u1.eql?(u2)) 29 | assert(!u1.equal?(u2)) 30 | 31 | assert(u2 == u3) 32 | assert(u2.eql?(u3)) 33 | assert(!u2.equal?(u3)) 34 | end 35 | 36 | def test_parse_rfc2396_parser 37 | URI.parser = URI::RFC2396_PARSER 38 | 39 | escaped = URI::REGEXP::PATTERN::ESCAPED 40 | hex = URI::REGEXP::PATTERN::HEX 41 | p1 = URI::Parser.new(:ESCAPED => "(?:#{escaped}|%u[#{hex}]{4})") 42 | u1 = p1.parse('http://a/b/%uABCD') 43 | assert_equal(['http', nil, 'a', URI::HTTP.default_port, '/b/%uABCD', nil, nil], 44 | uri_to_ary(u1)) 45 | u1.path = '/%uDCBA' 46 | assert_equal(['http', nil, 'a', URI::HTTP.default_port, '/%uDCBA', nil, nil], 47 | uri_to_ary(u1)) 48 | ensure 49 | URI.parser = URI::DEFAULT_PARSER 50 | end 51 | 52 | def test_parse_query_pct_encoded 53 | assert_equal('q=%32!$&-/?.09;=:@AZ_az~', URI.parse('https://www.example.com/search?q=%32!$&-/?.09;=:@AZ_az~').query) 54 | assert_raise(URI::InvalidURIError) { URI.parse('https://www.example.com/search?q=%XX') } 55 | end 56 | 57 | def test_parse_auth 58 | str = "http://al%40ice:p%40s%25sword@example.com/dir%2Fname/subdir?foo=bar%40example.com" 59 | uri = URI.parse(str) 60 | assert_equal "al%40ice", uri.user 61 | assert_equal "p%40s%25sword", uri.password 62 | assert_equal "al@ice", uri.decoded_user 63 | assert_equal "p@s%sword", uri.decoded_password 64 | end 65 | 66 | def test_raise_bad_uri_for_integer 67 | assert_raise(URI::InvalidURIError) do 68 | URI.parse(1) 69 | end 70 | end 71 | 72 | def test_rfc2822_unescape 73 | p1 = URI::RFC2396_Parser.new 74 | assert_equal("\xe3\x83\x90", p1.unescape("\xe3\x83\x90")) 75 | assert_equal("\xe3\x83\x90", p1.unescape('%e3%83%90')) 76 | assert_equal("\u3042", p1.unescape('%e3%81%82'.force_encoding(Encoding::US_ASCII))) 77 | assert_equal("\xe3\x83\x90\xe3\x83\x90", p1.unescape("\xe3\x83\x90%e3%83%90")) 78 | end 79 | 80 | def test_split 81 | assert_equal(["http", nil, "example.com", nil, nil, "", nil, nil, nil], URI.split("http://example.com")) 82 | assert_equal(["http", nil, "[0::0]", nil, nil, "", nil, nil, nil], URI.split("http://[0::0]")) 83 | assert_equal([nil, nil, "example.com", nil, nil, "", nil, nil, nil], URI.split("//example.com")) 84 | assert_equal([nil, nil, "[0::0]", nil, nil, "", nil, nil, nil], URI.split("//[0::0]")) 85 | 86 | assert_equal(["a", nil, nil, nil, nil, "", nil, nil, nil], URI.split("a:")) 87 | assert_raise(URI::InvalidURIError) do 88 | URI.parse("::") 89 | end 90 | assert_raise(URI::InvalidURIError) do 91 | URI.parse("foo@example:foo") 92 | end 93 | end 94 | 95 | def test_rfc2822_parse_relative_uri 96 | pre = ->(length) { 97 | " " * length + "\0" 98 | } 99 | parser = URI::RFC2396_Parser.new 100 | assert_linear_performance((1..5).map {|i| 10**i}, pre: pre) do |uri| 101 | assert_raise(URI::InvalidURIError) do 102 | parser.split(uri) 103 | end 104 | end 105 | end 106 | 107 | def test_rfc3986_port_check 108 | pre = ->(length) {"\t" * length + "a"} 109 | uri = URI.parse("http://my.example.com") 110 | assert_linear_performance((1..5).map {|i| 10**i}, pre: pre) do |port| 111 | assert_raise(URI::InvalidComponentError) do 112 | uri.port = port 113 | end 114 | end 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /test/uri/test_ws.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'test/unit' 3 | require 'uri/http' 4 | require 'uri/ws' 5 | 6 | class URI::TestWS < Test::Unit::TestCase 7 | def setup 8 | end 9 | 10 | def teardown 11 | end 12 | 13 | def uri_to_ary(uri) 14 | uri.class.component.collect {|c| uri.send(c)} 15 | end 16 | 17 | def test_build 18 | u = URI::WS.build(host: 'www.example.com', path: '/foo/bar') 19 | assert_kind_of(URI::WS, u) 20 | end 21 | 22 | def test_parse 23 | u = URI.parse('ws://a') 24 | assert_kind_of(URI::WS, u) 25 | assert_equal(['ws', 26 | nil, 'a', URI::HTTP.default_port, 27 | '', nil], uri_to_ary(u)) 28 | end 29 | 30 | def test_normalize 31 | host = 'aBcD' 32 | u1 = URI.parse('ws://' + host + '/eFg?HiJ') 33 | u2 = URI.parse('ws://' + host.downcase + '/eFg?HiJ') 34 | assert(u1.normalize.host == 'abcd') 35 | assert(u1.normalize.path == u1.path) 36 | assert(u1.normalize == u2.normalize) 37 | assert(!u1.normalize.host.equal?(u1.host)) 38 | assert( u2.normalize.host.equal?(u2.host)) 39 | 40 | assert_equal('ws://abc/', URI.parse('ws://abc').normalize.to_s) 41 | end 42 | 43 | def test_equal 44 | assert(URI.parse('ws://abc') == URI.parse('ws://ABC')) 45 | assert(URI.parse('ws://abc/def') == URI.parse('ws://ABC/def')) 46 | assert(URI.parse('ws://abc/def') != URI.parse('ws://ABC/DEF')) 47 | end 48 | 49 | def test_request_uri 50 | assert_equal('/', URI.parse('ws://a.b.c/').request_uri) 51 | assert_equal('/?abc=def', URI.parse('ws://a.b.c/?abc=def').request_uri) 52 | assert_equal('/', URI.parse('ws://a.b.c').request_uri) 53 | assert_equal('/?abc=def', URI.parse('ws://a.b.c?abc=def').request_uri) 54 | assert_equal(nil, URI.parse('ws:foo').request_uri) 55 | end 56 | 57 | def test_select 58 | assert_equal(['ws', 'a.b.c', 80], URI.parse('ws://a.b.c/').select(:scheme, :host, :port)) 59 | u = URI.parse('ws://a.b.c/') 60 | assert_equal(uri_to_ary(u), u.select(*u.component)) 61 | assert_raise(ArgumentError) do 62 | u.select(:scheme, :host, :not_exist, :port) 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /test/uri/test_wss.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'test/unit' 3 | require 'uri/https' 4 | require 'uri/wss' 5 | 6 | class URI::TestWSS < Test::Unit::TestCase 7 | def setup 8 | end 9 | 10 | def teardown 11 | end 12 | 13 | def uri_to_ary(uri) 14 | uri.class.component.collect {|c| uri.send(c)} 15 | end 16 | 17 | def test_build 18 | u = URI::WSS.build(host: 'www.example.com', path: '/foo/bar') 19 | assert_kind_of(URI::WSS, u) 20 | end 21 | 22 | def test_parse 23 | u = URI.parse('wss://a') 24 | assert_kind_of(URI::WSS, u) 25 | assert_equal(['wss', 26 | nil, 'a', URI::HTTPS.default_port, 27 | '', nil], uri_to_ary(u)) 28 | end 29 | 30 | def test_normalize 31 | host = 'aBcD' 32 | u1 = URI.parse('wss://' + host + '/eFg?HiJ') 33 | u2 = URI.parse('wss://' + host.downcase + '/eFg?HiJ') 34 | assert(u1.normalize.host == 'abcd') 35 | assert(u1.normalize.path == u1.path) 36 | assert(u1.normalize == u2.normalize) 37 | assert(!u1.normalize.host.equal?(u1.host)) 38 | assert( u2.normalize.host.equal?(u2.host)) 39 | 40 | assert_equal('wss://abc/', URI.parse('wss://abc').normalize.to_s) 41 | end 42 | 43 | def test_equal 44 | assert(URI.parse('wss://abc') == URI.parse('wss://ABC')) 45 | assert(URI.parse('wss://abc/def') == URI.parse('wss://ABC/def')) 46 | assert(URI.parse('wss://abc/def') != URI.parse('wss://ABC/DEF')) 47 | end 48 | 49 | def test_request_uri 50 | assert_equal('/', URI.parse('wss://a.b.c/').request_uri) 51 | assert_equal('/?abc=def', URI.parse('wss://a.b.c/?abc=def').request_uri) 52 | assert_equal('/', URI.parse('wss://a.b.c').request_uri) 53 | assert_equal('/?abc=def', URI.parse('wss://a.b.c?abc=def').request_uri) 54 | assert_equal(nil, URI.parse('wss:foo').request_uri) 55 | end 56 | 57 | def test_select 58 | assert_equal(['wss', 'a.b.c', 443], URI.parse('wss://a.b.c/').select(:scheme, :host, :port)) 59 | u = URI.parse('wss://a.b.c/') 60 | assert_equal(uri_to_ary(u), u.select(*u.component)) 61 | assert_raise(ArgumentError) do 62 | u.select(:scheme, :host, :not_exist, :port) 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /uri.gemspec: -------------------------------------------------------------------------------- 1 | begin 2 | require_relative "lib/uri/version" 3 | rescue LoadError # Fallback to load version file in ruby core repository 4 | require_relative "version" 5 | end 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = "uri" 9 | spec.version = URI::VERSION 10 | spec.authors = ["Akira Yamada"] 11 | spec.email = ["akira@ruby-lang.org"] 12 | 13 | spec.summary = %q{URI is a module providing classes to handle Uniform Resource Identifiers} 14 | spec.description = spec.summary 15 | 16 | github_link = "https://github.com/ruby/uri" 17 | 18 | spec.homepage = github_link 19 | spec.licenses = ["Ruby", "BSD-2-Clause"] 20 | 21 | spec.required_ruby_version = '>= 2.5' 22 | 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 | } 30 | 31 | # Specify which files should be added to the gem when it is released. 32 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 33 | gemspec = File.basename(__FILE__) 34 | spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do 35 | `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject do |file| 36 | (file == gemspec) || file.start_with?(*%w[bin/ test/ rakelib/ .github/ .gitignore Gemfile Rakefile]) 37 | end 38 | end 39 | spec.bindir = "exe" 40 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 41 | spec.require_paths = ["lib"] 42 | end 43 | --------------------------------------------------------------------------------