diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..5ace4600
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 00000000..7b2e23ed
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,42 @@
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ "main" ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ "main" ]
+ schedule:
+ - cron: '41 19 * * 2'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'ruby' ]
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ languages: ${{ matrix.language }}
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v2
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 00000000..b8f1d1d8
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,161 @@
+name: CI
+
+on: [push, pull_request, workflow_dispatch]
+
+env:
+ BUNDLE_JOBS: 4
+
+# jobs defined in the order we want them listed in the Actions UI
+jobs:
+ profile:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ ruby: [2.7]
+ idna_mode: [native, pure]
+ os: [ubuntu-20.04]
+ env:
+ IDNA_MODE: ${{ matrix.idna_mode }}
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Install libidn
+ run: sudo apt-get install libidn11-dev
+
+ - name: Setup ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ bundler-cache: false
+ ruby-version: ${{ matrix.ruby }}
+
+ - name: Install gems
+ run: bundle install
+
+ - name: >-
+ Profile Memory Allocation with ${{ matrix.idna_mode }} IDNA during Addressable::URI#parse
+ run: bundle exec rake profile:memory
+
+ - name: >-
+ Profile Memory Allocation with ${{ matrix.idna_mode }} IDNA during Addressable::Template#match
+ run: bundle exec rake profile:template_match_memory
+
+ coverage:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ ruby: [2.7]
+ os: [ubuntu-20.04]
+ env:
+ BUNDLE_WITHOUT: development
+ COVERALLS_SERVICE_NAME: github
+ COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ COVERALLS_DEBUG: true
+ CI_BUILD_NUMBER: ${{ github.run_id }}
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Install libidn
+ run: sudo apt-get install libidn11-dev
+
+ - name: Setup ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ bundler-cache: false
+ ruby-version: ${{ matrix.ruby }}
+
+ - name: Install gems
+ run: bundle install
+
+ - name: Run specs and report coverage
+ run: bundle exec rake
+
+ test:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # the job name is composed by these attributes
+ ruby:
+ - 2.2
+ - 2.3
+ - 2.4
+ - 2.5
+ - 2.6
+ - 2.7
+ # quotes because of YAML gotcha: https://github.com/actions/runner/issues/849
+ - '3.0'
+ - 3.1
+ - head
+ - jruby-9.1
+ - jruby-9.2
+ - jruby-9.3
+ - truffleruby-21.3
+ - truffleruby-22.1
+ os:
+ - ubuntu-20.04
+ gemfile:
+ - Gemfile
+ include:
+ - { os: ubuntu-20.04, ruby: 2.7, gemfile: gemfiles/public_suffix_2.rb }
+ - { os: ubuntu-20.04, ruby: 2.7, gemfile: gemfiles/public_suffix_3.rb }
+ - { os: ubuntu-20.04, ruby: 2.7, gemfile: gemfiles/public_suffix_4.rb }
+ # Ubuntu
+ - { os: ubuntu-22.04, ruby: 3.1 }
+ # macOS
+ - { os: macos-11, ruby: 3.1 }
+ - { os: macos-12, ruby: 3.1 }
+ # Windows
+ - { os: windows-2019, ruby: 3.1 }
+ - { os: windows-2022, ruby: 3.1 }
+ - { os: windows-2022, ruby: jruby-9.3 }
+ # allowed to fail
+ - { os: ubuntu-20.04, ruby: jruby-head, gemfile: Gemfile, allow-failure: true }
+ - { os: ubuntu-20.04, ruby: truffleruby-head, gemfile: Gemfile, allow-failure: true }
+ env:
+ BUNDLE_GEMFILE: ${{ matrix.gemfile }}
+ BUNDLE_WITHOUT: development:coverage
+ # Workaround for Windows JRuby JDK issue
+ # https://github.com/ruby/setup-ruby/issues/339
+ # https://github.com/jruby/jruby/issues/7182#issuecomment-1112953015
+ JAVA_OPTS: -Djdk.io.File.enableADS=true
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Install libidn (Ubuntu)
+ if: startsWith(matrix.os, 'ubuntu')
+ run: sudo apt-get install libidn11-dev
+
+ - name: Install libidn (macOS)
+ if: startsWith(matrix.os, 'macos')
+ run: brew install libidn
+
+ - name: Setup ruby
+ continue-on-error: ${{ matrix.allow-failure || false }}
+ id: setupruby
+ uses: ruby/setup-ruby@v1
+ with:
+ bundler-cache: false
+ ruby-version: ${{ matrix.ruby }}
+
+ - name: Install gems
+ continue-on-error: ${{ matrix.allow-failure || false }}
+ id: bundle
+ run: bundle install
+
+ - name: Run specs
+ continue-on-error: ${{ matrix.allow-failure || false }}
+ id: specs
+ run: bundle exec rake spec
+
+ # because continue-on-error marks the steps as pass if they fail
+ - name: >-
+ Setup ruby outcome: ${{ steps.setupruby.outcome }}
+ run: echo NOOP
+ - name: >-
+ Install gems outcome: ${{ steps.bundle.outcome }}
+ run: echo NOOP
+ - name: >-
+ Run specs outcome: ${{ steps.specs.outcome }}
+ run: echo NOOP
diff --git a/.gitignore b/.gitignore
index 874e3a0b..b53c0229 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,5 @@ doc
heckling
pkg
specdoc
+tmp/
+vendor/
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 4b280f7e..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-bundler_args: --without development --retry=3 --jobs=3
-cache: bundler
-language: ruby
-rvm:
- - jruby-9.0.5.0
- - jruby-9.1.16.0
- - jruby-9.2.5.0
- - ruby-head
- - ruby-head-clang
- - 2.0.0
- - 2.1.10
- - 2.2.10
- - 2.3.8
- - 2.4.5
- - 2.5.3
- - 2.6.0
-matrix:
- include:
- - gemfile: gemfiles/Gemfile.public_suffix_2
- rvm: 2.5.0
- allow_failures:
- - rvm: ruby-head
- - rvm: ruby-head-clang
- fast_finish: true
-before_install:
- - gem update bundler
-# - apt-get update
-# - apt-get install idn
-sudo: false
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 90531e1f..c15168dd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,38 @@
+# Addressable 2.8.1
+- refactor `Addressable::URI.normalize_path` to address linter offenses ([#430](https://github.com/sporkmonger/addressable/pull/430))
+- remove redundant colon in `Addressable::URI::CharacterClasses::AUTHORITY` regex ([#438](https://github.com/sporkmonger/addressable/pull/438))
+- update gemspec to reflect supported Ruby versions ([#466], [#464], [#463])
+- compatibility w/ public_suffix 5.x ([#466], [#465], [#460])
+- fixes "invalid byte sequence in UTF-8" exception when unencoding URLs containing non UTF-8 characters ([#459](https://github.com/sporkmonger/addressable/pull/459))
+- `Ractor` compatibility ([#449](https://github.com/sporkmonger/addressable/pull/449))
+- use the whole string instead of a single line for template match ([#431](https://github.com/sporkmonger/addressable/pull/431))
+- force UTF-8 encoding only if needed ([#341](https://github.com/sporkmonger/addressable/pull/341))
+
+[#460]: https://github.com/sporkmonger/addressable/pull/460
+[#463]: https://github.com/sporkmonger/addressable/pull/463
+[#464]: https://github.com/sporkmonger/addressable/pull/464
+[#465]: https://github.com/sporkmonger/addressable/pull/465
+[#466]: https://github.com/sporkmonger/addressable/pull/466
+
+# Addressable 2.8.0
+- fixes ReDoS vulnerability in Addressable::Template#match
+- no longer replaces `+` with spaces in queries for non-http(s) schemes
+- fixed encoding ipv6 literals
+- the `:compacted` flag for `normalized_query` now dedupes parameters
+- fix broken `escape_component` alias
+- dropping support for Ruby 2.0 and 2.1
+- adding Ruby 3.0 compatibility for development tasks
+- drop support for `rack-mount` and remove Addressable::Template#generate
+- performance improvements
+- switch CI/CD to GitHub Actions
+
+# Addressable 2.7.0
+- added `:compacted` flag to `normalized_query`
+- `heuristic_parse` handles `mailto:` more intuitively
+- dropped explicit support for JRuby 9.0.5.0
+- compatibility w/ public_suffix 4.x
+- performance improvements
+
# Addressable 2.6.0
- added `tld=` method to allow assignment to the public suffix
- most `heuristic_parse` patterns are now case-insensitive
diff --git a/Gemfile b/Gemfile
index 9ca55cab..0d36ffb5 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,10 +1,17 @@
+# frozen_string_literal: true
+
source 'https://rubygems.org'
gemspec
group :test do
- gem 'rspec', '~> 3.5'
- gem 'rspec-its', '~> 1.1'
+ gem 'rspec', '~> 3.8'
+ gem 'rspec-its', '~> 1.3'
+end
+
+group :coverage do
+ gem "coveralls", "> 0.7", require: false, platforms: :mri
+ gem "simplecov", require: false
end
group :development do
@@ -14,19 +21,10 @@ group :development do
end
group :test, :development do
- gem 'rake', '> 10.0', '< 12'
- gem 'simplecov', :require => false
- gem 'coveralls', :require => false, :platforms => [
- :ruby_20, :ruby_21, :ruby_22, :ruby_23
- ]
- # Used to test compatibility.
- gem 'rack-mount', git: 'https://github.com/sporkmonger/rack-mount.git', require: 'rack/mount'
-
- if RUBY_VERSION.start_with?('2.0', '2.1')
- gem 'rack', '< 2', :require => false
- else
- gem 'rack', :require => false
- end
+ gem 'memory_profiler'
+ gem "rake", ">= 12.3.3"
end
-gem 'idn-ruby', :platform => [:mri_20, :mri_21, :mri_22, :mri_23, :mri_24]
+unless ENV["IDNA_MODE"] == "pure"
+ gem "idn-ruby", platform: :mri
+end
diff --git a/README.md b/README.md
index fa65c288..9892f615 100644
--- a/README.md
+++ b/README.md
@@ -7,21 +7,23 @@
LicenseApache 2.0
-[![Gem Version](http://img.shields.io/gem/dt/addressable.svg)][gem]
-[![Build Status](https://secure.travis-ci.org/sporkmonger/addressable.svg?branch=master)][travis]
+[![Gem Version](https://img.shields.io/gem/dt/addressable.svg)][gem]
+[![Build Status](https://github.com/sporkmonger/addressable/workflows/CI/badge.svg)][actions]
[![Test Coverage Status](https://img.shields.io/coveralls/sporkmonger/addressable.svg)][coveralls]
-[![Documentation Coverage Status](http://inch-ci.org/github/sporkmonger/addressable.svg?branch=master)][inch]
+[![Documentation Coverage Status](https://inch-ci.org/github/sporkmonger/addressable.svg?branch=master)][inch]
[gem]: https://rubygems.org/gems/addressable
-[travis]: http://travis-ci.org/sporkmonger/addressable
+[actions]: https://github.com/sporkmonger/addressable/actions
[coveralls]: https://coveralls.io/r/sporkmonger/addressable
-[inch]: http://inch-ci.org/github/sporkmonger/addressable
+[inch]: https://inch-ci.org/github/sporkmonger/addressable
# Description
-Addressable is a replacement for the URI implementation that is part of
-Ruby's standard library. It more closely conforms to RFC 3986, RFC 3987, and
-RFC 6570 (level 4), providing support for IRIs and URI templates.
+Addressable is an alternative implementation to the URI implementation
+that is part of Ruby's standard library. It is flexible, offers heuristic
+parsing, and additionally provides extensive support for IRIs and URI templates.
+
+Addressable closely conforms to RFC 3986, RFC 3987, and RFC 6570 (level 4).
# Reference
@@ -96,7 +98,7 @@ You may optionally turn on native IDN support by installing libidn and the
idn gem:
```console
-$ sudo apt-get install idn # Debian/Ubuntu
+$ sudo apt-get install libidn11-dev # Debian/Ubuntu
$ brew install libidn # OS X
$ gem install idn-ruby
```
@@ -108,7 +110,7 @@ dependency using a pessimistic version constraint covering the major and minor
values:
```ruby
-spec.add_dependency 'addressable', '~> 2.5'
+spec.add_dependency 'addressable', '~> 2.7'
```
If you need a specific bug fix, you can also specify minimum tiny versions
diff --git a/Rakefile b/Rakefile
index 64b9deac..b7e0ff31 100644
--- a/Rakefile
+++ b/Rakefile
@@ -14,9 +14,9 @@ RELEASE_NAME = "REL #{PKG_VERSION}"
PKG_SUMMARY = "URI Implementation"
PKG_DESCRIPTION = <<-TEXT
-Addressable is a replacement for the URI implementation that is part of
-Ruby's standard library. It more closely conforms to the relevant RFCs and
-adds support for IRIs and URI templates.
+Addressable is an alternative implementation to the URI implementation that is
+part of Ruby's standard library. It is flexible, offers heuristic parsing, and
+additionally provides extensive support for IRIs and URI templates.
TEXT
PKG_FILES = FileList[
diff --git a/addressable.gemspec b/addressable.gemspec
index 83936c1b..d51f4655 100644
--- a/addressable.gemspec
+++ b/addressable.gemspec
@@ -1,37 +1,35 @@
# -*- encoding: utf-8 -*-
-# stub: addressable 2.6.0 ruby lib
+# stub: addressable 2.8.1 ruby lib
Gem::Specification.new do |s|
- s.name = "addressable"
- s.version = "2.6.0"
+ s.name = "addressable".freeze
+ s.version = "2.8.1"
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
- s.require_paths = ["lib"]
- s.authors = ["Bob Aman"]
- s.date = "2019-01-18"
- s.description = "Addressable is a replacement for the URI implementation that is part of\nRuby's standard library. It more closely conforms to the relevant RFCs and\nadds support for IRIs and URI templates.\n"
- s.email = "bob@sporkmonger.com"
- s.extra_rdoc_files = ["README.md"]
- s.files = ["CHANGELOG.md", "Gemfile", "LICENSE.txt", "README.md", "Rakefile", "data/unicode.data", "lib/addressable", "lib/addressable.rb", "lib/addressable/idna", "lib/addressable/idna.rb", "lib/addressable/idna/native.rb", "lib/addressable/idna/pure.rb", "lib/addressable/template.rb", "lib/addressable/uri.rb", "lib/addressable/version.rb", "spec/addressable", "spec/addressable/idna_spec.rb", "spec/addressable/net_http_compat_spec.rb", "spec/addressable/rack_mount_compat_spec.rb", "spec/addressable/security_spec.rb", "spec/addressable/template_spec.rb", "spec/addressable/uri_spec.rb", "spec/spec_helper.rb", "tasks/clobber.rake", "tasks/gem.rake", "tasks/git.rake", "tasks/metrics.rake", "tasks/rspec.rake", "tasks/yard.rake"]
- s.homepage = "https://github.com/sporkmonger/addressable"
- s.licenses = ["Apache-2.0"]
- s.rdoc_options = ["--main", "README.md"]
- s.required_ruby_version = Gem::Requirement.new(">= 2.0")
- s.rubygems_version = "2.5.1"
- s.summary = "URI Implementation"
+ s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
+ s.metadata = { "changelog_uri" => "https://github.com/sporkmonger/addressable/blob/main/CHANGELOG.md" } if s.respond_to? :metadata=
+ s.require_paths = ["lib".freeze]
+ s.authors = ["Bob Aman".freeze]
+ s.date = "2022-08-19"
+ s.description = "Addressable is an alternative implementation to the URI implementation that is\npart of Ruby's standard library. It is flexible, offers heuristic parsing, and\nadditionally provides extensive support for IRIs and URI templates.\n".freeze
+ s.email = "bob@sporkmonger.com".freeze
+ s.extra_rdoc_files = ["README.md".freeze]
+ s.files = ["CHANGELOG.md".freeze, "Gemfile".freeze, "LICENSE.txt".freeze, "README.md".freeze, "Rakefile".freeze, "data/unicode.data".freeze, "lib/addressable".freeze, "lib/addressable.rb".freeze, "lib/addressable/idna".freeze, "lib/addressable/idna.rb".freeze, "lib/addressable/idna/native.rb".freeze, "lib/addressable/idna/pure.rb".freeze, "lib/addressable/template.rb".freeze, "lib/addressable/uri.rb".freeze, "lib/addressable/version.rb".freeze, "spec/addressable".freeze, "spec/addressable/idna_spec.rb".freeze, "spec/addressable/net_http_compat_spec.rb".freeze, "spec/addressable/security_spec.rb".freeze, "spec/addressable/template_spec.rb".freeze, "spec/addressable/uri_spec.rb".freeze, "spec/spec_helper.rb".freeze, "tasks/clobber.rake".freeze, "tasks/gem.rake".freeze, "tasks/git.rake".freeze, "tasks/metrics.rake".freeze, "tasks/profile.rake".freeze, "tasks/rspec.rake".freeze, "tasks/yard.rake".freeze]
+ s.homepage = "https://github.com/sporkmonger/addressable".freeze
+ s.licenses = ["Apache-2.0".freeze]
+ s.rdoc_options = ["--main".freeze, "README.md".freeze]
+ s.required_ruby_version = Gem::Requirement.new(">= 2.2".freeze)
+ s.rubygems_version = "3.3.7".freeze
+ s.summary = "URI Implementation".freeze
if s.respond_to? :specification_version then
s.specification_version = 4
+ end
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
- s.add_runtime_dependency(%q, ["< 4.0", ">= 2.0.2"])
- s.add_development_dependency(%q, ["< 3.0", ">= 1.0"])
- else
- s.add_dependency(%q, ["< 4.0", ">= 2.0.2"])
- s.add_dependency(%q, ["< 3.0", ">= 1.0"])
- end
+ if s.respond_to? :add_runtime_dependency then
+ s.add_runtime_dependency(%q.freeze, [">= 2.0.2", "< 6.0"])
+ s.add_development_dependency(%q.freeze, [">= 1.0", "< 3.0"])
else
- s.add_dependency(%q, ["< 4.0", ">= 2.0.2"])
- s.add_dependency(%q, ["< 3.0", ">= 1.0"])
+ s.add_dependency(%q.freeze, [">= 2.0.2", "< 6.0"])
+ s.add_dependency(%q.freeze, [">= 1.0", "< 3.0"])
end
end
diff --git a/gemfiles/Gemfile.public_suffix_2 b/gemfiles/Gemfile.public_suffix_2
deleted file mode 100644
index 21767ac8..00000000
--- a/gemfiles/Gemfile.public_suffix_2
+++ /dev/null
@@ -1,34 +0,0 @@
-source 'https://rubygems.org'
-
-gemspec path: ".."
-
-gem 'public_suffix', '>= 2.0.2', '~> 2.0'
-
-group :test do
- gem 'rspec', '~> 3.0'
- gem 'rspec-its', '~> 1.1'
-end
-
-group :development do
- gem 'launchy', '~> 2.4', '>= 2.4.3'
- gem 'redcarpet', :platform => :mri_19
- gem 'yard'
-end
-
-group :test, :development do
- gem 'rake', '> 10.0', '< 12'
- gem 'simplecov', :require => false
- gem 'coveralls', :require => false, :platforms => [
- :ruby_20, :ruby_21, :ruby_22, :ruby_23
- ]
- # Used to test compatibility.
- gem 'rack-mount', git: 'https://github.com/sporkmonger/rack-mount.git', require: 'rack/mount'
-
- if RUBY_VERSION.start_with?('2.0', '2.1')
- gem 'rack', '< 2', :require => false
- else
- gem 'rack', :require => false
- end
-end
-
-gem 'idn-ruby', :platform => [:mri_20, :mri_21, :mri_22]
diff --git a/gemfiles/public_suffix_2.rb b/gemfiles/public_suffix_2.rb
new file mode 100644
index 00000000..1db989ab
--- /dev/null
+++ b/gemfiles/public_suffix_2.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+# Assumes this gemfile is used from the project root
+eval_gemfile "../Gemfile"
+
+gem "public_suffix", ">= 2.0.2", "~> 2.0"
diff --git a/gemfiles/public_suffix_3.rb b/gemfiles/public_suffix_3.rb
new file mode 100644
index 00000000..86d07aca
--- /dev/null
+++ b/gemfiles/public_suffix_3.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+# Assumes this gemfile is used from the project root
+eval_gemfile "../Gemfile"
+
+gem "public_suffix", "~> 3.0"
diff --git a/gemfiles/public_suffix_4.rb b/gemfiles/public_suffix_4.rb
new file mode 100644
index 00000000..84936d2c
--- /dev/null
+++ b/gemfiles/public_suffix_4.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+# Assumes this gemfile is used from the project root
+eval_gemfile "../Gemfile"
+
+gem "public_suffix", "~> 4.0"
diff --git a/lib/addressable/idna.rb b/lib/addressable/idna.rb
index e41c1f5d..2dbd3934 100644
--- a/lib/addressable/idna.rb
+++ b/lib/addressable/idna.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-# encoding:utf-8
#--
# Copyright (C) Bob Aman
#
diff --git a/lib/addressable/idna/native.rb b/lib/addressable/idna/native.rb
index 84de8e8c..302e1b0c 100644
--- a/lib/addressable/idna/native.rb
+++ b/lib/addressable/idna/native.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-# encoding:utf-8
#--
# Copyright (C) Bob Aman
#
diff --git a/lib/addressable/idna/pure.rb b/lib/addressable/idna/pure.rb
index 519094da..a7c796e3 100644
--- a/lib/addressable/idna/pure.rb
+++ b/lib/addressable/idna/pure.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-# encoding:utf-8
#--
# Copyright (C) Bob Aman
#
@@ -135,7 +134,7 @@ def self.unicode_downcase(input)
unpacked.map! { |codepoint| lookup_unicode_lowercase(codepoint) }
return unpacked.pack("U*")
end
- (class <= HANGUL_LBASE && ch_one < HANGUL_LBASE + HANGUL_LCOUNT &&
@@ -178,43 +177,45 @@ def self.unicode_compose_pair(ch_one, ch_two)
end
p = []
- ucs4_to_utf8 = lambda do |ch|
- if ch < 128
- p << ch
- elsif ch < 2048
- p << (ch >> 6 | 192)
- p << (ch & 63 | 128)
- elsif ch < 0x10000
- p << (ch >> 12 | 224)
- p << (ch >> 6 & 63 | 128)
- p << (ch & 63 | 128)
- elsif ch < 0x200000
- p << (ch >> 18 | 240)
- p << (ch >> 12 & 63 | 128)
- p << (ch >> 6 & 63 | 128)
- p << (ch & 63 | 128)
- elsif ch < 0x4000000
- p << (ch >> 24 | 248)
- p << (ch >> 18 & 63 | 128)
- p << (ch >> 12 & 63 | 128)
- p << (ch >> 6 & 63 | 128)
- p << (ch & 63 | 128)
- elsif ch < 0x80000000
- p << (ch >> 30 | 252)
- p << (ch >> 24 & 63 | 128)
- p << (ch >> 18 & 63 | 128)
- p << (ch >> 12 & 63 | 128)
- p << (ch >> 6 & 63 | 128)
- p << (ch & 63 | 128)
- end
- end
- ucs4_to_utf8.call(ch_one)
- ucs4_to_utf8.call(ch_two)
+ ucs4_to_utf8(ch_one, p)
+ ucs4_to_utf8(ch_two, p)
return lookup_unicode_composition(p)
end
- (class <> 6 | 192)
+ buffer << (char & 63 | 128)
+ elsif char < 0x10000
+ buffer << (char >> 12 | 224)
+ buffer << (char >> 6 & 63 | 128)
+ buffer << (char & 63 | 128)
+ elsif char < 0x200000
+ buffer << (char >> 18 | 240)
+ buffer << (char >> 12 & 63 | 128)
+ buffer << (char >> 6 & 63 | 128)
+ buffer << (char & 63 | 128)
+ elsif char < 0x4000000
+ buffer << (char >> 24 | 248)
+ buffer << (char >> 18 & 63 | 128)
+ buffer << (char >> 12 & 63 | 128)
+ buffer << (char >> 6 & 63 | 128)
+ buffer << (char & 63 | 128)
+ elsif char < 0x80000000
+ buffer << (char >> 30 | 252)
+ buffer << (char >> 24 & 63 | 128)
+ buffer << (char >> 18 & 63 | 128)
+ buffer << (char >> 12 & 63 | 128)
+ buffer << (char >> 6 & 63 | 128)
+ buffer << (char & 63 | 128)
+ end
+ end
+ private_class_method :ucs4_to_utf8
def self.unicode_sort_canonical(unpacked)
unpacked = unpacked.dup
@@ -238,7 +239,7 @@ def self.unicode_sort_canonical(unpacked)
end
return unpacked
end
- (class <(?:[#{variable_char_class}]|%[a-fA-F0-9][a-fA-F0-9])+)"
RESERVED =
"(?:[#{anything}]|%[a-fA-F0-9][a-fA-F0-9])"
UNRESERVED =
@@ -412,7 +411,7 @@ def extract(uri, processor=nil)
# match.captures
# #=> ["a", ["b", "c"]]
def match(uri, processor=nil)
- uri = Addressable::URI.parse(uri)
+ uri = Addressable::URI.parse(uri) unless uri.is_a?(Addressable::URI)
mapping = {}
# First, we need to process the pattern, and extract the values.
@@ -653,50 +652,16 @@ def named_captures
self.to_regexp.named_captures
end
- ##
- # Generates a route result for a given set of parameters.
- # Should only be used by rack-mount.
- #
- # @param params [Hash] The set of parameters used to expand the template.
- # @param recall [Hash] Default parameters used to expand the template.
- # @param options [Hash] Either a `:processor` or a `:parameterize` block.
- #
- # @api private
- def generate(params={}, recall={}, options={})
- merged = recall.merge(params)
- if options[:processor]
- processor = options[:processor]
- elsif options[:parameterize]
- # TODO: This is sending me into fits trying to shoe-horn this into
- # the existing API. I think I've got this backwards and processors
- # should be a set of 4 optional blocks named :validate, :transform,
- # :match, and :restore. Having to use a singleton here is a huge
- # code smell.
- processor = Object.new
- class <Regexp that parses a template pattern. Memoizes the
+ # value if template processor not set (processors may not be deterministic)
+ #
+ # @param [String] pattern The URI template pattern.
+ # @param [#match] processor The template processor to use.
+ #
+ # @return [Array, Regexp]
+ # An array of expansion variables nad a regular expression which may be
+ # used to parse a template pattern
+ def parse_template_pattern(pattern, processor = nil)
+ if processor.nil? && pattern == @pattern
+ @cached_template_parse ||=
+ parse_new_template_pattern(pattern, processor)
+ else
+ parse_new_template_pattern(pattern, processor)
+ end
+ end
+
##
# Generates the Regexp that parses a template pattern.
#
# @param [String] pattern The URI template pattern.
# @param [#match] processor The template processor to use.
#
- # @return [Regexp]
- # A regular expression which may be used to parse a template pattern.
- def parse_template_pattern(pattern, processor=nil)
+ # @return [Array, Regexp]
+ # An array of expansion variables nad a regular expression which may be
+ # used to parse a template pattern
+ def parse_new_template_pattern(pattern, processor = nil)
# Escape the pattern. The two gsubs restore the escaped curly braces
# back to their original form. Basically, escape everything that isn't
# within an expansion.
@@ -1037,7 +1022,7 @@ def parse_template_pattern(pattern, processor=nil)
end
# Ensure that the regular expression matches the whole URI.
- regexp_string = "^#{regexp_string}$"
+ regexp_string = "\\A#{regexp_string}\\z"
return expansions, Regexp.new(regexp_string)
end
diff --git a/lib/addressable/uri.rb b/lib/addressable/uri.rb
index 9109256c..14b92530 100644
--- a/lib/addressable/uri.rb
+++ b/lib/addressable/uri.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-# encoding:utf-8
#--
# Copyright (C) Bob Aman
#
@@ -38,20 +37,35 @@ class InvalidURIError < StandardError
##
# Container for the character classes specified in
# RFC 3986.
+ #
+ # Note: Concatenated and interpolated `String`s are not affected by the
+ # `frozen_string_literal` directive and must be frozen explicitly.
+ #
+ # Interpolated `String`s *were* frozen this way before Ruby 3.0:
+ # https://bugs.ruby-lang.org/issues/17104
module CharacterClasses
ALPHA = "a-zA-Z"
DIGIT = "0-9"
GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@"
SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\="
- RESERVED = GEN_DELIMS + SUB_DELIMS
- UNRESERVED = ALPHA + DIGIT + "\\-\\.\\_\\~"
- PCHAR = UNRESERVED + SUB_DELIMS + "\\:\\@"
- SCHEME = ALPHA + DIGIT + "\\-\\+\\."
- HOST = UNRESERVED + SUB_DELIMS + "\\[\\:\\]"
- AUTHORITY = PCHAR
- PATH = PCHAR + "\\/"
- QUERY = PCHAR + "\\/\\?"
- FRAGMENT = PCHAR + "\\/\\?"
+ RESERVED = (GEN_DELIMS + SUB_DELIMS).freeze
+ UNRESERVED = (ALPHA + DIGIT + "\\-\\.\\_\\~").freeze
+ PCHAR = (UNRESERVED + SUB_DELIMS + "\\:\\@").freeze
+ SCHEME = (ALPHA + DIGIT + "\\-\\+\\.").freeze
+ HOST = (UNRESERVED + SUB_DELIMS + "\\[\\:\\]").freeze
+ AUTHORITY = (PCHAR + "\\[\\:\\]").freeze
+ PATH = (PCHAR + "\\/").freeze
+ QUERY = (PCHAR + "\\/\\?").freeze
+ FRAGMENT = (PCHAR + "\\/\\?").freeze
+ end
+
+ module NormalizeCharacterClasses
+ HOST = /[^#{CharacterClasses::HOST}]/
+ UNRESERVED = /[^#{CharacterClasses::UNRESERVED}]/
+ PCHAR = /[^#{CharacterClasses::PCHAR}]/
+ SCHEME = /[^#{CharacterClasses::SCHEME}]/
+ FRAGMENT = /[^#{CharacterClasses::FRAGMENT}]/
+ QUERY = %r{[^a-zA-Z0-9\-\.\_\~\!\$\'\(\)\*\+\,\=\:\@\/\?%]|%(?!2B|2b)}
end
SLASH = '/'
@@ -73,7 +87,7 @@ module CharacterClasses
"wais" => 210,
"ldap" => 389,
"prospero" => 1525
- }
+ }.freeze
##
# Returns a URI object based on the parsed string.
@@ -207,7 +221,7 @@ def self.heuristic_parse(uri, hints={})
fragments = match.captures
authority = fragments[3]
if authority && authority.length > 0
- new_authority = authority.gsub(/\\/, '/').gsub(/ /, '%20')
+ new_authority = authority.tr("\\", "/").gsub(" ", "%20")
# NOTE: We want offset 4, not 3!
offset = match.offset(4)
uri = uri.dup
@@ -218,8 +232,9 @@ def self.heuristic_parse(uri, hints={})
parsed = self.parse(hints[:scheme] + "://" + uri)
end
if parsed.path.include?(".")
- new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1]
- if new_host
+ if parsed.path[/\b@\b/]
+ parsed.scheme = "mailto" unless parsed.scheme
+ elsif new_host = parsed.path[/^([^\/]+\.[^\/]*)/, 1]
parsed.defer_validation do
new_path = parsed.path.sub(
Regexp.new("^" + Regexp.escape(new_host)), EMPTY_STR)
@@ -281,15 +296,15 @@ def self.convert_path(path)
uri.path.sub!(/^\/?([a-zA-Z])[\|:][\\\/]/) do
"/#{$1.downcase}:/"
end
- uri.path.gsub!(/\\/, SLASH)
+ uri.path.tr!("\\", SLASH)
if File.exist?(uri.path) &&
File.stat(uri.path).directory?
- uri.path.sub!(/\/$/, EMPTY_STR)
+ uri.path.chomp!(SLASH)
uri.path = uri.path + '/'
end
# If the path is absolute, set the scheme and host.
- if uri.path =~ /^\//
+ if uri.path.start_with?(SLASH)
uri.scheme = "file"
uri.host = EMPTY_STR
end
@@ -326,6 +341,21 @@ def self.join(*uris)
return result
end
+ ##
+ # Tables used to optimize encoding operations in `self.encode_component`
+ # and `self.normalize_component`
+ SEQUENCE_ENCODING_TABLE = Hash.new do |hash, sequence|
+ hash[sequence] = sequence.unpack("C*").map do |c|
+ format("%02x", c)
+ end.join
+ end
+
+ SEQUENCE_UPCASED_PERCENT_ENCODING_TABLE = Hash.new do |hash, sequence|
+ hash[sequence] = sequence.unpack("C*").map do |c|
+ format("%%%02X", c)
+ end.join
+ end
+
##
# Percent encodes a URI component.
#
@@ -392,18 +422,20 @@ def self.encode_component(component, character_class=
component.force_encoding(Encoding::ASCII_8BIT)
# Avoiding gsub! because there are edge cases with frozen strings
component = component.gsub(character_class) do |sequence|
- (sequence.unpack('C*').map { |c| "%" + ("%02x" % c).upcase }).join
+ SEQUENCE_UPCASED_PERCENT_ENCODING_TABLE[sequence]
end
if upcase_encoded.length > 0
- component = component.gsub(/%(#{upcase_encoded.chars.map do |char|
- char.unpack('C*').map { |c| '%02x' % c }.join
- end.join('|')})/i) { |s| s.upcase }
+ upcase_encoded_chars = upcase_encoded.chars.map do |char|
+ SEQUENCE_ENCODING_TABLE[char]
+ end
+ component = component.gsub(/%(#{upcase_encoded_chars.join('|')})/,
+ &:upcase)
end
return component
end
class << self
- alias_method :encode_component, :encode_component
+ alias_method :escape_component, :encode_component
end
##
@@ -442,15 +474,13 @@ def self.unencode(uri, return_type=String, leave_encoded='')
"Expected Class (String or Addressable::URI), " +
"got #{return_type.inspect}"
end
- uri = uri.dup
- # Seriously, only use UTF-8. I'm really not kidding!
- uri.force_encoding("utf-8")
- leave_encoded = leave_encoded.dup.force_encoding("utf-8")
- result = uri.gsub(/%[0-9a-f]{2}/iu) do |sequence|
+
+ result = uri.gsub(/%[0-9a-f]{2}/i) do |sequence|
c = sequence[1..3].to_i(16).chr
- c.force_encoding("utf-8")
+ c.force_encoding(sequence.encoding)
leave_encoded.include?(c) ? sequence : c
end
+
result.force_encoding("utf-8")
if return_type == String
return result
@@ -530,13 +560,17 @@ def self.normalize_component(component, character_class=
leave_re = if leave_encoded.length > 0
character_class = "#{character_class}%" unless character_class.include?('%')
- "|%(?!#{leave_encoded.chars.map do |char|
- seq = char.unpack('C*').map { |c| '%02x' % c }.join
+ "|%(?!#{leave_encoded.chars.flat_map do |char|
+ seq = SEQUENCE_ENCODING_TABLE[char]
[seq.upcase, seq.downcase]
- end.flatten.join('|')})"
+ end.join('|')})"
end
- character_class = /[^#{character_class}]#{leave_re}/
+ character_class = if leave_re
+ /[^#{character_class}]#{leave_re}/
+ else
+ /[^#{character_class}]/
+ end
end
# We can't perform regexps on invalid UTF sequences, but
# here we need to, so switch to ASCII.
@@ -860,12 +894,12 @@ def normalized_scheme
else
Addressable::URI.normalize_component(
self.scheme.strip.downcase,
- Addressable::URI::CharacterClasses::SCHEME
+ Addressable::URI::NormalizeCharacterClasses::SCHEME
)
end
end
# All normalized values should be UTF-8
- @normalized_scheme.force_encoding(Encoding::UTF_8) if @normalized_scheme
+ force_utf8_encoding_if_needed(@normalized_scheme)
@normalized_scheme
end
@@ -880,7 +914,7 @@ def scheme=(new_scheme)
new_scheme = new_scheme.to_str
end
if new_scheme && new_scheme !~ /\A[a-z][a-z0-9\.\+\-]*\z/i
- raise InvalidURIError, "Invalid scheme format: #{new_scheme}"
+ raise InvalidURIError, "Invalid scheme format: '#{new_scheme}'"
end
@scheme = new_scheme
@scheme = nil if @scheme.to_s.strip.empty?
@@ -915,12 +949,12 @@ def normalized_user
else
Addressable::URI.normalize_component(
self.user.strip,
- Addressable::URI::CharacterClasses::UNRESERVED
+ Addressable::URI::NormalizeCharacterClasses::UNRESERVED
)
end
end
# All normalized values should be UTF-8
- @normalized_user.force_encoding(Encoding::UTF_8) if @normalized_user
+ force_utf8_encoding_if_needed(@normalized_user)
@normalized_user
end
@@ -972,14 +1006,12 @@ def normalized_password
else
Addressable::URI.normalize_component(
self.password.strip,
- Addressable::URI::CharacterClasses::UNRESERVED
+ Addressable::URI::NormalizeCharacterClasses::UNRESERVED
)
end
end
# All normalized values should be UTF-8
- if @normalized_password
- @normalized_password.force_encoding(Encoding::UTF_8)
- end
+ force_utf8_encoding_if_needed(@normalized_password)
@normalized_password
end
@@ -1047,9 +1079,7 @@ def normalized_userinfo
end
end
# All normalized values should be UTF-8
- if @normalized_userinfo
- @normalized_userinfo.force_encoding(Encoding::UTF_8)
- end
+ force_utf8_encoding_if_needed(@normalized_userinfo)
@normalized_userinfo
end
@@ -1096,6 +1126,7 @@ def host
# @return [String] The host component, normalized.
def normalized_host
return nil unless self.host
+
@normalized_host ||= begin
if !self.host.strip.empty?
result = ::Addressable::IDNA.to_ascii(
@@ -1107,14 +1138,15 @@ def normalized_host
end
result = Addressable::URI.normalize_component(
result,
- CharacterClasses::HOST)
+ NormalizeCharacterClasses::HOST
+ )
result
else
EMPTY_STR.dup
end
end
# All normalized values should be UTF-8
- @normalized_host.force_encoding(Encoding::UTF_8) if @normalized_host
+ force_utf8_encoding_if_needed(@normalized_host)
@normalized_host
end
@@ -1172,7 +1204,7 @@ def hostname=(new_hostname)
# Returns the top-level domain for this host.
#
# @example
- # Addressable::URI.parse("www.example.co.uk").tld # => "co.uk"
+ # Addressable::URI.parse("http://www.example.co.uk").tld # => "co.uk"
def tld
PublicSuffix.parse(self.host, ignore_private: true).tld
end
@@ -1182,7 +1214,7 @@ def tld
#
# @param [String, #to_str] new_tld The new top-level domain.
def tld=(new_tld)
- replaced_tld = domain.sub(/#{tld}\z/, new_tld)
+ replaced_tld = host.sub(/#{tld}\z/, new_tld)
self.host = PublicSuffix::Domain.new(replaced_tld).to_s
end
@@ -1190,7 +1222,7 @@ def tld=(new_tld)
# Returns the public suffix domain for this host.
#
# @example
- # Addressable::URI.parse("www.example.co.uk").domain # => "example.co.uk"
+ # Addressable::URI.parse("http://www.example.co.uk").domain # => "example.co.uk"
def domain
PublicSuffix.domain(self.host, ignore_private: true)
end
@@ -1232,9 +1264,7 @@ def normalized_authority
authority
end
# All normalized values should be UTF-8
- if @normalized_authority
- @normalized_authority.force_encoding(Encoding::UTF_8)
- end
+ force_utf8_encoding_if_needed(@normalized_authority)
@normalized_authority
end
@@ -1468,7 +1498,7 @@ def normalized_site
site_string
end
# All normalized values should be UTF-8
- @normalized_site.force_encoding(Encoding::UTF_8) if @normalized_site
+ force_utf8_encoding_if_needed(@normalized_site)
@normalized_site
end
@@ -1519,7 +1549,7 @@ def normalized_path
result = path.strip.split(SLASH, -1).map do |segment|
Addressable::URI.normalize_component(
segment,
- Addressable::URI::CharacterClasses::PCHAR
+ Addressable::URI::NormalizeCharacterClasses::PCHAR
)
end.join(SLASH)
@@ -1531,7 +1561,7 @@ def normalized_path
result
end
# All normalized values should be UTF-8
- @normalized_path.force_encoding(Encoding::UTF_8) if @normalized_path
+ force_utf8_encoding_if_needed(@normalized_path)
@normalized_path
end
@@ -1594,15 +1624,20 @@ def normalized_query(*flags)
modified_query_class = Addressable::URI::CharacterClasses::QUERY.dup
# Make sure possible key-value pair delimiters are escaped.
modified_query_class.sub!("\\&", "").sub!("\\;", "")
- pairs = (self.query || "").split("&", -1)
+ pairs = (query || "").split("&", -1)
+ pairs.delete_if(&:empty?).uniq! if flags.include?(:compacted)
pairs.sort! if flags.include?(:sorted)
component = pairs.map do |pair|
- Addressable::URI.normalize_component(pair, modified_query_class, "+")
+ Addressable::URI.normalize_component(
+ pair,
+ Addressable::URI::NormalizeCharacterClasses::QUERY,
+ "+"
+ )
end.join("&")
component == "" ? nil : component
end
# All normalized values should be UTF-8
- @normalized_query.force_encoding(Encoding::UTF_8) if @normalized_query
+ force_utf8_encoding_if_needed(@normalized_query)
@normalized_query
end
@@ -1656,11 +1691,13 @@ def query_values(return_type=Hash)
# so it's best to make all changes in-place.
pair[0] = URI.unencode_component(pair[0])
if pair[1].respond_to?(:to_str)
+ value = pair[1].to_str
# I loathe the fact that I have to do this. Stupid HTML 4.01.
# Treating '+' as a space was just an unbelievably bad idea.
# There was nothing wrong with '%20'!
# If it ain't broke, don't fix it!
- pair[1] = URI.unencode_component(pair[1].to_str.gsub(/\+/, " "))
+ value = value.tr("+", " ") if ["http", "https", nil].include?(scheme)
+ pair[1] = URI.unencode_component(value)
end
if return_type == Hash
accu[pair[0]] = pair[1]
@@ -1791,14 +1828,12 @@ def normalized_fragment
@normalized_fragment ||= begin
component = Addressable::URI.normalize_component(
self.fragment,
- Addressable::URI::CharacterClasses::FRAGMENT
+ Addressable::URI::NormalizeCharacterClasses::FRAGMENT
)
component == "" ? nil : component
end
# All normalized values should be UTF-8
- if @normalized_fragment
- @normalized_fragment.force_encoding(Encoding::UTF_8)
- end
+ force_utf8_encoding_if_needed(@normalized_fragment)
@normalized_fragment
end
@@ -1917,7 +1952,7 @@ def join(uri)
# Section 5.2.3 of RFC 3986
#
# Removes the right-most path segment from the base path.
- if base_path =~ /\//
+ if base_path.include?(SLASH)
base_path.sub!(/\/[^\/]+$/, SLASH)
else
base_path = EMPTY_STR
@@ -2367,10 +2402,10 @@ def inspect
#
# @param [Proc] block
# A set of operations to perform on a given URI.
- def defer_validation(&block)
- raise LocalJumpError, "No block given." unless block
+ def defer_validation
+ raise LocalJumpError, "No block given." unless block_given?
@validation_deferred = true
- block.call()
+ yield
@validation_deferred = false
validate
return nil
@@ -2394,30 +2429,35 @@ def defer_validation(&block)
def self.normalize_path(path)
# Section 5.2.4 of RFC 3986
- return nil if path.nil?
+ return if path.nil?
normalized_path = path.dup
- begin
- mod = nil
+ loop do
mod ||= normalized_path.gsub!(RULE_2A, SLASH)
pair = normalized_path.match(RULE_2B_2C)
- parent, current = pair[1], pair[2] if pair
+ if pair
+ parent = pair[1]
+ current = pair[2]
+ else
+ parent = nil
+ current = nil
+ end
+
+ regexp = "/#{Regexp.escape(parent.to_s)}/\\.\\./|"
+ regexp += "(/#{Regexp.escape(current.to_s)}/\\.\\.$)"
+
if pair && ((parent != SELF_REF && parent != PARENT) ||
(current != SELF_REF && current != PARENT))
- mod ||= normalized_path.gsub!(
- Regexp.new(
- "/#{Regexp.escape(parent.to_s)}/\\.\\./|" +
- "(/#{Regexp.escape(current.to_s)}/\\.\\.$)"
- ), SLASH
- )
+ mod ||= normalized_path.gsub!(Regexp.new(regexp), SLASH)
end
mod ||= normalized_path.gsub!(RULE_2D, EMPTY_STR)
# Non-standard, removes prefixed dotted segments from path.
mod ||= normalized_path.gsub!(RULE_PREFIXED_PARENT, SLASH)
- end until mod.nil?
+ break if mod.nil?
+ end
- return normalized_path
+ normalized_path
end
##
@@ -2506,5 +2546,15 @@ def remove_composite_values
remove_instance_variable(:@uri_string) if defined?(@uri_string)
remove_instance_variable(:@hash) if defined?(@hash)
end
+
+ ##
+ # Converts the string to be UTF-8 if it is not already UTF-8
+ #
+ # @api private
+ def force_utf8_encoding_if_needed(str)
+ if str && str.encoding != Encoding::UTF_8
+ str.force_encoding(Encoding::UTF_8)
+ end
+ end
end
end
diff --git a/lib/addressable/version.rb b/lib/addressable/version.rb
index 445d2823..d8e1644b 100644
--- a/lib/addressable/version.rb
+++ b/lib/addressable/version.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-# encoding:utf-8
#--
# Copyright (C) Bob Aman
#
@@ -23,8 +22,8 @@
module Addressable
module VERSION
MAJOR = 2
- MINOR = 6
- TINY = 0
+ MINOR = 8
+ TINY = 1
STRING = [MAJOR, MINOR, TINY].join('.')
end
diff --git a/spec/addressable/idna_spec.rb b/spec/addressable/idna_spec.rb
index 651f75af..b1509d22 100644
--- a/spec/addressable/idna_spec.rb
+++ b/spec/addressable/idna_spec.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-# coding: utf-8
# Copyright (C) Bob Aman
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -294,7 +293,9 @@
it_should_behave_like "converting from unicode to ASCII"
it_should_behave_like "converting from ASCII to unicode"
end
-rescue LoadError
+rescue LoadError => error
+ raise error if ENV["CI"] && TestHelper.native_supported?
+
# Cannot test the native implementation without libidn support.
warn('Could not load native IDN implementation.')
end
diff --git a/spec/addressable/net_http_compat_spec.rb b/spec/addressable/net_http_compat_spec.rb
index 8663a867..d07a43e5 100644
--- a/spec/addressable/net_http_compat_spec.rb
+++ b/spec/addressable/net_http_compat_spec.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-# coding: utf-8
# Copyright (C) Bob Aman
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/spec/addressable/rack_mount_compat_spec.rb b/spec/addressable/rack_mount_compat_spec.rb
deleted file mode 100644
index 7b02cb76..00000000
--- a/spec/addressable/rack_mount_compat_spec.rb
+++ /dev/null
@@ -1,106 +0,0 @@
-# frozen_string_literal: true
-
-# coding: utf-8
-# Copyright (C) Bob Aman
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-require "spec_helper"
-
-require "addressable/uri"
-require "addressable/template"
-require "rack/mount"
-
-describe Rack::Mount do
- let(:app_one) do
- proc { |env| [200, {'Content-Type' => 'text/plain'}, 'Route 1'] }
- end
- let(:app_two) do
- proc { |env| [200, {'Content-Type' => 'text/plain'}, 'Route 2'] }
- end
- let(:app_three) do
- proc { |env| [200, {'Content-Type' => 'text/plain'}, 'Route 3'] }
- end
- let(:routes) do
- s = Rack::Mount::RouteSet.new do |set|
- set.add_route(app_one, {
- :request_method => 'GET',
- :path_info => Addressable::Template.new('/one/{id}/')
- }, {:id => 'unidentified'}, :one)
- set.add_route(app_two, {
- :request_method => 'GET',
- :path_info => Addressable::Template.new('/two/')
- }, {:id => 'unidentified'}, :two)
- set.add_route(app_three, {
- :request_method => 'GET',
- :path_info => Addressable::Template.new('/three/{id}/').to_regexp
- }, {:id => 'unidentified'}, :three)
- end
- s.rehash
- s
- end
-
- it "should generate from routes with Addressable::Template" do
- path, _ = routes.generate(:path_info, :one, {:id => '123'})
- expect(path).to eq '/one/123/'
- end
-
- it "should generate from routes with Addressable::Template using defaults" do
- path, _ = routes.generate(:path_info, :one, {})
- expect(path).to eq '/one/unidentified/'
- end
-
- it "should recognize routes with Addressable::Template" do
- request = Rack::Request.new(
- 'REQUEST_METHOD' => 'GET',
- 'PATH_INFO' => '/one/123/'
- )
- route, _, params = routes.recognize(request)
- expect(route).not_to be_nil
- expect(route.app).to eq app_one
- expect(params).to eq({id: '123'})
- end
-
- it "should generate from routes with Addressable::Template" do
- path, _ = routes.generate(:path_info, :two, {:id => '654'})
- expect(path).to eq '/two/'
- end
-
- it "should generate from routes with Addressable::Template using defaults" do
- path, _ = routes.generate(:path_info, :two, {})
- expect(path).to eq '/two/'
- end
-
- it "should recognize routes with Addressable::Template" do
- request = Rack::Request.new(
- 'REQUEST_METHOD' => 'GET',
- 'PATH_INFO' => '/two/'
- )
- route, _, params = routes.recognize(request)
- expect(route).not_to be_nil
- expect(route.app).to eq app_two
- expect(params).to eq({id: 'unidentified'})
- end
-
- it "should recognize routes with derived Regexp" do
- request = Rack::Request.new(
- 'REQUEST_METHOD' => 'GET',
- 'PATH_INFO' => '/three/789/'
- )
- route, _, params = routes.recognize(request)
- expect(route).not_to be_nil
- expect(route.app).to eq app_three
- expect(params).to eq({id: '789'})
- end
-end
diff --git a/spec/addressable/security_spec.rb b/spec/addressable/security_spec.rb
index 601e8088..3bf90a20 100644
--- a/spec/addressable/security_spec.rb
+++ b/spec/addressable/security_spec.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-# coding: utf-8
# Copyright (C) Bob Aman
#
# Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/spec/addressable/template_spec.rb b/spec/addressable/template_spec.rb
index a0191652..f7b0994c 100644
--- a/spec/addressable/template_spec.rb
+++ b/spec/addressable/template_spec.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-# coding: utf-8
# Copyright (C) Bob Aman
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -19,6 +18,7 @@
require "spec_helper"
require "bigdecimal"
+require "timeout"
require "addressable/template"
shared_examples_for 'expands' do |tests|
@@ -77,6 +77,15 @@
end
end
+describe "#to_regexp" do
+ it "does not match the first line of multiline strings" do
+ uri = "https://www.example.com/bar"
+ template = Addressable::Template.new(uri)
+ expect(template.match(uri)).not_to be_nil
+ expect(template.match("#{uri}\ngarbage")).to be_nil
+ end
+end
+
describe "Type conversion" do
subject {
{
@@ -1340,6 +1349,14 @@ def self.match(name)
expect(subject).not_to match("foo_bar*")
expect(subject).not_to match("foo_bar:20")
end
+
+ it 'should parse in a reasonable time' do
+ expect do
+ Timeout.timeout(0.1) do
+ expect(subject).not_to match("0"*25 + "!")
+ end
+ end.not_to raise_error
+ end
end
context "VARIABLE_LIST" do
subject { Addressable::Template::VARIABLE_LIST }
diff --git a/spec/addressable/uri_spec.rb b/spec/addressable/uri_spec.rb
index 7cecd0c9..b1f95417 100644
--- a/spec/addressable/uri_spec.rb
+++ b/spec/addressable/uri_spec.rb
@@ -1,6 +1,5 @@
# frozen_string_literal: true
-# coding: utf-8
# Copyright (C) Bob Aman
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -65,116 +64,116 @@ def to_s
describe Addressable::URI, "when created with a non-numeric port number" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(:port => "bogus")
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
end
describe Addressable::URI, "when created with a invalid encoded port number" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(:port => "%eb")
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
end
describe Addressable::URI, "when created with a non-string scheme" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(:scheme => :bogus)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
end
describe Addressable::URI, "when created with a non-string user" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(:user => :bogus)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
end
describe Addressable::URI, "when created with a non-string password" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(:password => :bogus)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
end
describe Addressable::URI, "when created with a non-string userinfo" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(:userinfo => :bogus)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
end
describe Addressable::URI, "when created with a non-string host" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(:host => :bogus)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
end
describe Addressable::URI, "when created with a non-string authority" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(:authority => :bogus)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
end
describe Addressable::URI, "when created with a non-string path" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(:path => :bogus)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
end
describe Addressable::URI, "when created with a non-string query" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(:query => :bogus)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
end
describe Addressable::URI, "when created with a non-string fragment" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(:fragment => :bogus)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
end
describe Addressable::URI, "when created with a scheme but no hierarchical " +
"segment" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.parse("http:")
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
end
describe Addressable::URI, "quote handling" do
describe 'in host name' do
it "should raise an error for single quote" do
- expect(lambda do
+ expect do
Addressable::URI.parse("http://local\"host/")
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
end
end
describe Addressable::URI, "newline normalization" do
it "should not accept newlines in scheme" do
- expect(lambda do
+ expect do
Addressable::URI.parse("ht%0atp://localhost/")
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
it "should not unescape newline in path" do
@@ -199,47 +198,47 @@ def to_s
it "should not accept newline in hostname" do
uri = Addressable::URI.parse("http://localhost/")
- expect(lambda do
+ expect do
uri.host = "local\nhost"
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
end
describe Addressable::URI, "when created with ambiguous path" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.parse("::http")
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
end
describe Addressable::URI, "when created with an invalid host" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(:host => "")
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
end
describe Addressable::URI, "when created with a host consisting of " +
"sub-delims characters" do
it "should not raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(
:host => Addressable::URI::CharacterClasses::SUB_DELIMS.gsub(/\\/, '')
)
- end).not_to raise_error
+ end.not_to raise_error
end
end
describe Addressable::URI, "when created with a host consisting of " +
"unreserved characters" do
it "should not raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(
:host => Addressable::URI::CharacterClasses::UNRESERVED.gsub(/\\/, '')
)
- end).not_to raise_error
+ end.not_to raise_error
end
end
@@ -269,83 +268,83 @@ def to_s
end
it "should raise an error if the scheme is set to whitespace" do
- expect(lambda do
+ expect do
@uri.scheme = "\t \n"
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError, /'\t \n'/)
end
it "should raise an error if the scheme is set to all digits" do
- expect(lambda do
+ expect do
@uri.scheme = "123"
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError, /'123'/)
end
it "should raise an error if the scheme begins with a digit" do
- expect(lambda do
+ expect do
@uri.scheme = "1scheme"
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError, /'1scheme'/)
end
it "should raise an error if the scheme begins with a plus" do
- expect(lambda do
+ expect do
@uri.scheme = "+scheme"
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError, /'\+scheme'/)
end
it "should raise an error if the scheme begins with a dot" do
- expect(lambda do
+ expect do
@uri.scheme = ".scheme"
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError, /'\.scheme'/)
end
it "should raise an error if the scheme begins with a dash" do
- expect(lambda do
+ expect do
@uri.scheme = "-scheme"
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError, /'-scheme'/)
end
it "should raise an error if the scheme contains an illegal character" do
- expect(lambda do
+ expect do
@uri.scheme = "scheme!"
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError, /'scheme!'/)
end
it "should raise an error if the scheme contains whitespace" do
- expect(lambda do
+ expect do
@uri.scheme = "sch eme"
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError, /'sch eme'/)
end
it "should raise an error if the scheme contains a newline" do
- expect(lambda do
+ expect do
@uri.scheme = "sch\neme"
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
it "should raise an error if set into an invalid state" do
- expect(lambda do
+ expect do
@uri.user = "user"
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
it "should raise an error if set into an invalid state" do
- expect(lambda do
+ expect do
@uri.password = "pass"
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
it "should raise an error if set into an invalid state" do
- expect(lambda do
+ expect do
@uri.scheme = "http"
@uri.fragment = "fragment"
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
it "should raise an error if set into an invalid state" do
- expect(lambda do
+ expect do
@uri.fragment = "fragment"
@uri.scheme = "http"
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
end
@@ -999,6 +998,72 @@ def to_s
end
end
+describe Addressable::URI, "when normalized and then deeply frozen" do
+ before do
+ @uri = Addressable::URI.parse(
+ "http://user:password@example.com:8080/path?query=value#fragment"
+ ).normalize!
+
+ @uri.instance_variables.each do |var|
+ @uri.instance_variable_set(var, @uri.instance_variable_get(var).freeze)
+ end
+
+ @uri.freeze
+ end
+
+ it "#normalized_scheme should not error" do
+ expect { @uri.normalized_scheme }.not_to raise_error
+ end
+
+ it "#normalized_user should not error" do
+ expect { @uri.normalized_user }.not_to raise_error
+ end
+
+ it "#normalized_password should not error" do
+ expect { @uri.normalized_password }.not_to raise_error
+ end
+
+ it "#normalized_userinfo should not error" do
+ expect { @uri.normalized_userinfo }.not_to raise_error
+ end
+
+ it "#normalized_host should not error" do
+ expect { @uri.normalized_host }.not_to raise_error
+ end
+
+ it "#normalized_authority should not error" do
+ expect { @uri.normalized_authority }.not_to raise_error
+ end
+
+ it "#normalized_port should not error" do
+ expect { @uri.normalized_port }.not_to raise_error
+ end
+
+ it "#normalized_site should not error" do
+ expect { @uri.normalized_site }.not_to raise_error
+ end
+
+ it "#normalized_path should not error" do
+ expect { @uri.normalized_path }.not_to raise_error
+ end
+
+ it "#normalized_query should not error" do
+ expect { @uri.normalized_query }.not_to raise_error
+ end
+
+ it "#normalized_fragment should not error" do
+ expect { @uri.normalized_fragment }.not_to raise_error
+ end
+
+ it "should be frozen" do
+ expect(@uri).to be_frozen
+ end
+
+ it "should not allow destructive operations" do
+ expect { @uri.normalize! }.to raise_error(RuntimeError)
+ end
+end
+
describe Addressable::URI, "when created from string components" do
before do
@uri = Addressable::URI.new(
@@ -1015,31 +1080,31 @@ def to_s
end
it "should raise an error if invalid components omitted" do
- expect(lambda do
+ expect do
@uri.omit(:bogus)
- end).to raise_error(ArgumentError)
- expect(lambda do
+ end.to raise_error(ArgumentError)
+ expect do
@uri.omit(:scheme, :bogus, :path)
- end).to raise_error(ArgumentError)
+ end.to raise_error(ArgumentError)
end
end
describe Addressable::URI, "when created with a nil host but " +
"non-nil authority components" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(:user => "user", :password => "pass", :port => 80)
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
end
describe Addressable::URI, "when created with both an authority and a user" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(
:user => "user", :authority => "user@example.com:80"
)
- end).to raise_error(ArgumentError)
+ end.to raise_error(ArgumentError)
end
end
@@ -1077,33 +1142,33 @@ def to_s
describe Addressable::URI, "when created with a host with a backslash" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(:authority => "example\\example")
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
end
describe Addressable::URI, "when created with a host with a slash" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(:authority => "example/example")
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
end
describe Addressable::URI, "when created with a host with a space" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(:authority => "example example")
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
end
describe Addressable::URI, "when created with both a userinfo and a user" do
it "should raise an error" do
- expect(lambda do
+ expect do
Addressable::URI.new(:user => "user", :userinfo => "user:pass")
- end).to raise_error(ArgumentError)
+ end.to raise_error(ArgumentError)
end
end
@@ -1195,18 +1260,18 @@ def to_s
"like a URI object" do
it "should parse without error" do
uri = Addressable::URI.parse(Fake::URI::HTTP.new("http://example.com/"))
- expect(lambda do
+ expect do
Addressable::URI.parse(uri)
- end).not_to raise_error
+ end.not_to raise_error
end
end
describe Addressable::URI, "when parsed from a standard library URI object" do
it "should parse without error" do
uri = Addressable::URI.parse(URI.parse("http://example.com/"))
- expect(lambda do
+ expect do
Addressable::URI.parse(uri)
- end).not_to raise_error
+ end.not_to raise_error
end
end
@@ -1366,9 +1431,9 @@ def to_s
end
it "should not allow request URI assignment" do
- expect(lambda do
+ expect do
@uri.request_uri = "/"
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
it "should have a query of 'objectClass?one'" do
@@ -1390,9 +1455,9 @@ def to_s
end
it "should raise an error if omission would create an invalid URI" do
- expect(lambda do
+ expect do
@uri.omit(:authority, :path)
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
it "should have an origin of 'ldap://[2001:db8::7]'" do
@@ -1778,9 +1843,9 @@ def to_s
it "should not be roughly equal to the string " +
"'http://example.com:bogus/'" do
- expect(lambda do
+ expect do
expect(@uri === "http://example.com:bogus/").to eq(false)
- end).not_to raise_error
+ end.not_to raise_error
end
it "should result in itself when joined with itself" do
@@ -1810,21 +1875,21 @@ def to_s
end
it "should not allow origin assignment without scheme" do
- expect(lambda do
+ expect do
@uri.origin = "example.com"
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
it "should not allow origin assignment without host" do
- expect(lambda do
+ expect do
@uri.origin = "http://"
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
it "should not allow origin assignment with bogus type" do
- expect(lambda do
+ expect do
@uri.origin = :bogus
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
# Section 6.2.3 of RFC 3986
@@ -1880,9 +1945,9 @@ def to_s
end
it "when joined with a bogus object a TypeError should be raised" do
- expect(lambda do
+ expect do
@uri.join(42)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should have the correct username after assignment" do
@@ -2015,15 +2080,15 @@ def to_s
end
it "should raise an error if the site value is set to something bogus" do
- expect(lambda do
+ expect do
@uri.site = 42
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should raise an error if the request URI is set to something bogus" do
- expect(lambda do
+ expect do
@uri.request_uri = 42
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should correctly convert to a hash" do
@@ -2072,9 +2137,9 @@ def to_s
it "should raise an error for " +
"'http://[]/'" do
- expect(lambda do
+ expect do
Addressable::URI.parse("http://[]/")
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
end
@@ -2100,9 +2165,9 @@ def to_s
it "should not allow to set bare IPv6 address as host" do
uri = Addressable::URI.parse("http://[::1]/")
skip "not checked"
- expect(lambda do
+ expect do
uri.host = '3ffe:1900:4545:3:200:f8ff:fe21:67cf'
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
end
@@ -2134,9 +2199,9 @@ def to_s
it "should raise an error for " +
"'http://[v0.]/'" do
- expect(lambda do
+ expect do
Addressable::URI.parse("http://[v0.]/")
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
end
@@ -2480,9 +2545,9 @@ def to_s
end
it "should not raise an exception when normalized" do
- expect(lambda do
+ expect do
@uri.normalize
- end).not_to raise_error
+ end.not_to raise_error
end
it "should be considered to be in normal form" do
@@ -2534,9 +2599,9 @@ def to_s
end
it "should not raise an exception when normalized" do
- expect(lambda do
+ expect do
@uri.normalize
- end).not_to raise_error
+ end.not_to raise_error
end
it "should be considered to be in normal form" do
@@ -2559,9 +2624,9 @@ def to_s
end
it "should not raise an exception when normalized" do
- expect(lambda do
+ expect do
@uri.normalize
- end).not_to raise_error
+ end.not_to raise_error
end
it "should be considered to be in normal form" do
@@ -2597,9 +2662,9 @@ def to_s
end
it "should raise an error if encoding with an unexpected return type" do
- expect(lambda do
+ expect do
Addressable::URI.normalized_encode(@uri, Integer)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "if percent encoded should be 'http://example.com/C%25CC%25A7'" do
@@ -2615,9 +2680,9 @@ def to_s
end
it "should raise an error if encoding with an unexpected return type" do
- expect(lambda do
+ expect do
Addressable::URI.encode(@uri, Integer)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should be identical to its duplicate" do
@@ -2752,9 +2817,9 @@ def to_s
it "should not be roughly equal to the string " +
"'http://example.com:bogus/'" do
- expect(lambda do
+ expect do
expect(@uri === "http://example.com:bogus/").to eq(false)
- end).not_to raise_error
+ end.not_to raise_error
end
it "should result in itself when joined with itself" do
@@ -3100,9 +3165,9 @@ def to_s
end
it "should become invalid when normalized" do
- expect(lambda do
+ expect do
@uri.normalize
- end).to raise_error(Addressable::URI::InvalidURIError, /authority/)
+ end.to raise_error(Addressable::URI::InvalidURIError, /authority/)
end
it "should have a path of '/..//example.com'" do
@@ -3340,12 +3405,12 @@ def to_s
end
it "should raise an error if routing is attempted" do
- expect(lambda do
+ expect do
@uri.route_to("http://example.com/")
- end).to raise_error(ArgumentError, /relative\/path\/to\/resource/)
- expect(lambda do
+ end.to raise_error(ArgumentError, /relative\/path\/to\/resource/)
+ expect do
@uri.route_from("http://example.com/")
- end).to raise_error(ArgumentError, /relative\/path\/to\/resource/)
+ end.to raise_error(ArgumentError, /relative\/path\/to\/resource/)
end
it "when joined with 'another/relative/path' should be " +
@@ -3942,9 +4007,9 @@ def to_s
end
it "should raise an error if assigning a bogus object to the hostname" do
- expect(lambda do
+ expect do
@uri.hostname = Object.new
- end).to raise_error
+ end.to raise_error(TypeError)
end
it "should have the correct port after assignment" do
@@ -4023,9 +4088,9 @@ def to_s
end
it "should raise an error if query values are set to a bogus type" do
- expect(lambda do
+ expect do
@uri.query_values = "bogus"
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should have the correct fragment after assignment" do
@@ -4097,39 +4162,39 @@ def to_s
end
it "should fail to merge with bogus values" do
- expect(lambda do
+ expect do
@uri.merge(:port => "bogus")
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
it "should fail to merge with bogus values" do
- expect(lambda do
+ expect do
@uri.merge(:authority => "bar@baz:bogus")
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
it "should fail to merge with bogus parameters" do
- expect(lambda do
+ expect do
@uri.merge(42)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should fail to merge with bogus parameters" do
- expect(lambda do
+ expect do
@uri.merge("http://example.com/")
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should fail to merge with both authority and subcomponents" do
- expect(lambda do
+ expect do
@uri.merge(:authority => "foo:bar@baz:42", :port => "42")
- end).to raise_error(ArgumentError)
+ end.to raise_error(ArgumentError)
end
it "should fail to merge with both userinfo and subcomponents" do
- expect(lambda do
+ expect do
@uri.merge(:userinfo => "foo:bar", :user => "foo")
- end).to raise_error(ArgumentError)
+ end.to raise_error(ArgumentError)
end
it "should be identical to its duplicate" do
@@ -4262,6 +4327,36 @@ def to_s
end
end
+describe Addressable::URI, "when parsed from 'https://example.com/?q=a+b'" do
+ before do
+ @uri = Addressable::URI.parse("https://example.com/?q=a+b")
+ end
+
+ it "should have query_values of {'q' => 'a b'}" do
+ expect(@uri.query_values).to eq("q" => "a b")
+ end
+end
+
+describe Addressable::URI, "when parsed from 'example.com?q=a+b'" do
+ before do
+ @uri = Addressable::URI.parse("example.com?q=a+b")
+ end
+
+ it "should have query_values of {'q' => 'a b'}" do
+ expect(@uri.query_values).to eq("q" => "a b")
+ end
+end
+
+describe Addressable::URI, "when parsed from 'mailto:?q=a+b'" do
+ before do
+ @uri = Addressable::URI.parse("mailto:?q=a+b")
+ end
+
+ it "should have query_values of {'q' => 'a+b'}" do
+ expect(@uri.query_values).to eq("q" => "a+b")
+ end
+end
+
describe Addressable::URI, "when parsed from " +
"'http://example.com/?q=a%2bb'" do
before do
@@ -4303,6 +4398,46 @@ def to_s
end
end
+describe Addressable::URI, "when parsed from 'http://example/?b=1&a=2&c=3'" do
+ before do
+ @uri = Addressable::URI.parse("http://example/?b=1&a=2&c=3")
+ end
+
+ it "should have a sorted normalized query of 'a=2&b=1&c=3'" do
+ expect(@uri.normalized_query(:sorted)).to eq("a=2&b=1&c=3")
+ end
+end
+
+describe Addressable::URI, "when parsed from 'http://example/?&a&&c&'" do
+ before do
+ @uri = Addressable::URI.parse("http://example/?&a&&c&")
+ end
+
+ it "should have a compacted normalized query of 'a&c'" do
+ expect(@uri.normalized_query(:compacted)).to eq("a&c")
+ end
+end
+
+describe Addressable::URI, "when parsed from 'http://example.com/?a=1&a=1'" do
+ before do
+ @uri = Addressable::URI.parse("http://example.com/?a=1&a=1")
+ end
+
+ it "should have a compacted normalized query of 'a=1'" do
+ expect(@uri.normalized_query(:compacted)).to eq("a=1")
+ end
+end
+
+describe Addressable::URI, "when parsed from 'http://example.com/?a=1&a=2'" do
+ before do
+ @uri = Addressable::URI.parse("http://example.com/?a=1&a=2")
+ end
+
+ it "should have a compacted normalized query of 'a=1&a=2'" do
+ expect(@uri.normalized_query(:compacted)).to eq("a=1&a=2")
+ end
+end
+
describe Addressable::URI, "when parsed from " +
"'http://example.com/sound%2bvision'" do
before do
@@ -4414,10 +4549,10 @@ def to_s
end
it "should raise an error after nil assignment of authority segment" do
- expect(lambda do
+ expect do
# This would create an invalid URI
@uri.authority = nil
- end).to raise_error
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
end
@@ -4646,12 +4781,12 @@ def to_s
end
it "should raise an error if routing is attempted" do
- expect(lambda do
+ expect do
@uri.route_to("http://example.com/")
- end).to raise_error(ArgumentError, /\/\/example.com\//)
- expect(lambda do
+ end.to raise_error(ArgumentError, /\/\/example.com\//)
+ expect do
@uri.route_from("http://example.com/")
- end).to raise_error(ArgumentError, /\/\/example.com\//)
+ end.to raise_error(ArgumentError, /\/\/example.com\//)
end
it "should have a 'null' origin" do
@@ -4745,9 +4880,9 @@ def to_s
describe Addressable::URI, "when parsed from " +
"'http://under_score.example.com/'" do
it "should not cause an error" do
- expect(lambda do
+ expect do
Addressable::URI.parse("http://under_score.example.com/")
- end).not_to raise_error
+ end.not_to raise_error
end
end
@@ -4819,9 +4954,9 @@ def to_s
end
it "should raise an error for invalid return type values" do
- expect(lambda do
- @uri.query_values(Fixnum)
- end).to raise_error(ArgumentError)
+ expect do
+ @uri.query_values(Integer)
+ end.to raise_error(ArgumentError)
end
it "should have the correct array query values" do
@@ -5422,9 +5557,9 @@ def to_s
end
it "when joined with a bogus object a TypeError should be raised" do
- expect(lambda do
+ expect do
Addressable::URI.join(@uri, 42)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
end
@@ -5451,9 +5586,9 @@ def to_s
describe Addressable::URI, "when converting a bogus path" do
it "should raise a TypeError" do
- expect(lambda do
+ expect do
Addressable::URI.convert_path(42)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
end
@@ -5515,18 +5650,18 @@ def to_s
end
context "which " do
- let (:uri) { Addressable::URI.parse("http://comrade.net/path/to/source/") }
+ let (:uri) { Addressable::URI.parse("http://www.comrade.net/path/to/source/") }
it "contains a subdomain" do
uri.tld = "co.uk"
- expect(uri.to_s).to eq("http://comrade.co.uk/path/to/source/")
+ expect(uri.to_s).to eq("http://www.comrade.co.uk/path/to/source/")
end
it "is part of the domain" do
uri.tld = "com"
- expect(uri.to_s).to eq("http://comrade.com/path/to/source/")
+ expect(uri.to_s).to eq("http://www.comrade.com/path/to/source/")
end
end
end
@@ -5648,9 +5783,9 @@ def to_str
end
it "should raise a TypeError for objects than cannot be converted" do
- expect(lambda do
+ expect do
Addressable::URI.parse(42)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should correctly parse heuristically anything with a 'to_str' method" do
@@ -5658,9 +5793,9 @@ def to_str
end
it "should raise a TypeError for objects than cannot be converted" do
- expect(lambda do
+ expect do
Addressable::URI.heuristic_parse(42)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
end
@@ -5704,9 +5839,9 @@ def to_str
describe Addressable::URI, "when form encoding a non-Array object" do
it "should raise a TypeError for objects than cannot be converted" do
- expect(lambda do
+ expect do
Addressable::URI.form_encode(42)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
end
@@ -5772,9 +5907,9 @@ def to_str
end
it "should raise a TypeError for objects than cannot be converted" do
- expect(lambda do
+ expect do
Addressable::URI.form_unencode(42)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
end
@@ -5784,15 +5919,15 @@ def to_str
end
it "should raise a TypeError for objects than cannot be converted" do
- expect(lambda do
+ expect do
Addressable::URI.normalize_component(42)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should raise a TypeError for objects than cannot be converted" do
- expect(lambda do
+ expect do
Addressable::URI.normalize_component("component", 42)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
end
@@ -5864,6 +5999,18 @@ def to_str
end
end
+describe Addressable::URI, "when encoding IP literals" do
+ it "should work for IPv4" do
+ input = "http://127.0.0.1/"
+ expect(Addressable::URI.encode(input)).to eq(input)
+ end
+
+ it "should work for IPv6" do
+ input = "http://[fe80::200:f8ff:fe21:67cf]/"
+ expect(Addressable::URI.encode(input)).to eq(input)
+ end
+end
+
describe Addressable::URI, "when encoding a string with existing encodings to upcase" do
it "should result in correct percent encoded sequence" do
expect(Addressable::URI.encode_component("JK%4c", "0-9A-IKM-Za-z%", "L")).to eq("%4AK%4C")
@@ -5911,6 +6058,11 @@ def to_str
expect(Addressable::URI.unencode_component("ski=%BA%DAɫ")).to eq("ski=\xBA\xDAɫ")
end
+ it "should not fail with UTF-8 incompatible string" do
+ url = "/M%E9/\xE9?p=\xFC".b
+ expect(Addressable::URI.unencode_component(url)).to eq("/M\xE9/\xE9?p=\xFC")
+ end
+
it "should result in correct percent encoded sequence as a URI" do
expect(Addressable::URI.unencode(
"/path?g%C3%BCnther", ::Addressable::URI
@@ -5936,41 +6088,41 @@ def to_str
describe Addressable::URI, "when unencoding a bogus object" do
it "should raise a TypeError" do
- expect(lambda do
+ expect do
Addressable::URI.unencode_component(42)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should raise a TypeError" do
- expect(lambda do
+ expect do
Addressable::URI.unencode("/path?g%C3%BCnther", Integer)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
end
describe Addressable::URI, "when encoding a bogus object" do
it "should raise a TypeError" do
- expect(lambda do
+ expect do
Addressable::URI.encode(Object.new)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should raise a TypeError" do
- expect(lambda do
+ expect do
Addressable::URI.normalized_encode(Object.new)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should raise a TypeError" do
- expect(lambda do
+ expect do
Addressable::URI.encode_component("günther", Object.new)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should raise a TypeError" do
- expect(lambda do
+ expect do
Addressable::URI.encode_component(Object.new)
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
end
@@ -5986,9 +6138,9 @@ def to_str
end
it "should not raise error when frozen" do
- expect(lambda do
+ expect do
Addressable::URI.heuristic_parse(@input).freeze.to_s
- end).not_to raise_error
+ end.not_to raise_error
end
end
@@ -6352,6 +6504,44 @@ def to_str
end
end
+describe Addressable::URI, "when given the input: 'user@domain.com'" do
+ before do
+ @input = "user@domain.com"
+ end
+
+ context "for heuristic parse" do
+ it "should remain 'mailto:user@domain.com'" do
+ uri = Addressable::URI.heuristic_parse("mailto:#{@input}")
+ expect(uri.to_s).to eq("mailto:user@domain.com")
+ end
+
+ it "should have a scheme of 'mailto'" do
+ uri = Addressable::URI.heuristic_parse(@input)
+ expect(uri.to_s).to eq("mailto:user@domain.com")
+ expect(uri.scheme).to eq("mailto")
+ end
+
+ it "should remain 'acct:user@domain.com'" do
+ uri = Addressable::URI.heuristic_parse("acct:#{@input}")
+ expect(uri.to_s).to eq("acct:user@domain.com")
+ end
+
+ context "HTTP" do
+ before do
+ @uri = Addressable::URI.heuristic_parse("http://#{@input}/")
+ end
+
+ it "should remain 'http://user@domain.com/'" do
+ expect(@uri.to_s).to eq("http://user@domain.com/")
+ end
+
+ it "should have the username 'user' for HTTP basic authentication" do
+ expect(@uri.user).to eq("user")
+ end
+ end
+ end
+end
+
describe Addressable::URI, "when assigning query values" do
before do
@uri = Addressable::URI.new
@@ -6363,54 +6553,54 @@ def to_str
end
it "should raise an error attempting to assign {'a' => {'b' => ['c']}}" do
- expect(lambda do
+ expect do
@uri.query_values = { 'a' => {'b' => ['c'] } }
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should raise an error attempting to assign " +
"{:b => '2', :a => {:c => '1'}}" do
- expect(lambda do
+ expect do
@uri.query_values = {:b => '2', :a => {:c => '1'}}
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should raise an error attempting to assign " +
"{:a => 'a', :b => [{:c => 'c', :d => 'd'}, " +
"{:e => 'e', :f => 'f'}]}" do
- expect(lambda do
+ expect do
@uri.query_values = {
:a => "a", :b => [{:c => "c", :d => "d"}, {:e => "e", :f => "f"}]
}
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should raise an error attempting to assign " +
"{:a => 'a', :b => [{:c => true, :d => 'd'}, " +
"{:e => 'e', :f => 'f'}]}" do
- expect(lambda do
+ expect do
@uri.query_values = {
:a => 'a', :b => [{:c => true, :d => 'd'}, {:e => 'e', :f => 'f'}]
}
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should raise an error attempting to assign " +
"{:a => 'a', :b => {:c => true, :d => 'd'}}" do
- expect(lambda do
+ expect do
@uri.query_values = {
:a => 'a', :b => {:c => true, :d => 'd'}
}
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should raise an error attempting to assign " +
"{:a => 'a', :b => {:c => true, :d => 'd'}}" do
- expect(lambda do
+ expect do
@uri.query_values = {
:a => 'a', :b => {:c => true, :d => 'd'}
}
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should correctly assign {:a => 1, :b => 1.5}" do
@@ -6421,13 +6611,13 @@ def to_str
it "should raise an error attempting to assign " +
"{:z => 1, :f => [2, {999.1 => [3,'4']}, ['h', 'i']], " +
":a => {:b => ['c', 'd'], :e => true, :y => 0.5}}" do
- expect(lambda do
+ expect do
@uri.query_values = {
:z => 1,
:f => [ 2, {999.1 => [3,'4']}, ['h', 'i'] ],
:a => { :b => ['c', 'd'], :e => true, :y => 0.5 }
}
- end).to raise_error(TypeError)
+ end.to raise_error(TypeError)
end
it "should correctly assign {}" do
@@ -6477,7 +6667,7 @@ def to_str
@uri.path = "acct:bob@sporkmonger.com"
expect(@uri.path).to eq("acct:bob@sporkmonger.com")
expect(@uri.normalize.to_str).to eq("acct%2Fbob@sporkmonger.com")
- expect(lambda { @uri.to_s }).to raise_error(
+ expect { @uri.to_s }.to raise_error(
Addressable::URI::InvalidURIError
)
end
@@ -6495,26 +6685,26 @@ def to_str
end
it "should not allow relative paths to be assigned on absolute URIs" do
- expect(lambda do
+ expect do
@uri.scheme = "http"
@uri.host = "example.com"
@uri.path = "acct:bob@sporkmonger.com"
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
it "should not allow relative paths to be assigned on absolute URIs" do
- expect(lambda do
+ expect do
@uri.path = "acct:bob@sporkmonger.com"
@uri.scheme = "http"
@uri.host = "example.com"
- end).to raise_error(Addressable::URI::InvalidURIError)
+ end.to raise_error(Addressable::URI::InvalidURIError)
end
it "should not allow relative paths to be assigned on absolute URIs" do
- expect(lambda do
+ expect do
@uri.path = "uuid:0b3ecf60-3f93-11df-a9c3-001f5bfffe12"
@uri.scheme = "urn"
- end).not_to raise_error
+ end.not_to raise_error
end
end
@@ -6543,3 +6733,13 @@ def to_str
expect(@uri.class).to eq(@uri.join('path').class)
end
end
+
+describe Addressable::URI, "when initialized in a non-main `Ractor`" do
+ it "should have the same value as if used in the main `Ractor`" do
+ pending("Ruby 3.0+ for `Ractor` support") unless defined?(Ractor)
+ main = Addressable::URI.parse("http://example.com")
+ expect(
+ Ractor.new { Addressable::URI.parse("http://example.com") }.take
+ ).to eq(main)
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 4427f214..bd8e3958 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -16,6 +16,15 @@
add_filter "spec/"
add_filter "vendor/"
end
+end if Gem.loaded_specs.key?("simplecov")
+
+class TestHelper
+ def self.native_supported?
+ mri = RUBY_ENGINE == "ruby"
+ windows = RUBY_PLATFORM.include?("mingw")
+
+ mri && !windows
+ end
end
RSpec.configure do |config|
diff --git a/tasks/gem.rake b/tasks/gem.rake
index 6ca09eb3..24d9714b 100644
--- a/tasks/gem.rake
+++ b/tasks/gem.rake
@@ -11,7 +11,6 @@ namespace :gem do
s.files = PKG_FILES.to_a
- s.has_rdoc = true
s.extra_rdoc_files = %w( README.md )
s.rdoc_options.concat ["--main", "README.md"]
@@ -20,10 +19,10 @@ namespace :gem do
exit(1)
end
- s.required_ruby_version = '>= 2.0'
+ s.required_ruby_version = ">= 2.2"
- s.add_runtime_dependency 'public_suffix', '>= 2.0.2', '< 4.0'
- s.add_development_dependency 'bundler', '>= 1.0', '< 3.0'
+ s.add_runtime_dependency "public_suffix", ">= 2.0.2", "< 6.0"
+ s.add_development_dependency "bundler", ">= 1.0", "< 3.0"
s.require_path = "lib"
@@ -31,6 +30,9 @@ namespace :gem do
s.email = "bob@sporkmonger.com"
s.homepage = "https://github.com/sporkmonger/addressable"
s.license = "Apache-2.0"
+ s.metadata = {
+ "changelog_uri" => "https://github.com/sporkmonger/addressable/blob/main/CHANGELOG.md"
+ }
end
Gem::PackageTask.new(GEM_SPEC) do |p|
@@ -42,7 +44,7 @@ namespace :gem do
desc "Generates .gemspec file"
task :gemspec do
spec_string = GEM_SPEC.to_ruby
- File.open("#{GEM_SPEC.name}.gemspec", 'w') do |file|
+ File.open("#{GEM_SPEC.name}.gemspec", "w") do |file|
file.write spec_string
end
end
@@ -72,9 +74,9 @@ namespace :gem do
desc "Reinstall the gem"
task :reinstall => [:uninstall, :install]
- desc 'Package for release'
+ desc "Package for release"
task :release => ["gem:package", "gem:gemspec"] do |t|
- v = ENV['VERSION'] or abort 'Must supply VERSION=x.y.z'
+ v = ENV["VERSION"] or abort "Must supply VERSION=x.y.z"
abort "Versions don't match #{v} vs #{PROJ.version}" if v != PKG_VERSION
pkg = "pkg/#{GEM_SPEC.full_name}"
diff --git a/tasks/profile.rake b/tasks/profile.rake
new file mode 100644
index 00000000..b697d489
--- /dev/null
+++ b/tasks/profile.rake
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+namespace :profile do
+ desc "Profile Template match memory allocations"
+ task :template_match_memory do
+ require "memory_profiler"
+ require "addressable/template"
+
+ start_at = Time.now.to_f
+ template = Addressable::Template.new("http://example.com/{?one,two,three}")
+ report = MemoryProfiler.report do
+ 30_000.times do
+ template.match(
+ "http://example.com/?one=one&two=floo&three=me"
+ )
+ end
+ end
+ end_at = Time.now.to_f
+ print_options = { scale_bytes: true, normalize_paths: true }
+ puts "\n\n"
+
+ if ENV["CI"]
+ report.pretty_print(print_options)
+ else
+ t_allocated = report.scale_bytes(report.total_allocated_memsize)
+ t_retained = report.scale_bytes(report.total_retained_memsize)
+
+ puts "Total allocated: #{t_allocated} (#{report.total_allocated} objects)"
+ puts "Total retained: #{t_retained} (#{report.total_retained} objects)"
+ puts "Took #{end_at - start_at} seconds"
+
+ FileUtils.mkdir_p("tmp")
+ report.pretty_print(to_file: "tmp/memprof.txt", **print_options)
+ end
+ end
+
+ desc "Profile URI parse memory allocations"
+ task :memory do
+ require "memory_profiler"
+ require "addressable/uri"
+ if ENV["IDNA_MODE"] == "pure"
+ Addressable.send(:remove_const, :IDNA)
+ load "addressable/idna/pure.rb"
+ end
+
+ start_at = Time.now.to_f
+ report = MemoryProfiler.report do
+ 30_000.times do
+ Addressable::URI.parse(
+ "http://google.com/stuff/../?with_lots=of¶ms=asdff#!stuff"
+ ).normalize
+ end
+ end
+ end_at = Time.now.to_f
+ print_options = { scale_bytes: true, normalize_paths: true }
+ puts "\n\n"
+
+ if ENV["CI"]
+ report.pretty_print(**print_options)
+ else
+ t_allocated = report.scale_bytes(report.total_allocated_memsize)
+ t_retained = report.scale_bytes(report.total_retained_memsize)
+
+ puts "Total allocated: #{t_allocated} (#{report.total_allocated} objects)"
+ puts "Total retained: #{t_retained} (#{report.total_retained} objects)"
+ puts "Took #{end_at - start_at} seconds"
+
+ FileUtils.mkdir_p("tmp")
+ report.pretty_print(to_file: "tmp/memprof.txt", **print_options)
+ end
+ end
+end
diff --git a/tasks/rspec.rake b/tasks/rspec.rake
index 85288438..e3d9f014 100644
--- a/tasks/rspec.rake
+++ b/tasks/rspec.rake
@@ -5,7 +5,7 @@ require "rspec/core/rake_task"
namespace :spec do
RSpec::Core::RakeTask.new(:simplecov) do |t|
t.pattern = FileList['spec/**/*_spec.rb']
- t.rspec_opts = ['--color', '--format', 'documentation']
+ t.rspec_opts = %w[--color --format documentation] unless ENV["CI"]
end
namespace :simplecov do