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 index c653b593..d372d8ad 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,11 +14,11 @@ jobs: matrix: ruby: [2.7] idna_mode: [native, pure] - os: [ubuntu-18.04] + os: [ubuntu-20.04] env: IDNA_MODE: ${{ matrix.idna_mode }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install libidn run: sudo apt-get install libidn11-dev @@ -46,7 +46,7 @@ jobs: fail-fast: false matrix: ruby: [2.7] - os: [ubuntu-18.04] + os: [ubuntu-20.04] env: BUNDLE_WITHOUT: development COVERALLS_SERVICE_NAME: github @@ -54,7 +54,7 @@ jobs: COVERALLS_DEBUG: true CI_BUILD_NUMBER: ${{ github.run_id }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install libidn run: sudo apt-get install libidn11-dev @@ -86,45 +86,43 @@ jobs: - 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-head - # truffleruby-21.2 fails due to https://github.com/oracle/truffleruby/issues/2408 - - truffleruby-21.1 - - truffleruby-head + - jruby-9.3 + - truffleruby-21.3 + - truffleruby-22.1 os: - - ubuntu-18.04 + - ubuntu-20.04 gemfile: - Gemfile include: - - { os: ubuntu-18.04, ruby: 2.7, gemfile: gemfiles/public_suffix_2.rb } - - { os: ubuntu-18.04, ruby: 2.7, gemfile: gemfiles/public_suffix_3.rb } - # Ubuntu 20.04 - - { os: ubuntu-20.04, ruby: 2.7 } - - { os: ubuntu-20.04, ruby: '3.0' } + - { 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-18.04, ruby: 2.7 } + - { os: ubuntu-22.04, ruby: 3.1 } # macOS - - { os: macos-10.15, ruby: 2.7 } - - { os: macos-10.15, ruby: '3.0' } - - { os: macos-10.15, ruby: jruby } - - { os: macos-10.15, ruby: truffleruby-21.1 } - - { os: macos-11, ruby: 2.7 } - - { os: macos-11, ruby: '3.0' } - - { os: macos-11, ruby: jruby } - - { os: macos-11, ruby: truffleruby-21.1 } + - { os: macos-11, ruby: 3.1 } + - { os: macos-12, ruby: 3.1 } # Windows - - { os: windows-2019, ruby: 2.7 } - - { os: windows-2019, ruby: '3.0' } - - { os: windows-2019, ruby: jruby-9.1 } - - { os: windows-2019, ruby: jruby-9.2 } + - { os: windows-2019, ruby: 3.1 } + - { os: windows-2022, ruby: 3.1 } + - { os: windows-2022, ruby: jruby-9.3 } # allowed to fail - - { os: ubuntu-18.04, ruby: jruby-head, allow-failure: true } - - { os: ubuntu-18.04, ruby: truffleruby-head, allow-failure: true } + - { 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@v2 + - uses: actions/checkout@v3 - name: Install libidn (Ubuntu) if: startsWith(matrix.os, 'ubuntu') diff --git a/Gemfile b/Gemfile index 6ebb8b75..0d36ffb5 100644 --- a/Gemfile +++ b/Gemfile @@ -25,4 +25,6 @@ group :test, :development do gem "rake", ">= 12.3.3" end -gem "idn-ruby", platform: :mri +unless ENV["IDNA_MODE"] == "pure" + gem "idn-ruby", platform: :mri +end diff --git a/addressable.gemspec b/addressable.gemspec index 5a2710c2..1ee6542e 100644 --- a/addressable.gemspec +++ b/addressable.gemspec @@ -6,32 +6,30 @@ Gem::Specification.new do |s| s.version = "2.8.0" 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 = "2021-07-03" 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, "addressable.gemspec".freeze, "benchmark".freeze, "coverage".freeze, "data".freeze, "data/unicode.data".freeze, "documentation".freeze, "gemfiles".freeze, "lib".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".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, "specdoc".freeze, "tasks".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, "tmp".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.0".freeze) - s.rubygems_version = "3.0.3".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.freeze, [">= 2.0.2", "< 5.0"]) - s.add_development_dependency(%q.freeze, [">= 1.0", "< 3.0"]) - else - s.add_dependency(%q.freeze, [">= 2.0.2", "< 5.0"]) - s.add_dependency(%q.freeze, [">= 1.0", "< 3.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.freeze, [">= 2.0.2", "< 5.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/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 7a0c1fda..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 # diff --git a/lib/addressable/template.rb b/lib/addressable/template.rb index 398af17c..9e8174bf 100644 --- a/lib/addressable/template.rb +++ b/lib/addressable/template.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -# encoding:utf-8 #-- # Copyright (C) Bob Aman # @@ -1023,7 +1022,7 @@ def parse_new_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 526fa3d4..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,26 @@ 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 @@ -469,19 +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") - unless leave_encoded.empty? - leave_encoded = leave_encoded.dup.force_encoding("utf-8") - end - - 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 diff --git a/lib/addressable/version.rb b/lib/addressable/version.rb index 2efe4340..76670295 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 # diff --git a/spec/addressable/idna_spec.rb b/spec/addressable/idna_spec.rb index 4104b370..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"); 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/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 d47589ab..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"); @@ -78,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 { { diff --git a/spec/addressable/uri_spec.rb b/spec/addressable/uri_spec.rb index f1121502..4e3aced9 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"); @@ -6061,6 +6060,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 @@ -6731,3 +6735,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/tasks/gem.rake b/tasks/gem.rake index 4d9dee2e..24d9714b 100644 --- a/tasks/gem.rake +++ b/tasks/gem.rake @@ -19,9 +19,9 @@ 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", "< 5.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"