From 1619cf7cea7284336bfb80f9d2f7d47d5c91a755 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 25 Jun 2026 14:28:09 +1200 Subject: [PATCH 01/22] io_buffer: add safe callback wrappers for String views. (#17473) --- depend | 1 + ext/-test-/io_buffer/extconf.rb | 3 + ext/-test-/io_buffer/io_buffer.c | 105 +++++++++++++++++++++++++++++++ internal/io_buffer.h | 30 +++++++++ io_buffer.c | 100 +++++++++++++++++++++++++++++ test/ruby/test_io_buffer.rb | 53 ++++++++++++++++ 6 files changed, 292 insertions(+) create mode 100644 ext/-test-/io_buffer/extconf.rb create mode 100644 ext/-test-/io_buffer/io_buffer.c create mode 100644 internal/io_buffer.h diff --git a/depend b/depend index 493858c5e05b87..95128de92144f0 100644 --- a/depend +++ b/depend @@ -7354,6 +7354,7 @@ io_buffer.$(OBJEXT): $(top_srcdir)/internal/fixnum.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/gc.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/imemo.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/io.h +io_buffer.$(OBJEXT): $(top_srcdir)/internal/io_buffer.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/numeric.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/serial.h diff --git a/ext/-test-/io_buffer/extconf.rb b/ext/-test-/io_buffer/extconf.rb new file mode 100644 index 00000000000000..d786b15db98c7f --- /dev/null +++ b/ext/-test-/io_buffer/extconf.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: false +require_relative "../auto_ext.rb" +auto_ext(inc: true) diff --git a/ext/-test-/io_buffer/io_buffer.c b/ext/-test-/io_buffer/io_buffer.c new file mode 100644 index 00000000000000..1e61e228df3a38 --- /dev/null +++ b/ext/-test-/io_buffer/io_buffer.c @@ -0,0 +1,105 @@ +#include "ruby.h" +#include "internal/io_buffer.h" + +static VALUE +io_buffer_get_string(VALUE buffer, VALUE argument) +{ + (void)argument; + return rb_funcall(buffer, rb_intern("get_string"), 0); +} + +static VALUE +io_buffer_readonly_p(VALUE buffer, VALUE argument) +{ + (void)argument; + return rb_funcall(buffer, rb_intern("readonly?"), 0); +} + +static VALUE +io_buffer_object_id(VALUE buffer, VALUE argument) +{ + (void)argument; + return rb_obj_id(buffer); +} + +static VALUE +io_buffer_set_string(VALUE buffer, VALUE string) +{ + return rb_funcall(buffer, rb_intern("set_string"), 1, string); +} + +static VALUE +io_buffer_modify_string(VALUE buffer, VALUE string) +{ + (void)buffer; + rb_str_cat(string, "!", 1); + return Qnil; +} + +static VALUE +io_buffer_raise(VALUE buffer, VALUE argument) +{ + (void)buffer; + (void)argument; + rb_raise(rb_eRuntimeError, "interrupted"); +} + +static VALUE +io_buffer_for_reading_get_string(VALUE self, VALUE object) +{ + return rb_io_buffer_for_reading(object, io_buffer_get_string, Qnil); +} + +static VALUE +io_buffer_for_reading_readonly_p(VALUE self, VALUE object) +{ + return rb_io_buffer_for_reading(object, io_buffer_readonly_p, Qnil); +} + +static VALUE +io_buffer_for_reading_object_id(VALUE self, VALUE object) +{ + return rb_io_buffer_for_reading(object, io_buffer_object_id, Qnil); +} + +static VALUE +io_buffer_for_reading_raise(VALUE self, VALUE string) +{ + StringValue(string); + return rb_io_buffer_for_reading(string, io_buffer_raise, Qnil); +} + +static VALUE +io_buffer_for_writing_set_string(VALUE self, VALUE object, VALUE string) +{ + StringValue(string); + return rb_io_buffer_for_writing(object, io_buffer_set_string, string); +} + +static VALUE +io_buffer_for_writing_readonly_p(VALUE self, VALUE object) +{ + return rb_io_buffer_for_writing(object, io_buffer_readonly_p, Qnil); +} + +static VALUE +io_buffer_for_writing_modify_string(VALUE self, VALUE string) +{ + StringValue(string); + return rb_io_buffer_for_writing(string, io_buffer_modify_string, string); +} + +void +Init_io_buffer(void) +{ + VALUE mBug = rb_define_module("Bug"); + VALUE mIOBuffer = rb_define_module_under(mBug, "IOBuffer"); + + rb_define_singleton_method(mIOBuffer, "for_reading_get_string", io_buffer_for_reading_get_string, 1); + rb_define_singleton_method(mIOBuffer, "for_reading_readonly?", io_buffer_for_reading_readonly_p, 1); + rb_define_singleton_method(mIOBuffer, "for_reading_object_id", io_buffer_for_reading_object_id, 1); + rb_define_singleton_method(mIOBuffer, "for_reading_raise", io_buffer_for_reading_raise, 1); + rb_define_singleton_method(mIOBuffer, "for_writing_set_string", io_buffer_for_writing_set_string, 2); + rb_define_singleton_method(mIOBuffer, "for_writing_readonly?", io_buffer_for_writing_readonly_p, 1); + rb_define_singleton_method(mIOBuffer, "for_writing_modify_string", io_buffer_for_writing_modify_string, 1); +} diff --git a/internal/io_buffer.h b/internal/io_buffer.h new file mode 100644 index 00000000000000..51e78f13465aba --- /dev/null +++ b/internal/io_buffer.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +RUBY_SYMBOL_EXPORT_BEGIN + +/** + * Wrap string_or_buffer as a read-only IO::Buffer view and invoke callback(buffer, argument). + * + * - IO::Buffer: callback is called directly with no wrapping. + * - String: locked to prevent GC compaction from moving the backing memory, + * wrapped in a read-only IO::Buffer, callback called inside rb_ensure, buffer + * freed and string unlocked on exit. + * - Other: TypeError raised. + */ +VALUE rb_io_buffer_for_reading(VALUE string_or_buffer, VALUE (*callback)(VALUE buffer, VALUE argument), VALUE argument); + +/** + * Wrap string_or_buffer as a writable IO::Buffer view and invoke callback(buffer, argument). + * + * - Read-only IO::Buffer: ArgumentError raised. + * - IO::Buffer: callback is called directly with no wrapping. + * - String: locked, wrapped in a writable IO::Buffer, callback called inside + * rb_ensure, buffer freed and string unlocked on exit. + * - Other: TypeError raised. + */ +VALUE rb_io_buffer_for_writing(VALUE string_or_buffer, VALUE (*callback)(VALUE buffer, VALUE argument), VALUE argument); + +RUBY_SYMBOL_EXPORT_END diff --git a/io_buffer.c b/io_buffer.c index b07626c5d738f5..98c82bd6c7821d 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -20,6 +20,7 @@ #include "internal/numeric.h" #include "internal/string.h" #include "internal/io.h" +#include "internal/io_buffer.h" VALUE rb_cIOBuffer; VALUE rb_eIOBufferLockedError; @@ -556,6 +557,105 @@ io_buffer_for_yield_instance_ensure(VALUE _arguments) return Qnil; } +struct io_buffer_for_callback_arguments { + VALUE klass; + VALUE string; + VALUE instance; + enum rb_io_buffer_flags flags; + int locked; + VALUE (*callback)(VALUE, VALUE); + VALUE argument; +}; + +static VALUE +io_buffer_for_callback_call(VALUE _arguments) +{ + struct io_buffer_for_callback_arguments *arguments = (struct io_buffer_for_callback_arguments *)_arguments; + + arguments->instance = io_buffer_for_make_instance(arguments->klass, arguments->string, arguments->flags); + + if (!RB_OBJ_FROZEN(arguments->string)) { + rb_str_locktmp(arguments->string); + arguments->locked = 1; + } + + return arguments->callback(arguments->instance, arguments->argument); +} + +static VALUE +io_buffer_for_callback_ensure(VALUE _arguments) +{ + struct io_buffer_for_callback_arguments *arguments = (struct io_buffer_for_callback_arguments *)_arguments; + + if (arguments->instance != Qnil) { + rb_io_buffer_free(arguments->instance); + } + + if (arguments->locked) { + rb_str_unlocktmp(arguments->string); + } + + return Qnil; +} + +VALUE +rb_io_buffer_for_reading(VALUE string_or_buffer, VALUE (*callback)(VALUE, VALUE), VALUE argument) +{ + if (rb_obj_is_kind_of(string_or_buffer, rb_cIOBuffer)) { + return callback(string_or_buffer, argument); + } + else if (RB_TYPE_P(string_or_buffer, T_STRING)) { + StringValue(string_or_buffer); + struct io_buffer_for_callback_arguments arguments = { + .klass = rb_cIOBuffer, + .string = string_or_buffer, + .instance = Qnil, + .flags = RB_IO_BUFFER_READONLY, + .locked = 0, + .callback = callback, + .argument = argument, + }; + return rb_ensure(io_buffer_for_callback_call, (VALUE)&arguments, + io_buffer_for_callback_ensure, (VALUE)&arguments); + } + else { + rb_raise(rb_eTypeError, "expected String or IO::Buffer, not %"PRIsVALUE, + rb_obj_class(string_or_buffer)); + } +} + +/* Forward declaration: rb_io_buffer_readonly_p is defined later in this file. */ +int rb_io_buffer_readonly_p(VALUE self); + +VALUE +rb_io_buffer_for_writing(VALUE string_or_buffer, VALUE (*callback)(VALUE, VALUE), VALUE argument) +{ + if (rb_obj_is_kind_of(string_or_buffer, rb_cIOBuffer)) { + if (rb_io_buffer_readonly_p(string_or_buffer)) { + rb_raise(rb_eArgError, "buffer is read-only"); + } + return callback(string_or_buffer, argument); + } + else if (RB_TYPE_P(string_or_buffer, T_STRING)) { + StringValue(string_or_buffer); + struct io_buffer_for_callback_arguments arguments = { + .klass = rb_cIOBuffer, + .string = string_or_buffer, + .instance = Qnil, + .flags = 0, + .locked = 0, + .callback = callback, + .argument = argument, + }; + return rb_ensure(io_buffer_for_callback_call, (VALUE)&arguments, + io_buffer_for_callback_ensure, (VALUE)&arguments); + } + else { + rb_raise(rb_eTypeError, "expected String or IO::Buffer, not %"PRIsVALUE, + rb_obj_class(string_or_buffer)); + } +} + /* * call-seq: * IO::Buffer.for(string) -> readonly io_buffer diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb index 7d8583d49214ae..0917f1517ac485 100644 --- a/test/ruby/test_io_buffer.rb +++ b/test/ruby/test_io_buffer.rb @@ -2,6 +2,7 @@ require 'tempfile' require 'rbconfig/sizeof' +require '-test-/io_buffer' class TestIOBuffer < Test::Unit::TestCase experimental = Warning[:experimental] @@ -31,6 +32,58 @@ def test_flags assert_equal 128, IO::Buffer::READONLY end + def test_internal_for_reading_with_string + string = "hello" + + assert_equal "hello", Bug::IOBuffer.for_reading_get_string(string) + assert_equal true, Bug::IOBuffer.for_reading_readonly?(string) + end + + def test_internal_for_reading_with_io_buffer + buffer = IO::Buffer.for("hello") + + assert_equal buffer.object_id, Bug::IOBuffer.for_reading_object_id(buffer) + end + + def test_internal_for_writing_with_string + string = "hello" + + Bug::IOBuffer.for_writing_set_string(string, "world") + + assert_equal "world", string + assert_equal false, Bug::IOBuffer.for_writing_readonly?(string) + end + + def test_internal_for_writing_rejects_readonly_buffer + buffer = IO::Buffer.for("hello") + + assert_raise(ArgumentError) do + Bug::IOBuffer.for_writing_set_string(buffer, "world") + end + end + + def test_internal_for_writing_unlocks_after_callback_exception + string = "hello" + + assert_raise(RuntimeError) do + Bug::IOBuffer.for_writing_modify_string(string) + end + + string << "!" + assert_equal "hello!", string + end + + def test_internal_for_reading_unlocks_after_callback_exception + string = "hello" + + assert_raise(RuntimeError) do + Bug::IOBuffer.for_reading_raise(string) + end + + string << "!" + assert_equal "hello!", string + end + def test_endian assert_equal 4, IO::Buffer::LITTLE_ENDIAN assert_equal 8, IO::Buffer::BIG_ENDIAN From bc1348aa2c8e39f39d34ae13c6eff54eedbbb9c3 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 25 Jun 2026 11:01:18 +0900 Subject: [PATCH 02/22] [ruby/rubygems] Guard the vendored URI against double loading When the same Gem::URI copy is shipped in two gems (RubyGems and Bundler) and required from different paths, RubyGems' unguarded require_relative reloads it and emits "already initialized constant" warnings. Add an idempotent guard at the top of the vendored uri.rb so a second load from any path short-circuits. This prepares Bundler to reuse RubyGems' copy. https://github.com/ruby/rubygems/commit/a73d0e282e Co-Authored-By: Claude Opus 4.8 --- lib/rubygems/vendor/uri/lib/uri.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/rubygems/vendor/uri/lib/uri.rb b/lib/rubygems/vendor/uri/lib/uri.rb index 4691b122b26735..58a474b9e3e6d0 100644 --- a/lib/rubygems/vendor/uri/lib/uri.rb +++ b/lib/rubygems/vendor/uri/lib/uri.rb @@ -87,6 +87,10 @@ # You can redistribute it and/or modify it under the same term as Ruby. # +# Skip reloading when an identical copy (e.g. the one shipped inside the Bundler +# gem) was already required from a different path, to avoid redefinition warnings. +return if defined?(Gem::URI::VERSION) + module Gem::URI end From 0953a29617cec12e1c9684e4159f8a46460de087 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 25 Jun 2026 11:01:50 +0900 Subject: [PATCH 03/22] [ruby/rubygems] Ship RubyGems' vendored URI in the Bundler gem Add lib/rubygems/vendor/uri to the gem's files so Bundler carries the Gem::URI copy itself. This keeps Bundler self-contained on RubyGems versions older than 3.5, which do not vendor URI, independently of any release cutoff. https://github.com/ruby/rubygems/commit/268b317b9c Co-Authored-By: Claude Opus 4.8 --- lib/bundler/bundler.gemspec | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/bundler/bundler.gemspec b/lib/bundler/bundler.gemspec index 77e957108ccf6c..bd48d810286825 100644 --- a/lib/bundler/bundler.gemspec +++ b/lib/bundler/bundler.gemspec @@ -36,6 +36,10 @@ Gem::Specification.new do |s| s.files = Dir.glob("lib/bundler{.rb,/**/*}", File::FNM_DOTMATCH).reject {|f| File.directory?(f) } + # Bundler reuses RubyGems' vendored URI. Ship a copy under lib/rubygems so + # Bundler stays self-contained on RubyGems versions that predate it. + s.files += Dir.glob("lib/rubygems/vendor/uri/**/*", File::FNM_DOTMATCH).reject {|f| File.directory?(f) } + # include the gemspec itself because warbler breaks w/o it s.files += %w[lib/bundler/bundler.gemspec] From 223f796732b95fc3ecd461b8aaacc9be358b6569 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 25 Jun 2026 11:05:04 +0900 Subject: [PATCH 04/22] [ruby/rubygems] Use RubyGems' vendored URI in Bundler Drop Bundler's duplicate vendored URI copy and load Gem::URI from RubyGems' copy instead, which the gem now ships under lib/rubygems. The shim skips loading when Gem::URI is already defined and falls back to the stdlib only when no vendored copy can be required at all. https://github.com/ruby/rubygems/commit/e03d77a78c Co-Authored-By: Claude Opus 4.8 --- lib/bundler/vendor/uri/lib/uri.rb | 104 -- lib/bundler/vendor/uri/lib/uri/common.rb | 922 ---------- lib/bundler/vendor/uri/lib/uri/file.rb | 100 -- lib/bundler/vendor/uri/lib/uri/ftp.rb | 267 --- lib/bundler/vendor/uri/lib/uri/generic.rb | 1592 ----------------- lib/bundler/vendor/uri/lib/uri/http.rb | 137 -- lib/bundler/vendor/uri/lib/uri/https.rb | 23 - lib/bundler/vendor/uri/lib/uri/ldap.rb | 261 --- lib/bundler/vendor/uri/lib/uri/ldaps.rb | 22 - lib/bundler/vendor/uri/lib/uri/mailto.rb | 293 --- .../vendor/uri/lib/uri/rfc2396_parser.rb | 547 ------ .../vendor/uri/lib/uri/rfc3986_parser.rb | 206 --- lib/bundler/vendor/uri/lib/uri/version.rb | 6 - lib/bundler/vendor/uri/lib/uri/ws.rb | 83 - lib/bundler/vendor/uri/lib/uri/wss.rb | 23 - lib/bundler/vendored_uri.rb | 28 +- 16 files changed, 14 insertions(+), 4600 deletions(-) delete mode 100644 lib/bundler/vendor/uri/lib/uri.rb delete mode 100644 lib/bundler/vendor/uri/lib/uri/common.rb delete mode 100644 lib/bundler/vendor/uri/lib/uri/file.rb delete mode 100644 lib/bundler/vendor/uri/lib/uri/ftp.rb delete mode 100644 lib/bundler/vendor/uri/lib/uri/generic.rb delete mode 100644 lib/bundler/vendor/uri/lib/uri/http.rb delete mode 100644 lib/bundler/vendor/uri/lib/uri/https.rb delete mode 100644 lib/bundler/vendor/uri/lib/uri/ldap.rb delete mode 100644 lib/bundler/vendor/uri/lib/uri/ldaps.rb delete mode 100644 lib/bundler/vendor/uri/lib/uri/mailto.rb delete mode 100644 lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb delete mode 100644 lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb delete mode 100644 lib/bundler/vendor/uri/lib/uri/version.rb delete mode 100644 lib/bundler/vendor/uri/lib/uri/ws.rb delete mode 100644 lib/bundler/vendor/uri/lib/uri/wss.rb diff --git a/lib/bundler/vendor/uri/lib/uri.rb b/lib/bundler/vendor/uri/lib/uri.rb deleted file mode 100644 index 57b380c480a560..00000000000000 --- a/lib/bundler/vendor/uri/lib/uri.rb +++ /dev/null @@ -1,104 +0,0 @@ -# frozen_string_literal: false -# Bundler::URI is a module providing classes to handle Uniform Resource Identifiers -# (RFC2396[https://www.rfc-editor.org/rfc/rfc2396]). -# -# == Features -# -# * Uniform way of handling URIs. -# * Flexibility to introduce custom Bundler::URI schemes. -# * Flexibility to have an alternate Bundler::URI::Parser (or just different patterns -# and regexp's). -# -# == Basic example -# -# require 'bundler/vendor/uri/lib/uri' -# -# uri = Bundler::URI("http://foo.com/posts?id=30&limit=5#time=1305298413") -# #=> # -# -# uri.scheme #=> "http" -# uri.host #=> "foo.com" -# uri.path #=> "/posts" -# uri.query #=> "id=30&limit=5" -# uri.fragment #=> "time=1305298413" -# -# uri.to_s #=> "http://foo.com/posts?id=30&limit=5#time=1305298413" -# -# == Adding custom URIs -# -# module Bundler::URI -# class RSYNC < Generic -# DEFAULT_PORT = 873 -# end -# register_scheme 'RSYNC', RSYNC -# end -# #=> Bundler::URI::RSYNC -# -# Bundler::URI.scheme_list -# #=> {"FILE"=>Bundler::URI::File, "FTP"=>Bundler::URI::FTP, "HTTP"=>Bundler::URI::HTTP, -# # "HTTPS"=>Bundler::URI::HTTPS, "LDAP"=>Bundler::URI::LDAP, "LDAPS"=>Bundler::URI::LDAPS, -# # "MAILTO"=>Bundler::URI::MailTo, "RSYNC"=>Bundler::URI::RSYNC} -# -# uri = Bundler::URI("rsync://rsync.foo.com") -# #=> # -# -# == RFC References -# -# A good place to view an RFC spec is http://www.ietf.org/rfc.html. -# -# Here is a list of all related RFC's: -# - RFC822[https://www.rfc-editor.org/rfc/rfc822] -# - RFC1738[https://www.rfc-editor.org/rfc/rfc1738] -# - RFC2255[https://www.rfc-editor.org/rfc/rfc2255] -# - RFC2368[https://www.rfc-editor.org/rfc/rfc2368] -# - RFC2373[https://www.rfc-editor.org/rfc/rfc2373] -# - RFC2396[https://www.rfc-editor.org/rfc/rfc2396] -# - RFC2732[https://www.rfc-editor.org/rfc/rfc2732] -# - RFC3986[https://www.rfc-editor.org/rfc/rfc3986] -# -# == Class tree -# -# - Bundler::URI::Generic (in uri/generic.rb) -# - Bundler::URI::File - (in uri/file.rb) -# - Bundler::URI::FTP - (in uri/ftp.rb) -# - Bundler::URI::HTTP - (in uri/http.rb) -# - Bundler::URI::HTTPS - (in uri/https.rb) -# - Bundler::URI::LDAP - (in uri/ldap.rb) -# - Bundler::URI::LDAPS - (in uri/ldaps.rb) -# - Bundler::URI::MailTo - (in uri/mailto.rb) -# - Bundler::URI::Parser - (in uri/common.rb) -# - Bundler::URI::REGEXP - (in uri/common.rb) -# - Bundler::URI::REGEXP::PATTERN - (in uri/common.rb) -# - Bundler::URI::Util - (in uri/common.rb) -# - Bundler::URI::Error - (in uri/common.rb) -# - Bundler::URI::InvalidURIError - (in uri/common.rb) -# - Bundler::URI::InvalidComponentError - (in uri/common.rb) -# - Bundler::URI::BadURIError - (in uri/common.rb) -# -# == Copyright Info -# -# Author:: Akira Yamada -# Documentation:: -# Akira Yamada -# Dmitry V. Sabanin -# Vincent Batts -# License:: -# Copyright (c) 2001 akira yamada -# You can redistribute it and/or modify it under the same term as Ruby. -# - -module Bundler::URI -end - -require_relative 'uri/version' -require_relative 'uri/common' -require_relative 'uri/generic' -require_relative 'uri/file' -require_relative 'uri/ftp' -require_relative 'uri/http' -require_relative 'uri/https' -require_relative 'uri/ldap' -require_relative 'uri/ldaps' -require_relative 'uri/mailto' -require_relative 'uri/ws' -require_relative 'uri/wss' diff --git a/lib/bundler/vendor/uri/lib/uri/common.rb b/lib/bundler/vendor/uri/lib/uri/common.rb deleted file mode 100644 index 38339119c53047..00000000000000 --- a/lib/bundler/vendor/uri/lib/uri/common.rb +++ /dev/null @@ -1,922 +0,0 @@ -# frozen_string_literal: true -#-- -# = uri/common.rb -# -# Author:: Akira Yamada -# License:: -# You can redistribute it and/or modify it under the same term as Ruby. -# -# See Bundler::URI for general documentation -# - -require_relative "rfc2396_parser" -require_relative "rfc3986_parser" - -module Bundler::URI - # The default parser instance for RFC 2396. - RFC2396_PARSER = RFC2396_Parser.new - Ractor.make_shareable(RFC2396_PARSER) if defined?(Ractor) - - # The default parser instance for RFC 3986. - RFC3986_PARSER = RFC3986_Parser.new - Ractor.make_shareable(RFC3986_PARSER) if defined?(Ractor) - - # The default parser instance. - DEFAULT_PARSER = RFC3986_PARSER - Ractor.make_shareable(DEFAULT_PARSER) if defined?(Ractor) - - # Set the default parser instance. - def self.parser=(parser = RFC3986_PARSER) - remove_const(:Parser) if defined?(::Bundler::URI::Parser) - const_set("Parser", parser.class) - - remove_const(:PARSER) if defined?(::Bundler::URI::PARSER) - const_set("PARSER", parser) - - remove_const(:REGEXP) if defined?(::Bundler::URI::REGEXP) - remove_const(:PATTERN) if defined?(::Bundler::URI::PATTERN) - if Parser == RFC2396_Parser - const_set("REGEXP", Bundler::URI::RFC2396_REGEXP) - const_set("PATTERN", Bundler::URI::RFC2396_REGEXP::PATTERN) - end - - Parser.new.regexp.each_pair do |sym, str| - remove_const(sym) if const_defined?(sym, false) - const_set(sym, str) - end - end - self.parser = RFC3986_PARSER - - def self.const_missing(const) # :nodoc: - if const == :REGEXP - warn "Bundler::URI::REGEXP is obsolete. Use Bundler::URI::RFC2396_REGEXP explicitly.", uplevel: 1 if $VERBOSE - Bundler::URI::RFC2396_REGEXP - elsif value = RFC2396_PARSER.regexp[const] - warn "Bundler::URI::#{const} is obsolete. Use Bundler::URI::RFC2396_PARSER.regexp[#{const.inspect}] explicitly.", uplevel: 1 if $VERBOSE - value - elsif value = RFC2396_Parser.const_get(const) - warn "Bundler::URI::#{const} is obsolete. Use Bundler::URI::RFC2396_Parser::#{const} explicitly.", uplevel: 1 if $VERBOSE - value - else - super - end - end - - module Util # :nodoc: - def make_components_hash(klass, array_hash) - tmp = {} - if array_hash.kind_of?(Array) && - array_hash.size == klass.component.size - 1 - klass.component[1..-1].each_index do |i| - begin - tmp[klass.component[i + 1]] = array_hash[i].clone - rescue TypeError - tmp[klass.component[i + 1]] = array_hash[i] - end - end - - elsif array_hash.kind_of?(Hash) - array_hash.each do |key, value| - begin - tmp[key] = value.clone - rescue TypeError - tmp[key] = value - end - end - else - raise ArgumentError, - "expected Array of or Hash of components of #{klass} (#{klass.component[1..-1].join(', ')})" - end - tmp[:scheme] = klass.to_s.sub(/\A.*::/, '').downcase - - return tmp - end - module_function :make_components_hash - end - - module Schemes # :nodoc: - class << self - ReservedChars = ".+-" - EscapedChars = "\u01C0\u01C1\u01C2" - # Use Lo category chars as escaped chars for TruffleRuby, which - # does not allow Symbol categories as identifiers. - - def escape(name) - unless name and name.ascii_only? - return nil - end - name.upcase.tr(ReservedChars, EscapedChars) - end - - def unescape(name) - name.tr(EscapedChars, ReservedChars).encode(Encoding::US_ASCII).upcase - end - - def find(name) - const_get(name, false) if name and const_defined?(name, false) - end - - def register(name, klass) - unless scheme = escape(name) - raise ArgumentError, "invalid character as scheme - #{name}" - end - const_set(scheme, klass) - end - - def list - constants.map { |name| - [unescape(name.to_s), const_get(name)] - }.to_h - end - end - end - private_constant :Schemes - - # Registers the given +klass+ as the class to be instantiated - # when parsing a \Bundler::URI with the given +scheme+: - # - # Bundler::URI.register_scheme('MS_SEARCH', Bundler::URI::Generic) # => Bundler::URI::Generic - # Bundler::URI.scheme_list['MS_SEARCH'] # => Bundler::URI::Generic - # - # Note that after calling String#upcase on +scheme+, it must be a valid - # constant name. - def self.register_scheme(scheme, klass) - Schemes.register(scheme, klass) - end - - # Returns a hash of the defined schemes: - # - # Bundler::URI.scheme_list - # # => - # {"MAILTO"=>Bundler::URI::MailTo, - # "LDAPS"=>Bundler::URI::LDAPS, - # "WS"=>Bundler::URI::WS, - # "HTTP"=>Bundler::URI::HTTP, - # "HTTPS"=>Bundler::URI::HTTPS, - # "LDAP"=>Bundler::URI::LDAP, - # "FILE"=>Bundler::URI::File, - # "FTP"=>Bundler::URI::FTP} - # - # Related: Bundler::URI.register_scheme. - def self.scheme_list - Schemes.list - end - - # :stopdoc: - INITIAL_SCHEMES = scheme_list - private_constant :INITIAL_SCHEMES - Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor) - # :startdoc: - - # Returns a new object constructed from the given +scheme+, +arguments+, - # and +default+: - # - # - The new object is an instance of Bundler::URI.scheme_list[scheme.upcase]. - # - The object is initialized by calling the class initializer - # using +scheme+ and +arguments+. - # See Bundler::URI::Generic.new. - # - # Examples: - # - # values = ['john.doe', 'www.example.com', '123', nil, '/forum/questions/', nil, 'tag=networking&order=newest', 'top'] - # Bundler::URI.for('https', *values) - # # => # - # Bundler::URI.for('foo', *values, default: Bundler::URI::HTTP) - # # => # - # - def self.for(scheme, *arguments, default: Generic) - const_name = Schemes.escape(scheme) - - uri_class = INITIAL_SCHEMES[const_name] - uri_class ||= Schemes.find(const_name) - uri_class ||= default - - return uri_class.new(scheme, *arguments) - end - - # - # Base class for all Bundler::URI exceptions. - # - class Error < StandardError; end - # - # Not a Bundler::URI. - # - class InvalidURIError < Error; end - # - # Not a Bundler::URI component. - # - class InvalidComponentError < Error; end - # - # Bundler::URI is valid, bad usage is not. - # - class BadURIError < Error; end - - # Returns a 9-element array representing the parts of the \Bundler::URI - # formed from the string +uri+; - # each array element is a string or +nil+: - # - # names = %w[scheme userinfo host port registry path opaque query fragment] - # values = Bundler::URI.split('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') - # names.zip(values) - # # => - # [["scheme", "https"], - # ["userinfo", "john.doe"], - # ["host", "www.example.com"], - # ["port", "123"], - # ["registry", nil], - # ["path", "/forum/questions/"], - # ["opaque", nil], - # ["query", "tag=networking&order=newest"], - # ["fragment", "top"]] - # - def self.split(uri) - PARSER.split(uri) - end - - # Returns a new \Bundler::URI object constructed from the given string +uri+: - # - # Bundler::URI.parse('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') - # # => # - # Bundler::URI.parse('http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') - # # => # - # - # It's recommended to first Bundler::URI::RFC2396_PARSER.escape string +uri+ - # if it may contain invalid Bundler::URI characters. - # - def self.parse(uri) - PARSER.parse(uri) - end - - # Merges the given Bundler::URI strings +str+ - # per {RFC 2396}[https://www.rfc-editor.org/rfc/rfc2396.html]. - # - # Each string in +str+ is converted to an - # {RFC3986 Bundler::URI}[https://www.rfc-editor.org/rfc/rfc3986.html] before being merged. - # - # Examples: - # - # Bundler::URI.join("http://example.com/","main.rbx") - # # => # - # - # Bundler::URI.join('http://example.com', 'foo') - # # => # - # - # Bundler::URI.join('http://example.com', '/foo', '/bar') - # # => # - # - # Bundler::URI.join('http://example.com', '/foo', 'bar') - # # => # - # - # Bundler::URI.join('http://example.com', '/foo/', 'bar') - # # => # - # - def self.join(*str) - DEFAULT_PARSER.join(*str) - end - - # - # == Synopsis - # - # Bundler::URI::extract(str[, schemes][,&blk]) - # - # == Args - # - # +str+:: - # String to extract URIs from. - # +schemes+:: - # Limit Bundler::URI matching to specific schemes. - # - # == Description - # - # Extracts URIs from a string. If block given, iterates through all matched URIs. - # Returns nil if block given or array with matches. - # - # == Usage - # - # require "bundler/vendor/uri/lib/uri" - # - # Bundler::URI.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.") - # # => ["http://foo.example.com/bla", "mailto:test@example.com"] - # - def self.extract(str, schemes = nil, &block) # :nodoc: - warn "Bundler::URI.extract is obsolete", uplevel: 1 if $VERBOSE - PARSER.extract(str, schemes, &block) - end - - # - # == Synopsis - # - # Bundler::URI::regexp([match_schemes]) - # - # == Args - # - # +match_schemes+:: - # Array of schemes. If given, resulting regexp matches to URIs - # whose scheme is one of the match_schemes. - # - # == Description - # - # Returns a Regexp object which matches to Bundler::URI-like strings. - # The Regexp object returned by this method includes arbitrary - # number of capture group (parentheses). Never rely on its number. - # - # == Usage - # - # require 'bundler/vendor/uri/lib/uri' - # - # # extract first Bundler::URI from html_string - # html_string.slice(Bundler::URI.regexp) - # - # # remove ftp URIs - # html_string.sub(Bundler::URI.regexp(['ftp']), '') - # - # # You should not rely on the number of parentheses - # html_string.scan(Bundler::URI.regexp) do |*matches| - # p $& - # end - # - def self.regexp(schemes = nil)# :nodoc: - warn "Bundler::URI.regexp is obsolete", uplevel: 1 if $VERBOSE - PARSER.make_regexp(schemes) - end - - TBLENCWWWCOMP_ = {} # :nodoc: - 256.times do |i| - TBLENCWWWCOMP_[-i.chr] = -('%%%02X' % i) - end - TBLENCURICOMP_ = TBLENCWWWCOMP_.dup.freeze # :nodoc: - TBLENCWWWCOMP_[' '] = '+' - TBLENCWWWCOMP_.freeze - TBLDECWWWCOMP_ = {} # :nodoc: - 256.times do |i| - h, l = i>>4, i&15 - TBLDECWWWCOMP_[-('%%%X%X' % [h, l])] = -i.chr - TBLDECWWWCOMP_[-('%%%x%X' % [h, l])] = -i.chr - TBLDECWWWCOMP_[-('%%%X%x' % [h, l])] = -i.chr - TBLDECWWWCOMP_[-('%%%x%x' % [h, l])] = -i.chr - end - TBLDECWWWCOMP_['+'] = ' ' - TBLDECWWWCOMP_.freeze - - # Returns a URL-encoded string derived from the given string +str+. - # - # The returned string: - # - # - Preserves: - # - # - Characters '*', '.', '-', and '_'. - # - Character in ranges 'a'..'z', 'A'..'Z', - # and '0'..'9'. - # - # Example: - # - # Bundler::URI.encode_www_form_component('*.-_azAZ09') - # # => "*.-_azAZ09" - # - # - Converts: - # - # - Character ' ' to character '+'. - # - Any other character to "percent notation"; - # the percent notation for character c is '%%%X' % c.ord. - # - # Example: - # - # Bundler::URI.encode_www_form_component('Here are some punctuation characters: ,;?:') - # # => "Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A" - # - # Encoding: - # - # - If +str+ has encoding Encoding::ASCII_8BIT, argument +enc+ is ignored. - # - Otherwise +str+ is converted first to Encoding::UTF_8 - # (with suitable character replacements), - # and then to encoding +enc+. - # - # In either case, the returned string has forced encoding Encoding::US_ASCII. - # - # Related: Bundler::URI.encode_uri_component (encodes ' ' as '%20'). - def self.encode_www_form_component(str, enc=nil) - _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP_, str, enc) - end - - # Returns a string decoded from the given \URL-encoded string +str+. - # - # The given string is first encoded as Encoding::ASCII-8BIT (using String#b), - # then decoded (as below), and finally force-encoded to the given encoding +enc+. - # - # The returned string: - # - # - Preserves: - # - # - Characters '*', '.', '-', and '_'. - # - Character in ranges 'a'..'z', 'A'..'Z', - # and '0'..'9'. - # - # Example: - # - # Bundler::URI.decode_www_form_component('*.-_azAZ09') - # # => "*.-_azAZ09" - # - # - Converts: - # - # - Character '+' to character ' '. - # - Each "percent notation" to an ASCII character. - # - # Example: - # - # Bundler::URI.decode_www_form_component('Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A') - # # => "Here are some punctuation characters: ,;?:" - # - # Related: Bundler::URI.decode_uri_component (preserves '+'). - def self.decode_www_form_component(str, enc=Encoding::UTF_8) - _decode_uri_component(/\+|%\h\h/, str, enc) - end - - # Like Bundler::URI.encode_www_form_component, except that ' ' (space) - # is encoded as '%20' (instead of '+'). - def self.encode_uri_component(str, enc=nil) - _encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCURICOMP_, str, enc) - end - - # Like Bundler::URI.decode_www_form_component, except that '+' is preserved. - def self.decode_uri_component(str, enc=Encoding::UTF_8) - _decode_uri_component(/%\h\h/, str, enc) - end - - # Returns a string derived from the given string +str+ with - # Bundler::URI-encoded characters matching +regexp+ according to +table+. - def self._encode_uri_component(regexp, table, str, enc) - str = str.to_s.dup - if str.encoding != Encoding::ASCII_8BIT - if enc && enc != Encoding::ASCII_8BIT - str.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace) - str.encode!(enc, fallback: ->(x){"&##{x.ord};"}) - end - str.force_encoding(Encoding::ASCII_8BIT) - end - str.gsub!(regexp, table) - str.force_encoding(Encoding::US_ASCII) - end - private_class_method :_encode_uri_component - - # Returns a string decoding characters matching +regexp+ from the - # given \URL-encoded string +str+. - def self._decode_uri_component(regexp, str, enc) - raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/.match?(str) - str.b.gsub(regexp, TBLDECWWWCOMP_).force_encoding(enc) - end - private_class_method :_decode_uri_component - - # Returns a URL-encoded string derived from the given - # {Enumerable}[rdoc-ref:Enumerable@Enumerable+in+Ruby+Classes] - # +enum+. - # - # The result is suitable for use as form data - # for an \HTTP request whose Content-Type is - # 'application/x-www-form-urlencoded'. - # - # The returned string consists of the elements of +enum+, - # each converted to one or more URL-encoded strings, - # and all joined with character '&'. - # - # Simple examples: - # - # Bundler::URI.encode_www_form([['foo', 0], ['bar', 1], ['baz', 2]]) - # # => "foo=0&bar=1&baz=2" - # Bundler::URI.encode_www_form({foo: 0, bar: 1, baz: 2}) - # # => "foo=0&bar=1&baz=2" - # - # The returned string is formed using method Bundler::URI.encode_www_form_component, - # which converts certain characters: - # - # Bundler::URI.encode_www_form('f#o': '/', 'b-r': '$', 'b z': '@') - # # => "f%23o=%2F&b-r=%24&b+z=%40" - # - # When +enum+ is Array-like, each element +ele+ is converted to a field: - # - # - If +ele+ is an array of two or more elements, - # the field is formed from its first two elements - # (and any additional elements are ignored): - # - # name = Bundler::URI.encode_www_form_component(ele[0], enc) - # value = Bundler::URI.encode_www_form_component(ele[1], enc) - # "#{name}=#{value}" - # - # Examples: - # - # Bundler::URI.encode_www_form([%w[foo bar], %w[baz bat bah]]) - # # => "foo=bar&baz=bat" - # Bundler::URI.encode_www_form([['foo', 0], ['bar', :baz, 'bat']]) - # # => "foo=0&bar=baz" - # - # - If +ele+ is an array of one element, - # the field is formed from ele[0]: - # - # Bundler::URI.encode_www_form_component(ele[0]) - # - # Example: - # - # Bundler::URI.encode_www_form([['foo'], [:bar], [0]]) - # # => "foo&bar&0" - # - # - Otherwise the field is formed from +ele+: - # - # Bundler::URI.encode_www_form_component(ele) - # - # Example: - # - # Bundler::URI.encode_www_form(['foo', :bar, 0]) - # # => "foo&bar&0" - # - # The elements of an Array-like +enum+ may be mixture: - # - # Bundler::URI.encode_www_form([['foo', 0], ['bar', 1, 2], ['baz'], :bat]) - # # => "foo=0&bar=1&baz&bat" - # - # When +enum+ is Hash-like, - # each +key+/+value+ pair is converted to one or more fields: - # - # - If +value+ is - # {Array-convertible}[rdoc-ref:implicit_conversion.rdoc@Array-Convertible+Objects], - # each element +ele+ in +value+ is paired with +key+ to form a field: - # - # name = Bundler::URI.encode_www_form_component(key, enc) - # value = Bundler::URI.encode_www_form_component(ele, enc) - # "#{name}=#{value}" - # - # Example: - # - # Bundler::URI.encode_www_form({foo: [:bar, 1], baz: [:bat, :bam, 2]}) - # # => "foo=bar&foo=1&baz=bat&baz=bam&baz=2" - # - # - Otherwise, +key+ and +value+ are paired to form a field: - # - # name = Bundler::URI.encode_www_form_component(key, enc) - # value = Bundler::URI.encode_www_form_component(value, enc) - # "#{name}=#{value}" - # - # Example: - # - # Bundler::URI.encode_www_form({foo: 0, bar: 1, baz: 2}) - # # => "foo=0&bar=1&baz=2" - # - # The elements of a Hash-like +enum+ may be mixture: - # - # Bundler::URI.encode_www_form({foo: [0, 1], bar: 2}) - # # => "foo=0&foo=1&bar=2" - # - def self.encode_www_form(enum, enc=nil) - enum.map do |k,v| - if v.nil? - encode_www_form_component(k, enc) - elsif v.respond_to?(:to_ary) - v.to_ary.map do |w| - str = encode_www_form_component(k, enc) - unless w.nil? - str << '=' - str << encode_www_form_component(w, enc) - end - end.join('&') - else - str = encode_www_form_component(k, enc) - str << '=' - str << encode_www_form_component(v, enc) - end - end.join('&') - end - - # Returns name/value pairs derived from the given string +str+, - # which must be an ASCII string. - # - # The method may be used to decode the body of Net::HTTPResponse object +res+ - # for which res['Content-Type'] is 'application/x-www-form-urlencoded'. - # - # The returned data is an array of 2-element subarrays; - # each subarray is a name/value pair (both are strings). - # Each returned string has encoding +enc+, - # and has had invalid characters removed via - # {String#scrub}[rdoc-ref:String#scrub]. - # - # A simple example: - # - # Bundler::URI.decode_www_form('foo=0&bar=1&baz') - # # => [["foo", "0"], ["bar", "1"], ["baz", ""]] - # - # The returned strings have certain conversions, - # similar to those performed in Bundler::URI.decode_www_form_component: - # - # Bundler::URI.decode_www_form('f%23o=%2F&b-r=%24&b+z=%40') - # # => [["f#o", "/"], ["b-r", "$"], ["b z", "@"]] - # - # The given string may contain consecutive separators: - # - # Bundler::URI.decode_www_form('foo=0&&bar=1&&baz=2') - # # => [["foo", "0"], ["", ""], ["bar", "1"], ["", ""], ["baz", "2"]] - # - # A different separator may be specified: - # - # Bundler::URI.decode_www_form('foo=0--bar=1--baz', separator: '--') - # # => [["foo", "0"], ["bar", "1"], ["baz", ""]] - # - def self.decode_www_form(str, enc=Encoding::UTF_8, separator: '&', use__charset_: false, isindex: false) - raise ArgumentError, "the input of #{self.name}.#{__method__} must be ASCII only string" unless str.ascii_only? - ary = [] - return ary if str.empty? - enc = Encoding.find(enc) - str.b.each_line(separator) do |string| - string.chomp!(separator) - key, sep, val = string.partition('=') - if isindex - if sep.empty? - val = key - key = +'' - end - isindex = false - end - - if use__charset_ and key == '_charset_' and e = get_encoding(val) - enc = e - use__charset_ = false - end - - key.gsub!(/\+|%\h\h/, TBLDECWWWCOMP_) - if val - val.gsub!(/\+|%\h\h/, TBLDECWWWCOMP_) - else - val = +'' - end - - ary << [key, val] - end - ary.each do |k, v| - k.force_encoding(enc) - k.scrub! - v.force_encoding(enc) - v.scrub! - end - ary - end - - private -=begin command for WEB_ENCODINGS_ - curl https://encoding.spec.whatwg.org/encodings.json| - ruby -rjson -e 'H={} - h={ - "shift_jis"=>"Windows-31J", - "euc-jp"=>"cp51932", - "iso-2022-jp"=>"cp50221", - "x-mac-cyrillic"=>"macCyrillic", - } - JSON($<.read).map{|x|x["encodings"]}.flatten.each{|x| - Encoding.find(n=h.fetch(n=x["name"].downcase,n))rescue next - x["labels"].each{|y|H[y]=n} - } - puts "{" - H.each{|k,v|puts %[ #{k.dump}=>#{v.dump},]} - puts "}" -' -=end - WEB_ENCODINGS_ = { - "unicode-1-1-utf-8"=>"utf-8", - "utf-8"=>"utf-8", - "utf8"=>"utf-8", - "866"=>"ibm866", - "cp866"=>"ibm866", - "csibm866"=>"ibm866", - "ibm866"=>"ibm866", - "csisolatin2"=>"iso-8859-2", - "iso-8859-2"=>"iso-8859-2", - "iso-ir-101"=>"iso-8859-2", - "iso8859-2"=>"iso-8859-2", - "iso88592"=>"iso-8859-2", - "iso_8859-2"=>"iso-8859-2", - "iso_8859-2:1987"=>"iso-8859-2", - "l2"=>"iso-8859-2", - "latin2"=>"iso-8859-2", - "csisolatin3"=>"iso-8859-3", - "iso-8859-3"=>"iso-8859-3", - "iso-ir-109"=>"iso-8859-3", - "iso8859-3"=>"iso-8859-3", - "iso88593"=>"iso-8859-3", - "iso_8859-3"=>"iso-8859-3", - "iso_8859-3:1988"=>"iso-8859-3", - "l3"=>"iso-8859-3", - "latin3"=>"iso-8859-3", - "csisolatin4"=>"iso-8859-4", - "iso-8859-4"=>"iso-8859-4", - "iso-ir-110"=>"iso-8859-4", - "iso8859-4"=>"iso-8859-4", - "iso88594"=>"iso-8859-4", - "iso_8859-4"=>"iso-8859-4", - "iso_8859-4:1988"=>"iso-8859-4", - "l4"=>"iso-8859-4", - "latin4"=>"iso-8859-4", - "csisolatincyrillic"=>"iso-8859-5", - "cyrillic"=>"iso-8859-5", - "iso-8859-5"=>"iso-8859-5", - "iso-ir-144"=>"iso-8859-5", - "iso8859-5"=>"iso-8859-5", - "iso88595"=>"iso-8859-5", - "iso_8859-5"=>"iso-8859-5", - "iso_8859-5:1988"=>"iso-8859-5", - "arabic"=>"iso-8859-6", - "asmo-708"=>"iso-8859-6", - "csiso88596e"=>"iso-8859-6", - "csiso88596i"=>"iso-8859-6", - "csisolatinarabic"=>"iso-8859-6", - "ecma-114"=>"iso-8859-6", - "iso-8859-6"=>"iso-8859-6", - "iso-8859-6-e"=>"iso-8859-6", - "iso-8859-6-i"=>"iso-8859-6", - "iso-ir-127"=>"iso-8859-6", - "iso8859-6"=>"iso-8859-6", - "iso88596"=>"iso-8859-6", - "iso_8859-6"=>"iso-8859-6", - "iso_8859-6:1987"=>"iso-8859-6", - "csisolatingreek"=>"iso-8859-7", - "ecma-118"=>"iso-8859-7", - "elot_928"=>"iso-8859-7", - "greek"=>"iso-8859-7", - "greek8"=>"iso-8859-7", - "iso-8859-7"=>"iso-8859-7", - "iso-ir-126"=>"iso-8859-7", - "iso8859-7"=>"iso-8859-7", - "iso88597"=>"iso-8859-7", - "iso_8859-7"=>"iso-8859-7", - "iso_8859-7:1987"=>"iso-8859-7", - "sun_eu_greek"=>"iso-8859-7", - "csiso88598e"=>"iso-8859-8", - "csisolatinhebrew"=>"iso-8859-8", - "hebrew"=>"iso-8859-8", - "iso-8859-8"=>"iso-8859-8", - "iso-8859-8-e"=>"iso-8859-8", - "iso-ir-138"=>"iso-8859-8", - "iso8859-8"=>"iso-8859-8", - "iso88598"=>"iso-8859-8", - "iso_8859-8"=>"iso-8859-8", - "iso_8859-8:1988"=>"iso-8859-8", - "visual"=>"iso-8859-8", - "csisolatin6"=>"iso-8859-10", - "iso-8859-10"=>"iso-8859-10", - "iso-ir-157"=>"iso-8859-10", - "iso8859-10"=>"iso-8859-10", - "iso885910"=>"iso-8859-10", - "l6"=>"iso-8859-10", - "latin6"=>"iso-8859-10", - "iso-8859-13"=>"iso-8859-13", - "iso8859-13"=>"iso-8859-13", - "iso885913"=>"iso-8859-13", - "iso-8859-14"=>"iso-8859-14", - "iso8859-14"=>"iso-8859-14", - "iso885914"=>"iso-8859-14", - "csisolatin9"=>"iso-8859-15", - "iso-8859-15"=>"iso-8859-15", - "iso8859-15"=>"iso-8859-15", - "iso885915"=>"iso-8859-15", - "iso_8859-15"=>"iso-8859-15", - "l9"=>"iso-8859-15", - "iso-8859-16"=>"iso-8859-16", - "cskoi8r"=>"koi8-r", - "koi"=>"koi8-r", - "koi8"=>"koi8-r", - "koi8-r"=>"koi8-r", - "koi8_r"=>"koi8-r", - "koi8-ru"=>"koi8-u", - "koi8-u"=>"koi8-u", - "dos-874"=>"windows-874", - "iso-8859-11"=>"windows-874", - "iso8859-11"=>"windows-874", - "iso885911"=>"windows-874", - "tis-620"=>"windows-874", - "windows-874"=>"windows-874", - "cp1250"=>"windows-1250", - "windows-1250"=>"windows-1250", - "x-cp1250"=>"windows-1250", - "cp1251"=>"windows-1251", - "windows-1251"=>"windows-1251", - "x-cp1251"=>"windows-1251", - "ansi_x3.4-1968"=>"windows-1252", - "ascii"=>"windows-1252", - "cp1252"=>"windows-1252", - "cp819"=>"windows-1252", - "csisolatin1"=>"windows-1252", - "ibm819"=>"windows-1252", - "iso-8859-1"=>"windows-1252", - "iso-ir-100"=>"windows-1252", - "iso8859-1"=>"windows-1252", - "iso88591"=>"windows-1252", - "iso_8859-1"=>"windows-1252", - "iso_8859-1:1987"=>"windows-1252", - "l1"=>"windows-1252", - "latin1"=>"windows-1252", - "us-ascii"=>"windows-1252", - "windows-1252"=>"windows-1252", - "x-cp1252"=>"windows-1252", - "cp1253"=>"windows-1253", - "windows-1253"=>"windows-1253", - "x-cp1253"=>"windows-1253", - "cp1254"=>"windows-1254", - "csisolatin5"=>"windows-1254", - "iso-8859-9"=>"windows-1254", - "iso-ir-148"=>"windows-1254", - "iso8859-9"=>"windows-1254", - "iso88599"=>"windows-1254", - "iso_8859-9"=>"windows-1254", - "iso_8859-9:1989"=>"windows-1254", - "l5"=>"windows-1254", - "latin5"=>"windows-1254", - "windows-1254"=>"windows-1254", - "x-cp1254"=>"windows-1254", - "cp1255"=>"windows-1255", - "windows-1255"=>"windows-1255", - "x-cp1255"=>"windows-1255", - "cp1256"=>"windows-1256", - "windows-1256"=>"windows-1256", - "x-cp1256"=>"windows-1256", - "cp1257"=>"windows-1257", - "windows-1257"=>"windows-1257", - "x-cp1257"=>"windows-1257", - "cp1258"=>"windows-1258", - "windows-1258"=>"windows-1258", - "x-cp1258"=>"windows-1258", - "x-mac-cyrillic"=>"macCyrillic", - "x-mac-ukrainian"=>"macCyrillic", - "chinese"=>"gbk", - "csgb2312"=>"gbk", - "csiso58gb231280"=>"gbk", - "gb2312"=>"gbk", - "gb_2312"=>"gbk", - "gb_2312-80"=>"gbk", - "gbk"=>"gbk", - "iso-ir-58"=>"gbk", - "x-gbk"=>"gbk", - "gb18030"=>"gb18030", - "big5"=>"big5", - "big5-hkscs"=>"big5", - "cn-big5"=>"big5", - "csbig5"=>"big5", - "x-x-big5"=>"big5", - "cseucpkdfmtjapanese"=>"cp51932", - "euc-jp"=>"cp51932", - "x-euc-jp"=>"cp51932", - "csiso2022jp"=>"cp50221", - "iso-2022-jp"=>"cp50221", - "csshiftjis"=>"Windows-31J", - "ms932"=>"Windows-31J", - "ms_kanji"=>"Windows-31J", - "shift-jis"=>"Windows-31J", - "shift_jis"=>"Windows-31J", - "sjis"=>"Windows-31J", - "windows-31j"=>"Windows-31J", - "x-sjis"=>"Windows-31J", - "cseuckr"=>"euc-kr", - "csksc56011987"=>"euc-kr", - "euc-kr"=>"euc-kr", - "iso-ir-149"=>"euc-kr", - "korean"=>"euc-kr", - "ks_c_5601-1987"=>"euc-kr", - "ks_c_5601-1989"=>"euc-kr", - "ksc5601"=>"euc-kr", - "ksc_5601"=>"euc-kr", - "windows-949"=>"euc-kr", - "utf-16be"=>"utf-16be", - "utf-16"=>"utf-16le", - "utf-16le"=>"utf-16le", - } # :nodoc: - Ractor.make_shareable(WEB_ENCODINGS_) if defined?(Ractor) - - # :nodoc: - # return encoding or nil - # http://encoding.spec.whatwg.org/#concept-encoding-get - def self.get_encoding(label) - Encoding.find(WEB_ENCODINGS_[label.to_str.strip.downcase]) rescue nil - end -end # module Bundler::URI - -module Bundler - - # - # Returns a \Bundler::URI object derived from the given +uri+, - # which may be a \Bundler::URI string or an existing \Bundler::URI object: - # - # require 'bundler/vendor/uri/lib/uri' - # # Returns a new Bundler::URI. - # uri = Bundler::URI('http://github.com/ruby/ruby') - # # => # - # # Returns the given Bundler::URI. - # Bundler::URI(uri) - # # => # - # - # You must require 'bundler/vendor/uri/lib/uri' to use this method. - # - def URI(uri) - if uri.is_a?(Bundler::URI::Generic) - uri - elsif uri = String.try_convert(uri) - Bundler::URI.parse(uri) - else - raise ArgumentError, - "bad argument (expected Bundler::URI object or Bundler::URI string)" - end - end - module_function :URI -end diff --git a/lib/bundler/vendor/uri/lib/uri/file.rb b/lib/bundler/vendor/uri/lib/uri/file.rb deleted file mode 100644 index 21dd9ee5356f0e..00000000000000 --- a/lib/bundler/vendor/uri/lib/uri/file.rb +++ /dev/null @@ -1,100 +0,0 @@ -# frozen_string_literal: true - -require_relative 'generic' - -module Bundler::URI - - # - # The "file" Bundler::URI is defined by RFC8089. - # - class File < Generic - # A Default port of nil for Bundler::URI::File. - DEFAULT_PORT = nil - - # - # An Array of the available components for Bundler::URI::File. - # - COMPONENT = [ - :scheme, - :host, - :path - ].freeze - - # - # == Description - # - # Creates a new Bundler::URI::File object from components, with syntax checking. - # - # The components accepted are +host+ and +path+. - # - # The components should be provided either as an Array, or as a Hash - # with keys formed by preceding the component names with a colon. - # - # If an Array is used, the components must be passed in the - # order [host, path]. - # - # A path from e.g. the File class should be escaped before - # being passed. - # - # Examples: - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri1 = Bundler::URI::File.build(['host.example.com', '/path/file.zip']) - # uri1.to_s # => "file://host.example.com/path/file.zip" - # - # uri2 = Bundler::URI::File.build({:host => 'host.example.com', - # :path => '/ruby/src'}) - # uri2.to_s # => "file://host.example.com/ruby/src" - # - # uri3 = Bundler::URI::File.build({:path => Bundler::URI::RFC2396_PARSER.escape('/path/my file.txt')}) - # uri3.to_s # => "file:///path/my%20file.txt" - # - def self.build(args) - tmp = Util::make_components_hash(self, args) - super(tmp) - end - - # Protected setter for the host component +v+. - # - # See also Bundler::URI::Generic.host=. - # - def set_host(v) - v = "" if v.nil? || v == "localhost" - @host = v - end - - # do nothing - def set_port(v) - end - - # raise InvalidURIError - def check_userinfo(user) - raise Bundler::URI::InvalidURIError, "cannot set userinfo for file Bundler::URI" - end - - # raise InvalidURIError - def check_user(user) - raise Bundler::URI::InvalidURIError, "cannot set user for file Bundler::URI" - end - - # raise InvalidURIError - def check_password(user) - raise Bundler::URI::InvalidURIError, "cannot set password for file Bundler::URI" - end - - # do nothing - def set_userinfo(v) - end - - # do nothing - def set_user(v) - end - - # do nothing - def set_password(v) - end - end - - register_scheme 'FILE', File -end diff --git a/lib/bundler/vendor/uri/lib/uri/ftp.rb b/lib/bundler/vendor/uri/lib/uri/ftp.rb deleted file mode 100644 index f83985fd3de278..00000000000000 --- a/lib/bundler/vendor/uri/lib/uri/ftp.rb +++ /dev/null @@ -1,267 +0,0 @@ -# frozen_string_literal: false -# = uri/ftp.rb -# -# Author:: Akira Yamada -# License:: You can redistribute it and/or modify it under the same term as Ruby. -# -# See Bundler::URI for general documentation -# - -require_relative 'generic' - -module Bundler::URI - - # - # FTP Bundler::URI syntax is defined by RFC1738 section 3.2. - # - # This class will be redesigned because of difference of implementations; - # the structure of its path. draft-hoffman-ftp-uri-04 is a draft but it - # is a good summary about the de facto spec. - # https://datatracker.ietf.org/doc/html/draft-hoffman-ftp-uri-04 - # - class FTP < Generic - # A Default port of 21 for Bundler::URI::FTP. - DEFAULT_PORT = 21 - - # - # An Array of the available components for Bundler::URI::FTP. - # - COMPONENT = [ - :scheme, - :userinfo, :host, :port, - :path, :typecode - ].freeze - - # - # Typecode is "a", "i", or "d". - # - # * "a" indicates a text file (the FTP command was ASCII) - # * "i" indicates a binary file (FTP command IMAGE) - # * "d" indicates the contents of a directory should be displayed - # - TYPECODE = ['a', 'i', 'd'].freeze - - # Typecode prefix ";type=". - TYPECODE_PREFIX = ';type='.freeze - - def self.new2(user, password, host, port, path, - typecode = nil, arg_check = true) # :nodoc: - # Do not use this method! Not tested. [Bug #7301] - # This methods remains just for compatibility, - # Keep it undocumented until the active maintainer is assigned. - typecode = nil if typecode.size == 0 - if typecode && !TYPECODE.include?(typecode) - raise ArgumentError, - "bad typecode is specified: #{typecode}" - end - - # do escape - - self.new('ftp', - [user, password], - host, port, nil, - typecode ? path + TYPECODE_PREFIX + typecode : path, - nil, nil, nil, arg_check) - end - - # - # == Description - # - # Creates a new Bundler::URI::FTP object from components, with syntax checking. - # - # The components accepted are +userinfo+, +host+, +port+, +path+, and - # +typecode+. - # - # The components should be provided either as an Array, or as a Hash - # with keys formed by preceding the component names with a colon. - # - # If an Array is used, the components must be passed in the - # order [userinfo, host, port, path, typecode]. - # - # If the path supplied is absolute, it will be escaped in order to - # make it absolute in the Bundler::URI. - # - # Examples: - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri1 = Bundler::URI::FTP.build(['user:password', 'ftp.example.com', nil, - # '/path/file.zip', 'i']) - # uri1.to_s # => "ftp://user:password@ftp.example.com/%2Fpath/file.zip;type=i" - # - # uri2 = Bundler::URI::FTP.build({:host => 'ftp.example.com', - # :path => 'ruby/src'}) - # uri2.to_s # => "ftp://ftp.example.com/ruby/src" - # - def self.build(args) - - # Fix the incoming path to be generic URL syntax - # FTP path -> URL path - # foo/bar /foo/bar - # /foo/bar /%2Ffoo/bar - # - if args.kind_of?(Array) - args[3] = '/' + args[3].sub(/^\//, '%2F') - else - args[:path] = '/' + args[:path].sub(/^\//, '%2F') - end - - tmp = Util::make_components_hash(self, args) - - if tmp[:typecode] - if tmp[:typecode].size == 1 - tmp[:typecode] = TYPECODE_PREFIX + tmp[:typecode] - end - tmp[:path] << tmp[:typecode] - end - - return super(tmp) - end - - # - # == Description - # - # Creates a new Bundler::URI::FTP object from generic URL components with no - # syntax checking. - # - # Unlike build(), this method does not escape the path component as - # required by RFC1738; instead it is treated as per RFC2396. - # - # Arguments are +scheme+, +userinfo+, +host+, +port+, +registry+, +path+, - # +opaque+, +query+, and +fragment+, in that order. - # - def initialize(scheme, - userinfo, host, port, registry, - path, opaque, - query, - fragment, - parser = nil, - arg_check = false) - raise InvalidURIError unless path - path = path.sub(/^\//,'') - path.sub!(/^%2F/,'/') - super(scheme, userinfo, host, port, registry, path, opaque, - query, fragment, parser, arg_check) - @typecode = nil - if tmp = @path.index(TYPECODE_PREFIX) - typecode = @path[tmp + TYPECODE_PREFIX.size..-1] - @path = @path[0..tmp - 1] - - if arg_check - self.typecode = typecode - else - self.set_typecode(typecode) - end - end - end - - # typecode accessor. - # - # See Bundler::URI::FTP::COMPONENT. - attr_reader :typecode - - # Validates typecode +v+, - # returns +true+ or +false+. - # - def check_typecode(v) - if TYPECODE.include?(v) - return true - else - raise InvalidComponentError, - "bad typecode(expected #{TYPECODE.join(', ')}): #{v}" - end - end - private :check_typecode - - # Private setter for the typecode +v+. - # - # See also Bundler::URI::FTP.typecode=. - # - def set_typecode(v) - @typecode = v - end - protected :set_typecode - - # - # == Args - # - # +v+:: - # String - # - # == Description - # - # Public setter for the typecode +v+ - # (with validation). - # - # See also Bundler::URI::FTP.check_typecode. - # - # == Usage - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri = Bundler::URI.parse("ftp://john@ftp.example.com/my_file.img") - # #=> # - # uri.typecode = "i" - # uri - # #=> # - # - def typecode=(typecode) - check_typecode(typecode) - set_typecode(typecode) - typecode - end - - def merge(oth) # :nodoc: - tmp = super(oth) - if self != tmp - tmp.set_typecode(oth.typecode) - end - - return tmp - end - - # Returns the path from an FTP Bundler::URI. - # - # RFC 1738 specifically states that the path for an FTP Bundler::URI does not - # include the / which separates the Bundler::URI path from the Bundler::URI host. Example: - # - # ftp://ftp.example.com/pub/ruby - # - # The above Bundler::URI indicates that the client should connect to - # ftp.example.com then cd to pub/ruby from the initial login directory. - # - # If you want to cd to an absolute directory, you must include an - # escaped / (%2F) in the path. Example: - # - # ftp://ftp.example.com/%2Fpub/ruby - # - # This method will then return "/pub/ruby". - # - def path - return @path.sub(/^\//,'').sub(/^%2F/,'/') - end - - # Private setter for the path of the Bundler::URI::FTP. - def set_path(v) - super("/" + v.sub(/^\//, "%2F")) - end - protected :set_path - - # Returns a String representation of the Bundler::URI::FTP. - def to_s - save_path = nil - if @typecode - save_path = @path - @path = @path + TYPECODE_PREFIX + @typecode - end - str = super - if @typecode - @path = save_path - end - - return str - end - end - - register_scheme 'FTP', FTP -end diff --git a/lib/bundler/vendor/uri/lib/uri/generic.rb b/lib/bundler/vendor/uri/lib/uri/generic.rb deleted file mode 100644 index 30dab609035bab..00000000000000 --- a/lib/bundler/vendor/uri/lib/uri/generic.rb +++ /dev/null @@ -1,1592 +0,0 @@ -# frozen_string_literal: true - -# = uri/generic.rb -# -# Author:: Akira Yamada -# License:: You can redistribute it and/or modify it under the same term as Ruby. -# -# See Bundler::URI for general documentation -# - -require_relative 'common' -autoload :IPSocket, 'socket' -autoload :IPAddr, 'ipaddr' - -module Bundler::URI - - # - # Base class for all Bundler::URI classes. - # Implements generic Bundler::URI syntax as per RFC 2396. - # - class Generic - include Bundler::URI - - # - # A Default port of nil for Bundler::URI::Generic. - # - DEFAULT_PORT = nil - - # - # Returns default port. - # - def self.default_port - self::DEFAULT_PORT - end - - # - # Returns default port. - # - def default_port - self.class.default_port - end - - # - # An Array of the available components for Bundler::URI::Generic. - # - COMPONENT = [ - :scheme, - :userinfo, :host, :port, :registry, - :path, :opaque, - :query, - :fragment - ].freeze - - # - # Components of the Bundler::URI in the order. - # - def self.component - self::COMPONENT - end - - USE_REGISTRY = false # :nodoc: - - def self.use_registry # :nodoc: - self::USE_REGISTRY - end - - # - # == Synopsis - # - # See ::new. - # - # == Description - # - # At first, tries to create a new Bundler::URI::Generic instance using - # Bundler::URI::Generic::build. But, if exception Bundler::URI::InvalidComponentError is raised, - # then it does Bundler::URI::RFC2396_PARSER.escape all Bundler::URI components and tries again. - # - def self.build2(args) - begin - return self.build(args) - rescue InvalidComponentError - if args.kind_of?(Array) - return self.build(args.collect{|x| - if x.is_a?(String) - Bundler::URI::RFC2396_PARSER.escape(x) - else - x - end - }) - elsif args.kind_of?(Hash) - tmp = {} - args.each do |key, value| - tmp[key] = if value - Bundler::URI::RFC2396_PARSER.escape(value) - else - value - end - end - return self.build(tmp) - end - end - end - - # - # == Synopsis - # - # See ::new. - # - # == Description - # - # Creates a new Bundler::URI::Generic instance from components of Bundler::URI::Generic - # with check. Components are: scheme, userinfo, host, port, registry, path, - # opaque, query, and fragment. You can provide arguments either by an Array or a Hash. - # See ::new for hash keys to use or for order of array items. - # - def self.build(args) - if args.kind_of?(Array) && - args.size == ::Bundler::URI::Generic::COMPONENT.size - tmp = args.dup - elsif args.kind_of?(Hash) - tmp = ::Bundler::URI::Generic::COMPONENT.collect do |c| - if args.include?(c) - args[c] - else - nil - end - end - else - component = self.component rescue ::Bundler::URI::Generic::COMPONENT - raise ArgumentError, - "expected Array of or Hash of components of #{self} (#{component.join(', ')})" - end - - tmp << nil - tmp << true - return self.new(*tmp) - end - - # - # == Args - # - # +scheme+:: - # Protocol scheme, i.e. 'http','ftp','mailto' and so on. - # +userinfo+:: - # User name and password, i.e. 'sdmitry:bla'. - # +host+:: - # Server host name. - # +port+:: - # Server port. - # +registry+:: - # Registry of naming authorities. - # +path+:: - # Path on server. - # +opaque+:: - # Opaque part. - # +query+:: - # Query data. - # +fragment+:: - # Part of the Bundler::URI after '#' character. - # +parser+:: - # Parser for internal use [Bundler::URI::DEFAULT_PARSER by default]. - # +arg_check+:: - # Check arguments [false by default]. - # - # == Description - # - # Creates a new Bundler::URI::Generic instance from ``generic'' components without check. - # - def initialize(scheme, - userinfo, host, port, registry, - path, opaque, - query, - fragment, - parser = DEFAULT_PARSER, - arg_check = false) - @scheme = nil - @user = nil - @password = nil - @host = nil - @port = nil - @path = nil - @query = nil - @opaque = nil - @fragment = nil - @parser = parser == DEFAULT_PARSER ? nil : parser - - if arg_check - self.scheme = scheme - self.hostname = host - self.port = port - self.userinfo = userinfo - self.path = path - self.query = query - self.opaque = opaque - self.fragment = fragment - else - self.set_scheme(scheme) - self.set_host(host) - self.set_port(port) - self.set_userinfo(userinfo) - self.set_path(path) - self.query = query - self.set_opaque(opaque) - self.fragment=(fragment) - end - if registry - raise InvalidURIError, - "the scheme #{@scheme} does not accept registry part: #{registry} (or bad hostname?)" - end - - @scheme&.freeze - self.set_path('') if !@path && !@opaque # (see RFC2396 Section 5.2) - self.set_port(self.default_port) if self.default_port && !@port - end - - # - # Returns the scheme component of the Bundler::URI. - # - # Bundler::URI("http://foo/bar/baz").scheme #=> "http" - # - attr_reader :scheme - - # Returns the host component of the Bundler::URI. - # - # Bundler::URI("http://foo/bar/baz").host #=> "foo" - # - # It returns nil if no host component exists. - # - # Bundler::URI("mailto:foo@example.org").host #=> nil - # - # The component does not contain the port number. - # - # Bundler::URI("http://foo:8080/bar/baz").host #=> "foo" - # - # Since IPv6 addresses are wrapped with brackets in URIs, - # this method returns IPv6 addresses wrapped with brackets. - # This form is not appropriate to pass to socket methods such as TCPSocket.open. - # If unwrapped host names are required, use the #hostname method. - # - # Bundler::URI("http://[::1]/bar/baz").host #=> "[::1]" - # Bundler::URI("http://[::1]/bar/baz").hostname #=> "::1" - # - attr_reader :host - - # Returns the port component of the Bundler::URI. - # - # Bundler::URI("http://foo/bar/baz").port #=> 80 - # Bundler::URI("http://foo:8080/bar/baz").port #=> 8080 - # - attr_reader :port - - def registry # :nodoc: - nil - end - - # Returns the path component of the Bundler::URI. - # - # Bundler::URI("http://foo/bar/baz").path #=> "/bar/baz" - # - attr_reader :path - - # Returns the query component of the Bundler::URI. - # - # Bundler::URI("http://foo/bar/baz?search=FooBar").query #=> "search=FooBar" - # - attr_reader :query - - # Returns the opaque part of the Bundler::URI. - # - # Bundler::URI("mailto:foo@example.org").opaque #=> "foo@example.org" - # Bundler::URI("http://foo/bar/baz").opaque #=> nil - # - # The portion of the path that does not make use of the slash '/'. - # The path typically refers to an absolute path or an opaque part. - # (See RFC2396 Section 3 and 5.2.) - # - attr_reader :opaque - - # Returns the fragment component of the Bundler::URI. - # - # Bundler::URI("http://foo/bar/baz?search=FooBar#ponies").fragment #=> "ponies" - # - attr_reader :fragment - - # Returns the parser to be used. - # - # Unless the +parser+ is defined, DEFAULT_PARSER is used. - # - def parser - if !defined?(@parser) || !@parser - DEFAULT_PARSER - else - @parser || DEFAULT_PARSER - end - end - - # Replaces self by other Bundler::URI object. - # - def replace!(oth) - if self.class != oth.class - raise ArgumentError, "expected #{self.class} object" - end - - component.each do |c| - self.__send__("#{c}=", oth.__send__(c)) - end - end - private :replace! - - # - # Components of the Bundler::URI in the order. - # - def component - self.class.component - end - - # - # Checks the scheme +v+ component against the +parser+ Regexp for :SCHEME. - # - def check_scheme(v) - if v && parser.regexp[:SCHEME] !~ v - raise InvalidComponentError, - "bad component(expected scheme component): #{v}" - end - - return true - end - private :check_scheme - - # Protected setter for the scheme component +v+. - # - # See also Bundler::URI::Generic.scheme=. - # - def set_scheme(v) - @scheme = v&.downcase - end - protected :set_scheme - - # - # == Args - # - # +v+:: - # String - # - # == Description - # - # Public setter for the scheme component +v+ - # (with validation). - # - # See also Bundler::URI::Generic.check_scheme. - # - # == Usage - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri = Bundler::URI.parse("http://my.example.com") - # uri.scheme = "https" - # uri.to_s #=> "https://my.example.com" - # - def scheme=(v) - check_scheme(v) - set_scheme(v) - v - end - - # - # Checks the +user+ and +password+. - # - # If +password+ is not provided, then +user+ is - # split, using Bundler::URI::Generic.split_userinfo, to - # pull +user+ and +password. - # - # See also Bundler::URI::Generic.check_user, Bundler::URI::Generic.check_password. - # - def check_userinfo(user, password = nil) - if !password - user, password = split_userinfo(user) - end - check_user(user) - check_password(password, user) - - return true - end - private :check_userinfo - - # - # Checks the user +v+ component for RFC2396 compliance - # and against the +parser+ Regexp for :USERINFO. - # - # Can not have a registry or opaque component defined, - # with a user component defined. - # - def check_user(v) - if @opaque - raise InvalidURIError, - "cannot set user with opaque" - end - - return v unless v - - if parser.regexp[:USERINFO] !~ v - raise InvalidComponentError, - "bad component(expected userinfo component or user component): #{v}" - end - - return true - end - private :check_user - - # - # Checks the password +v+ component for RFC2396 compliance - # and against the +parser+ Regexp for :USERINFO. - # - # Can not have a registry or opaque component defined, - # with a user component defined. - # - def check_password(v, user = @user) - if @opaque - raise InvalidURIError, - "cannot set password with opaque" - end - return v unless v - - if !user - raise InvalidURIError, - "password component depends user component" - end - - if parser.regexp[:USERINFO] !~ v - raise InvalidComponentError, - "bad password component" - end - - return true - end - private :check_password - - # - # Sets userinfo, argument is string like 'name:pass'. - # - def userinfo=(userinfo) - if userinfo.nil? - return nil - end - check_userinfo(*userinfo) - set_userinfo(*userinfo) - # returns userinfo - end - - # - # == Args - # - # +v+:: - # String - # - # == Description - # - # Public setter for the +user+ component - # (with validation). - # - # See also Bundler::URI::Generic.check_user. - # - # == Usage - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri = Bundler::URI.parse("http://john:S3nsit1ve@my.example.com") - # uri.user = "sam" - # uri.to_s #=> "http://sam:V3ry_S3nsit1ve@my.example.com" - # - def user=(user) - check_user(user) - set_user(user) - # returns user - end - - # - # == Args - # - # +v+:: - # String - # - # == Description - # - # Public setter for the +password+ component - # (with validation). - # - # See also Bundler::URI::Generic.check_password. - # - # == Usage - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri = Bundler::URI.parse("http://john:S3nsit1ve@my.example.com") - # uri.password = "V3ry_S3nsit1ve" - # uri.to_s #=> "http://john:V3ry_S3nsit1ve@my.example.com" - # - def password=(password) - check_password(password) - set_password(password) - # returns password - end - - # Protected setter for the +user+ component, and +password+ if available - # (with validation). - # - # See also Bundler::URI::Generic.userinfo=. - # - def set_userinfo(user, password = nil) - unless password - user, password = split_userinfo(user) - end - @user = user - @password = password - - [@user, @password] - end - protected :set_userinfo - - # Protected setter for the user component +v+. - # - # See also Bundler::URI::Generic.user=. - # - def set_user(v) - set_userinfo(v, nil) - v - end - protected :set_user - - # Protected setter for the password component +v+. - # - # See also Bundler::URI::Generic.password=. - # - def set_password(v) - @password = v - # returns v - end - protected :set_password - - # Returns the userinfo +ui+ as [user, password] - # if properly formatted as 'user:password'. - def split_userinfo(ui) - return nil, nil unless ui - user, password = ui.split(':', 2) - - return user, password - end - private :split_userinfo - - # Escapes 'user:password' +v+ based on RFC 1738 section 3.1. - def escape_userpass(v) - parser.escape(v, /[@:\/]/o) # RFC 1738 section 3.1 #/ - end - private :escape_userpass - - # Returns the userinfo, either as 'user' or 'user:password'. - def userinfo - if @user.nil? - nil - elsif @password.nil? - @user - else - @user + ':' + @password - end - end - - # Returns the user component (without Bundler::URI decoding). - def user - @user - end - - # Returns the password component (without Bundler::URI decoding). - def password - @password - end - - # Returns the authority info (array of user, password, host and - # port), if any is set. Or returns +nil+. - def authority - return @user, @password, @host, @port if @user || @password || @host || @port - end - - # Returns the user component after Bundler::URI decoding. - def decoded_user - Bundler::URI.decode_uri_component(@user) if @user - end - - # Returns the password component after Bundler::URI decoding. - def decoded_password - Bundler::URI.decode_uri_component(@password) if @password - end - - # - # Checks the host +v+ component for RFC2396 compliance - # and against the +parser+ Regexp for :HOST. - # - # Can not have a registry or opaque component defined, - # with a host component defined. - # - def check_host(v) - return v unless v - - if @opaque - raise InvalidURIError, - "cannot set host with registry or opaque" - elsif parser.regexp[:HOST] !~ v - raise InvalidComponentError, - "bad component(expected host component): #{v}" - end - - return true - end - private :check_host - - # Protected setter for the host component +v+. - # - # See also Bundler::URI::Generic.host=. - # - def set_host(v) - @host = v - end - protected :set_host - - # Protected setter for the authority info (+user+, +password+, +host+ - # and +port+). If +port+ is +nil+, +default_port+ will be set. - # - protected def set_authority(user, password, host, port = nil) - @user, @password, @host, @port = user, password, host, port || self.default_port - end - - # - # == Args - # - # +v+:: - # String - # - # == Description - # - # Public setter for the host component +v+ - # (with validation). - # - # See also Bundler::URI::Generic.check_host. - # - # == Usage - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri = Bundler::URI.parse("http://my.example.com") - # uri.host = "foo.com" - # uri.to_s #=> "http://foo.com" - # - def host=(v) - check_host(v) - set_host(v) - set_userinfo(nil) - v - end - - # Extract the host part of the Bundler::URI and unwrap brackets for IPv6 addresses. - # - # This method is the same as Bundler::URI::Generic#host except - # brackets for IPv6 (and future IP) addresses are removed. - # - # uri = Bundler::URI("http://[::1]/bar") - # uri.hostname #=> "::1" - # uri.host #=> "[::1]" - # - def hostname - v = self.host - v&.start_with?('[') && v.end_with?(']') ? v[1..-2] : v - end - - # Sets the host part of the Bundler::URI as the argument with brackets for IPv6 addresses. - # - # This method is the same as Bundler::URI::Generic#host= except - # the argument can be a bare IPv6 address. - # - # uri = Bundler::URI("http://foo/bar") - # uri.hostname = "::1" - # uri.to_s #=> "http://[::1]/bar" - # - # If the argument seems to be an IPv6 address, - # it is wrapped with brackets. - # - def hostname=(v) - v = "[#{v}]" if !(v&.start_with?('[') && v&.end_with?(']')) && v&.index(':') - self.host = v - end - - # - # Checks the port +v+ component for RFC2396 compliance - # and against the +parser+ Regexp for :PORT. - # - # Can not have a registry or opaque component defined, - # with a port component defined. - # - def check_port(v) - return v unless v - - if @opaque - raise InvalidURIError, - "cannot set port with registry or opaque" - elsif !v.kind_of?(Integer) && parser.regexp[:PORT] !~ v - raise InvalidComponentError, - "bad component(expected port component): #{v.inspect}" - end - - return true - end - private :check_port - - # Protected setter for the port component +v+. - # - # See also Bundler::URI::Generic.port=. - # - def set_port(v) - v = v.empty? ? nil : v.to_i unless !v || v.kind_of?(Integer) - @port = v - end - protected :set_port - - # - # == Args - # - # +v+:: - # String - # - # == Description - # - # Public setter for the port component +v+ - # (with validation). - # - # See also Bundler::URI::Generic.check_port. - # - # == Usage - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri = Bundler::URI.parse("http://my.example.com") - # uri.port = 8080 - # uri.to_s #=> "http://my.example.com:8080" - # - def port=(v) - check_port(v) - set_port(v) - set_userinfo(nil) - port - end - - def check_registry(v) # :nodoc: - raise InvalidURIError, "cannot set registry" - end - private :check_registry - - def set_registry(v) # :nodoc: - raise InvalidURIError, "cannot set registry" - end - protected :set_registry - - def registry=(v) # :nodoc: - raise InvalidURIError, "cannot set registry" - end - - # - # Checks the path +v+ component for RFC2396 compliance - # and against the +parser+ Regexp - # for :ABS_PATH and :REL_PATH. - # - # Can not have a opaque component defined, - # with a path component defined. - # - def check_path(v) - # raise if both hier and opaque are not nil, because: - # absoluteURI = scheme ":" ( hier_part | opaque_part ) - # hier_part = ( net_path | abs_path ) [ "?" query ] - if v && @opaque - raise InvalidURIError, - "path conflicts with opaque" - end - - # If scheme is ftp, path may be relative. - # See RFC 1738 section 3.2.2, and RFC 2396. - if @scheme && @scheme != "ftp" - if v && v != '' && parser.regexp[:ABS_PATH] !~ v - raise InvalidComponentError, - "bad component(expected absolute path component): #{v}" - end - else - if v && v != '' && parser.regexp[:ABS_PATH] !~ v && - parser.regexp[:REL_PATH] !~ v - raise InvalidComponentError, - "bad component(expected relative path component): #{v}" - end - end - - return true - end - private :check_path - - # Protected setter for the path component +v+. - # - # See also Bundler::URI::Generic.path=. - # - def set_path(v) - @path = v - end - protected :set_path - - # - # == Args - # - # +v+:: - # String - # - # == Description - # - # Public setter for the path component +v+ - # (with validation). - # - # See also Bundler::URI::Generic.check_path. - # - # == Usage - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri = Bundler::URI.parse("http://my.example.com/pub/files") - # uri.path = "/faq/" - # uri.to_s #=> "http://my.example.com/faq/" - # - def path=(v) - check_path(v) - set_path(v) - v - end - - # - # == Args - # - # +v+:: - # String - # - # == Description - # - # Public setter for the query component +v+. - # - # == Usage - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri = Bundler::URI.parse("http://my.example.com/?id=25") - # uri.query = "id=1" - # uri.to_s #=> "http://my.example.com/?id=1" - # - def query=(v) - return @query = nil unless v - raise InvalidURIError, "query conflicts with opaque" if @opaque - - x = v.to_str - v = x.dup if x.equal? v - v.encode!(Encoding::UTF_8) rescue nil - v.delete!("\t\r\n") - v.force_encoding(Encoding::ASCII_8BIT) - raise InvalidURIError, "invalid percent escape: #{$1}" if /(%\H\H)/n.match(v) - v.gsub!(/(?!%\h\h|[!$-&(-;=?-_a-~])./n.freeze){'%%%02X' % $&.ord} - v.force_encoding(Encoding::US_ASCII) - @query = v - end - - # - # Checks the opaque +v+ component for RFC2396 compliance and - # against the +parser+ Regexp for :OPAQUE. - # - # Can not have a host, port, user, or path component defined, - # with an opaque component defined. - # - def check_opaque(v) - return v unless v - - # raise if both hier and opaque are not nil, because: - # absoluteURI = scheme ":" ( hier_part | opaque_part ) - # hier_part = ( net_path | abs_path ) [ "?" query ] - if @host || @port || @user || @path # userinfo = @user + ':' + @password - raise InvalidURIError, - "cannot set opaque with host, port, userinfo or path" - elsif v && parser.regexp[:OPAQUE] !~ v - raise InvalidComponentError, - "bad component(expected opaque component): #{v}" - end - - return true - end - private :check_opaque - - # Protected setter for the opaque component +v+. - # - # See also Bundler::URI::Generic.opaque=. - # - def set_opaque(v) - @opaque = v - end - protected :set_opaque - - # - # == Args - # - # +v+:: - # String - # - # == Description - # - # Public setter for the opaque component +v+ - # (with validation). - # - # See also Bundler::URI::Generic.check_opaque. - # - def opaque=(v) - check_opaque(v) - set_opaque(v) - v - end - - # - # Checks the fragment +v+ component against the +parser+ Regexp for :FRAGMENT. - # - # - # == Args - # - # +v+:: - # String - # - # == Description - # - # Public setter for the fragment component +v+ - # (with validation). - # - # == Usage - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri = Bundler::URI.parse("http://my.example.com/?id=25#time=1305212049") - # uri.fragment = "time=1305212086" - # uri.to_s #=> "http://my.example.com/?id=25#time=1305212086" - # - def fragment=(v) - return @fragment = nil unless v - - x = v.to_str - v = x.dup if x.equal? v - v.encode!(Encoding::UTF_8) rescue nil - v.delete!("\t\r\n") - v.force_encoding(Encoding::ASCII_8BIT) - v.gsub!(/(?!%\h\h|[!-~])./n){'%%%02X' % $&.ord} - v.force_encoding(Encoding::US_ASCII) - @fragment = v - end - - # - # Returns true if Bundler::URI is hierarchical. - # - # == Description - # - # Bundler::URI has components listed in order of decreasing significance from left to right, - # see RFC3986 https://www.rfc-editor.org/rfc/rfc3986 1.2.3. - # - # == Usage - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri = Bundler::URI.parse("http://my.example.com/") - # uri.hierarchical? - # #=> true - # uri = Bundler::URI.parse("mailto:joe@example.com") - # uri.hierarchical? - # #=> false - # - def hierarchical? - if @path - true - else - false - end - end - - # - # Returns true if Bundler::URI has a scheme (e.g. http:// or https://) specified. - # - def absolute? - if @scheme - true - else - false - end - end - alias absolute absolute? - - # - # Returns true if Bundler::URI does not have a scheme (e.g. http:// or https://) specified. - # - def relative? - !absolute? - end - - # - # Returns an Array of the path split on '/'. - # - def split_path(path) - path.split("/", -1) - end - private :split_path - - # - # Merges a base path +base+, with relative path +rel+, - # returns a modified base path. - # - def merge_path(base, rel) - - # RFC2396, Section 5.2, 5) - # RFC2396, Section 5.2, 6) - base_path = split_path(base) - rel_path = split_path(rel) - - # RFC2396, Section 5.2, 6), a) - base_path << '' if base_path.last == '..' - while i = base_path.index('..') - base_path.slice!(i - 1, 2) - end - - if (first = rel_path.first) and first.empty? - base_path.clear - rel_path.shift - end - - # RFC2396, Section 5.2, 6), c) - # RFC2396, Section 5.2, 6), d) - rel_path.push('') if rel_path.last == '.' || rel_path.last == '..' - rel_path.delete('.') - - # RFC2396, Section 5.2, 6), e) - tmp = [] - rel_path.each do |x| - if x == '..' && - !(tmp.empty? || tmp.last == '..') - tmp.pop - else - tmp << x - end - end - - add_trailer_slash = !tmp.empty? - if base_path.empty? - base_path = [''] # keep '/' for root directory - elsif add_trailer_slash - base_path.pop - end - while x = tmp.shift - if x == '..' - # RFC2396, Section 4 - # a .. or . in an absolute path has no special meaning - base_path.pop if base_path.size > 1 - else - # if x == '..' - # valid absolute (but abnormal) path "/../..." - # else - # valid absolute path - # end - base_path << x - tmp.each {|t| base_path << t} - add_trailer_slash = false - break - end - end - base_path.push('') if add_trailer_slash - - return base_path.join('/') - end - private :merge_path - - # - # == Args - # - # +oth+:: - # Bundler::URI or String - # - # == Description - # - # Destructive form of #merge. - # - # == Usage - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri = Bundler::URI.parse("http://my.example.com") - # uri.merge!("/main.rbx?page=1") - # uri.to_s # => "http://my.example.com/main.rbx?page=1" - # - def merge!(oth) - t = merge(oth) - if self == t - nil - else - replace!(t) - self - end - end - - # - # == Args - # - # +oth+:: - # Bundler::URI or String - # - # == Description - # - # Merges two URIs. - # - # == Usage - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri = Bundler::URI.parse("http://my.example.com") - # uri.merge("/main.rbx?page=1") - # # => "http://my.example.com/main.rbx?page=1" - # - def merge(oth) - rel = parser.__send__(:convert_to_uri, oth) - - if rel.absolute? - #raise BadURIError, "both Bundler::URI are absolute" if absolute? - # hmm... should return oth for usability? - return rel - end - - unless self.absolute? - raise BadURIError, "both Bundler::URI are relative" - end - - base = self.dup - - authority = rel.authority - - # RFC2396, Section 5.2, 2) - if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query - base.fragment=(rel.fragment) if rel.fragment - return base - end - - base.query = nil - base.fragment=(nil) - - # RFC2396, Section 5.2, 4) - if authority - base.set_authority(*authority) - base.set_path(rel.path) - elsif base.path && rel.path - base.set_path(merge_path(base.path, rel.path)) - end - - # RFC2396, Section 5.2, 7) - base.query = rel.query if rel.query - base.fragment=(rel.fragment) if rel.fragment - - return base - end # merge - alias + merge - - # :stopdoc: - def route_from_path(src, dst) - case dst - when src - # RFC2396, Section 4.2 - return '' - when %r{(?:\A|/)\.\.?(?:/|\z)} - # dst has abnormal absolute path, - # like "/./", "/../", "/x/../", ... - return dst.dup - end - - src_path = src.scan(%r{[^/]*/}) - dst_path = dst.scan(%r{[^/]*/?}) - - # discard same parts - while !dst_path.empty? && dst_path.first == src_path.first - src_path.shift - dst_path.shift - end - - tmp = dst_path.join - - # calculate - if src_path.empty? - if tmp.empty? - return './' - elsif dst_path.first.include?(':') # (see RFC2396 Section 5) - return './' + tmp - else - return tmp - end - end - - return '../' * src_path.size + tmp - end - private :route_from_path - # :startdoc: - - # :stopdoc: - def route_from0(oth) - oth = parser.__send__(:convert_to_uri, oth) - if self.relative? - raise BadURIError, - "relative Bundler::URI: #{self}" - end - if oth.relative? - raise BadURIError, - "relative Bundler::URI: #{oth}" - end - - if self.scheme != oth.scheme - return self, self.dup - end - rel = Bundler::URI::Generic.new(nil, # it is relative Bundler::URI - self.userinfo, self.host, self.port, - nil, self.path, self.opaque, - self.query, self.fragment, parser) - - if rel.userinfo != oth.userinfo || - rel.host.to_s.downcase != oth.host.to_s.downcase || - rel.port != oth.port - - if self.userinfo.nil? && self.host.nil? - return self, self.dup - end - - rel.set_port(nil) if rel.port == oth.default_port - return rel, rel - end - rel.set_userinfo(nil) - rel.set_host(nil) - rel.set_port(nil) - - if rel.path && rel.path == oth.path - rel.set_path('') - rel.query = nil if rel.query == oth.query - return rel, rel - elsif rel.opaque && rel.opaque == oth.opaque - rel.set_opaque('') - rel.query = nil if rel.query == oth.query - return rel, rel - end - - # you can modify `rel', but cannot `oth'. - return oth, rel - end - private :route_from0 - # :startdoc: - - # - # == Args - # - # +oth+:: - # Bundler::URI or String - # - # == Description - # - # Calculates relative path from oth to self. - # - # == Usage - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri = Bundler::URI.parse('http://my.example.com/main.rbx?page=1') - # uri.route_from('http://my.example.com') - # #=> # - # - def route_from(oth) - # you can modify `rel', but cannot `oth'. - begin - oth, rel = route_from0(oth) - rescue - raise $!.class, $!.message - end - if oth == rel - return rel - end - - rel.set_path(route_from_path(oth.path, self.path)) - if rel.path == './' && self.query - # "./?foo" -> "?foo" - rel.set_path('') - end - - return rel - end - - alias - route_from - - # - # == Args - # - # +oth+:: - # Bundler::URI or String - # - # == Description - # - # Calculates relative path to oth from self. - # - # == Usage - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri = Bundler::URI.parse('http://my.example.com') - # uri.route_to('http://my.example.com/main.rbx?page=1') - # #=> # - # - def route_to(oth) - parser.__send__(:convert_to_uri, oth).route_from(self) - end - - # - # Returns normalized Bundler::URI. - # - # require 'bundler/vendor/uri/lib/uri' - # - # Bundler::URI("HTTP://my.EXAMPLE.com").normalize - # #=> # - # - # Normalization here means: - # - # * scheme and host are converted to lowercase, - # * an empty path component is set to "/". - # - def normalize - uri = dup - uri.normalize! - uri - end - - # - # Destructive version of #normalize. - # - def normalize! - if path&.empty? - set_path('/') - end - if scheme && scheme != scheme.downcase - set_scheme(self.scheme.downcase) - end - if host && host != host.downcase - set_host(self.host.downcase) - end - end - - # - # Constructs String from Bundler::URI. - # - def to_s - str = ''.dup - if @scheme - str << @scheme - str << ':' - end - - if @opaque - str << @opaque - else - if @host || %w[file postgres].include?(@scheme) - str << '//' - end - if self.userinfo - str << self.userinfo - str << '@' - end - if @host - str << @host - end - if @port && @port != self.default_port - str << ':' - str << @port.to_s - end - if (@host || @port) && !@path.empty? && !@path.start_with?('/') - str << '/' - end - str << @path - if @query - str << '?' - str << @query - end - end - if @fragment - str << '#' - str << @fragment - end - str - end - alias to_str to_s - - # - # Compares two URIs. - # - def ==(oth) - if self.class == oth.class - self.normalize.component_ary == oth.normalize.component_ary - else - false - end - end - - # Returns the hash value. - def hash - self.component_ary.hash - end - - # Compares with _oth_ for Hash. - def eql?(oth) - self.class == oth.class && - parser == oth.parser && - self.component_ary.eql?(oth.component_ary) - end - - # Returns an Array of the components defined from the COMPONENT Array. - def component_ary - component.collect do |x| - self.__send__(x) - end - end - protected :component_ary - - # == Args - # - # +components+:: - # Multiple Symbol arguments defined in Bundler::URI::HTTP. - # - # == Description - # - # Selects specified components from Bundler::URI. - # - # == Usage - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri = Bundler::URI.parse('http://myuser:mypass@my.example.com/test.rbx') - # uri.select(:userinfo, :host, :path) - # # => ["myuser:mypass", "my.example.com", "/test.rbx"] - # - def select(*components) - components.collect do |c| - if component.include?(c) - self.__send__(c) - else - raise ArgumentError, - "expected of components of #{self.class} (#{self.class.component.join(', ')})" - end - end - end - - def inspect # :nodoc: - "#<#{self.class} #{self}>" - end - - # - # == Args - # - # +v+:: - # Bundler::URI or String - # - # == Description - # - # Attempts to parse other Bundler::URI +oth+, - # returns [parsed_oth, self]. - # - # == Usage - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri = Bundler::URI.parse("http://my.example.com") - # uri.coerce("http://foo.com") - # #=> [#, #] - # - def coerce(oth) - case oth - when String - oth = parser.parse(oth) - else - super - end - - return oth, self - end - - # Returns a proxy Bundler::URI. - # The proxy Bundler::URI is obtained from environment variables such as http_proxy, - # ftp_proxy, no_proxy, etc. - # If there is no proper proxy, nil is returned. - # - # If the optional parameter +env+ is specified, it is used instead of ENV. - # - # Note that capitalized variables (HTTP_PROXY, FTP_PROXY, NO_PROXY, etc.) - # are examined, too. - # - # But http_proxy and HTTP_PROXY is treated specially under CGI environment. - # It's because HTTP_PROXY may be set by Proxy: header. - # So HTTP_PROXY is not used. - # http_proxy is not used too if the variable is case insensitive. - # CGI_HTTP_PROXY can be used instead. - def find_proxy(env=ENV) - raise BadURIError, "relative Bundler::URI: #{self}" if self.relative? - name = self.scheme.downcase + '_proxy' - proxy_uri = nil - if name == 'http_proxy' && env.include?('REQUEST_METHOD') # CGI? - # HTTP_PROXY conflicts with *_proxy for proxy settings and - # HTTP_* for header information in CGI. - # So it should be careful to use it. - pairs = env.reject {|k, v| /\Ahttp_proxy\z/i !~ k } - case pairs.length - when 0 # no proxy setting anyway. - proxy_uri = nil - when 1 - k, _ = pairs.shift - if k == 'http_proxy' && env[k.upcase] == nil - # http_proxy is safe to use because ENV is case sensitive. - proxy_uri = env[name] - else - proxy_uri = nil - end - else # http_proxy is safe to use because ENV is case sensitive. - proxy_uri = env.to_hash[name] - end - if !proxy_uri - # Use CGI_HTTP_PROXY. cf. libwww-perl. - proxy_uri = env["CGI_#{name.upcase}"] - end - elsif name == 'http_proxy' - if RUBY_ENGINE == 'jruby' && p_addr = ENV_JAVA['http.proxyHost'] - p_port = ENV_JAVA['http.proxyPort'] - if p_user = ENV_JAVA['http.proxyUser'] - p_pass = ENV_JAVA['http.proxyPass'] - proxy_uri = "http://#{p_user}:#{p_pass}@#{p_addr}:#{p_port}" - else - proxy_uri = "http://#{p_addr}:#{p_port}" - end - else - unless proxy_uri = env[name] - if proxy_uri = env[name.upcase] - warn 'The environment variable HTTP_PROXY is discouraged. Please use http_proxy instead.', uplevel: 1 - end - end - end - else - proxy_uri = env[name] || env[name.upcase] - end - - if proxy_uri.nil? || proxy_uri.empty? - return nil - end - - if self.hostname - begin - addr = IPSocket.getaddress(self.hostname) - return nil if /\A127\.|\A::1\z/ =~ addr - rescue SocketError - end - end - - name = 'no_proxy' - if no_proxy = env[name] || env[name.upcase] - return nil unless Bundler::URI::Generic.use_proxy?(self.hostname, addr, self.port, no_proxy) - end - Bundler::URI.parse(proxy_uri) - end - - def self.use_proxy?(hostname, addr, port, no_proxy) # :nodoc: - hostname = hostname.downcase - dothostname = ".#{hostname}" - no_proxy.scan(/([^:,\s]+)(?::(\d+))?/) {|p_host, p_port| - if !p_port || port == p_port.to_i - if p_host.start_with?('.') - return false if hostname.end_with?(p_host.downcase) - else - return false if dothostname.end_with?(".#{p_host.downcase}") - end - if addr - begin - return false if IPAddr.new(p_host).include?(addr) - rescue IPAddr::InvalidAddressError - next - end - end - end - } - true - end - end -end diff --git a/lib/bundler/vendor/uri/lib/uri/http.rb b/lib/bundler/vendor/uri/lib/uri/http.rb deleted file mode 100644 index 9b217ee266ddaf..00000000000000 --- a/lib/bundler/vendor/uri/lib/uri/http.rb +++ /dev/null @@ -1,137 +0,0 @@ -# frozen_string_literal: false -# = uri/http.rb -# -# Author:: Akira Yamada -# License:: You can redistribute it and/or modify it under the same term as Ruby. -# -# See Bundler::URI for general documentation -# - -require_relative 'generic' - -module Bundler::URI - - # - # The syntax of HTTP URIs is defined in RFC1738 section 3.3. - # - # Note that the Ruby Bundler::URI library allows HTTP URLs containing usernames and - # passwords. This is not legal as per the RFC, but used to be - # supported in Internet Explorer 5 and 6, before the MS04-004 security - # update. See . - # - class HTTP < Generic - # A Default port of 80 for Bundler::URI::HTTP. - DEFAULT_PORT = 80 - - # An Array of the available components for Bundler::URI::HTTP. - COMPONENT = %i[ - scheme - userinfo host port - path - query - fragment - ].freeze - - # - # == Description - # - # Creates a new Bundler::URI::HTTP object from components, with syntax checking. - # - # The components accepted are userinfo, host, port, path, query, and - # fragment. - # - # The components should be provided either as an Array, or as a Hash - # with keys formed by preceding the component names with a colon. - # - # If an Array is used, the components must be passed in the - # order [userinfo, host, port, path, query, fragment]. - # - # Example: - # - # uri = Bundler::URI::HTTP.build(host: 'www.example.com', path: '/foo/bar') - # - # uri = Bundler::URI::HTTP.build([nil, "www.example.com", nil, "/path", - # "query", 'fragment']) - # - # Currently, if passed userinfo components this method generates - # invalid HTTP URIs as per RFC 1738. - # - def self.build(args) - tmp = Util.make_components_hash(self, args) - super(tmp) - end - - # Do not allow empty host names, as they are not allowed by RFC 3986. - def check_host(v) - ret = super - - if ret && v.empty? - raise InvalidComponentError, - "bad component(expected host component): #{v}" - end - - ret - end - - # - # == Description - # - # Returns the full path for an HTTP request, as required by Net::HTTP::Get. - # - # If the Bundler::URI contains a query, the full path is Bundler::URI#path + '?' + Bundler::URI#query. - # Otherwise, the path is simply Bundler::URI#path. - # - # Example: - # - # uri = Bundler::URI::HTTP.build(path: '/foo/bar', query: 'test=true') - # uri.request_uri # => "/foo/bar?test=true" - # - def request_uri - return unless @path - - url = @query ? "#@path?#@query" : @path.dup - url.start_with?(?/.freeze) ? url : ?/ + url - end - - # - # == Description - # - # Returns the authority for an HTTP uri, as defined in - # https://www.rfc-editor.org/rfc/rfc3986#section-3.2. - # - # - # Example: - # - # Bundler::URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').authority #=> "www.example.com" - # Bundler::URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').authority #=> "www.example.com:8000" - # Bundler::URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').authority #=> "www.example.com" - # - def authority - if port == default_port - host - else - "#{host}:#{port}" - end - end - - # - # == Description - # - # Returns the origin for an HTTP uri, as defined in - # https://www.rfc-editor.org/rfc/rfc6454. - # - # - # Example: - # - # Bundler::URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').origin #=> "http://www.example.com" - # Bundler::URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').origin #=> "http://www.example.com:8000" - # Bundler::URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').origin #=> "http://www.example.com" - # Bundler::URI::HTTPS.build(host: 'www.example.com', path: '/foo/bar').origin #=> "https://www.example.com" - # - def origin - "#{scheme}://#{authority}" - end - end - - register_scheme 'HTTP', HTTP -end diff --git a/lib/bundler/vendor/uri/lib/uri/https.rb b/lib/bundler/vendor/uri/lib/uri/https.rb deleted file mode 100644 index e4556e3ecb28be..00000000000000 --- a/lib/bundler/vendor/uri/lib/uri/https.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: false -# = uri/https.rb -# -# Author:: Akira Yamada -# License:: You can redistribute it and/or modify it under the same term as Ruby. -# -# See Bundler::URI for general documentation -# - -require_relative 'http' - -module Bundler::URI - - # The default port for HTTPS URIs is 443, and the scheme is 'https:' rather - # than 'http:'. Other than that, HTTPS URIs are identical to HTTP URIs; - # see Bundler::URI::HTTP. - class HTTPS < HTTP - # A Default port of 443 for Bundler::URI::HTTPS - DEFAULT_PORT = 443 - end - - register_scheme 'HTTPS', HTTPS -end diff --git a/lib/bundler/vendor/uri/lib/uri/ldap.rb b/lib/bundler/vendor/uri/lib/uri/ldap.rb deleted file mode 100644 index 9811b6e2f5110c..00000000000000 --- a/lib/bundler/vendor/uri/lib/uri/ldap.rb +++ /dev/null @@ -1,261 +0,0 @@ -# frozen_string_literal: false -# = uri/ldap.rb -# -# Author:: -# Takaaki Tateishi -# Akira Yamada -# License:: -# Bundler::URI::LDAP is copyrighted free software by Takaaki Tateishi and Akira Yamada. -# You can redistribute it and/or modify it under the same term as Ruby. -# -# See Bundler::URI for general documentation -# - -require_relative 'generic' - -module Bundler::URI - - # - # LDAP Bundler::URI SCHEMA (described in RFC2255). - #-- - # ldap:///[?[?[?[?]]]] - #++ - class LDAP < Generic - - # A Default port of 389 for Bundler::URI::LDAP. - DEFAULT_PORT = 389 - - # An Array of the available components for Bundler::URI::LDAP. - COMPONENT = [ - :scheme, - :host, :port, - :dn, - :attributes, - :scope, - :filter, - :extensions, - ].freeze - - # Scopes available for the starting point. - # - # * SCOPE_BASE - the Base DN - # * SCOPE_ONE - one level under the Base DN, not including the base DN and - # not including any entries under this - # * SCOPE_SUB - subtrees, all entries at all levels - # - SCOPE = [ - SCOPE_ONE = 'one', - SCOPE_SUB = 'sub', - SCOPE_BASE = 'base', - ].freeze - - # - # == Description - # - # Creates a new Bundler::URI::LDAP object from components, with syntax checking. - # - # The components accepted are host, port, dn, attributes, - # scope, filter, and extensions. - # - # The components should be provided either as an Array, or as a Hash - # with keys formed by preceding the component names with a colon. - # - # If an Array is used, the components must be passed in the - # order [host, port, dn, attributes, scope, filter, extensions]. - # - # Example: - # - # uri = Bundler::URI::LDAP.build({:host => 'ldap.example.com', - # :dn => '/dc=example'}) - # - # uri = Bundler::URI::LDAP.build(["ldap.example.com", nil, - # "/dc=example;dc=com", "query", nil, nil, nil]) - # - def self.build(args) - tmp = Util::make_components_hash(self, args) - - if tmp[:dn] - tmp[:path] = tmp[:dn] - end - - query = [] - [:extensions, :filter, :scope, :attributes].collect do |x| - next if !tmp[x] && query.size == 0 - query.unshift(tmp[x]) - end - - tmp[:query] = query.join('?') - - return super(tmp) - end - - # - # == Description - # - # Creates a new Bundler::URI::LDAP object from generic Bundler::URI components as per - # RFC 2396. No LDAP-specific syntax checking is performed. - # - # Arguments are +scheme+, +userinfo+, +host+, +port+, +registry+, +path+, - # +opaque+, +query+, and +fragment+, in that order. - # - # Example: - # - # uri = Bundler::URI::LDAP.new("ldap", nil, "ldap.example.com", nil, nil, - # "/dc=example;dc=com", nil, "query", nil) - # - # See also Bundler::URI::Generic.new. - # - def initialize(*arg) - super(*arg) - - if @fragment - raise InvalidURIError, 'bad LDAP URL' - end - - parse_dn - parse_query - end - - # Private method to cleanup +dn+ from using the +path+ component attribute. - def parse_dn - raise InvalidURIError, 'bad LDAP URL' unless @path - @dn = @path[1..-1] - end - private :parse_dn - - # Private method to cleanup +attributes+, +scope+, +filter+, and +extensions+ - # from using the +query+ component attribute. - def parse_query - @attributes = nil - @scope = nil - @filter = nil - @extensions = nil - - if @query - attrs, scope, filter, extensions = @query.split('?') - - @attributes = attrs if attrs && attrs.size > 0 - @scope = scope if scope && scope.size > 0 - @filter = filter if filter && filter.size > 0 - @extensions = extensions if extensions && extensions.size > 0 - end - end - private :parse_query - - # Private method to assemble +query+ from +attributes+, +scope+, +filter+, and +extensions+. - def build_path_query - @path = '/' + @dn - - query = [] - [@extensions, @filter, @scope, @attributes].each do |x| - next if !x && query.size == 0 - query.unshift(x) - end - @query = query.join('?') - end - private :build_path_query - - # Returns dn. - def dn - @dn - end - - # Private setter for dn +val+. - def set_dn(val) - @dn = val - build_path_query - @dn - end - protected :set_dn - - # Setter for dn +val+. - def dn=(val) - set_dn(val) - val - end - - # Returns attributes. - def attributes - @attributes - end - - # Private setter for attributes +val+. - def set_attributes(val) - @attributes = val - build_path_query - @attributes - end - protected :set_attributes - - # Setter for attributes +val+. - def attributes=(val) - set_attributes(val) - val - end - - # Returns scope. - def scope - @scope - end - - # Private setter for scope +val+. - def set_scope(val) - @scope = val - build_path_query - @scope - end - protected :set_scope - - # Setter for scope +val+. - def scope=(val) - set_scope(val) - val - end - - # Returns filter. - def filter - @filter - end - - # Private setter for filter +val+. - def set_filter(val) - @filter = val - build_path_query - @filter - end - protected :set_filter - - # Setter for filter +val+. - def filter=(val) - set_filter(val) - val - end - - # Returns extensions. - def extensions - @extensions - end - - # Private setter for extensions +val+. - def set_extensions(val) - @extensions = val - build_path_query - @extensions - end - protected :set_extensions - - # Setter for extensions +val+. - def extensions=(val) - set_extensions(val) - val - end - - # Checks if Bundler::URI has a path. - # For Bundler::URI::LDAP this will return +false+. - def hierarchical? - false - end - end - - register_scheme 'LDAP', LDAP -end diff --git a/lib/bundler/vendor/uri/lib/uri/ldaps.rb b/lib/bundler/vendor/uri/lib/uri/ldaps.rb deleted file mode 100644 index c7861684506bf8..00000000000000 --- a/lib/bundler/vendor/uri/lib/uri/ldaps.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: false -# = uri/ldap.rb -# -# License:: You can redistribute it and/or modify it under the same term as Ruby. -# -# See Bundler::URI for general documentation -# - -require_relative 'ldap' - -module Bundler::URI - - # The default port for LDAPS URIs is 636, and the scheme is 'ldaps:' rather - # than 'ldap:'. Other than that, LDAPS URIs are identical to LDAP URIs; - # see Bundler::URI::LDAP. - class LDAPS < LDAP - # A Default port of 636 for Bundler::URI::LDAPS - DEFAULT_PORT = 636 - end - - register_scheme 'LDAPS', LDAPS -end diff --git a/lib/bundler/vendor/uri/lib/uri/mailto.rb b/lib/bundler/vendor/uri/lib/uri/mailto.rb deleted file mode 100644 index ff2e30be86731c..00000000000000 --- a/lib/bundler/vendor/uri/lib/uri/mailto.rb +++ /dev/null @@ -1,293 +0,0 @@ -# frozen_string_literal: false -# = uri/mailto.rb -# -# Author:: Akira Yamada -# License:: You can redistribute it and/or modify it under the same term as Ruby. -# -# See Bundler::URI for general documentation -# - -require_relative 'generic' - -module Bundler::URI - - # - # RFC6068, the mailto URL scheme. - # - class MailTo < Generic - include RFC2396_REGEXP - - # A Default port of nil for Bundler::URI::MailTo. - DEFAULT_PORT = nil - - # An Array of the available components for Bundler::URI::MailTo. - COMPONENT = [ :scheme, :to, :headers ].freeze - - # :stopdoc: - # "hname" and "hvalue" are encodings of an RFC 822 header name and - # value, respectively. As with "to", all URL reserved characters must - # be encoded. - # - # "#mailbox" is as specified in RFC 822 [RFC822]. This means that it - # consists of zero or more comma-separated mail addresses, possibly - # including "phrase" and "comment" components. Note that all URL - # reserved characters in "to" must be encoded: in particular, - # parentheses, commas, and the percent sign ("%"), which commonly occur - # in the "mailbox" syntax. - # - # Within mailto URLs, the characters "?", "=", "&" are reserved. - - # ; RFC 6068 - # hfields = "?" hfield *( "&" hfield ) - # hfield = hfname "=" hfvalue - # hfname = *qchar - # hfvalue = *qchar - # qchar = unreserved / pct-encoded / some-delims - # some-delims = "!" / "$" / "'" / "(" / ")" / "*" - # / "+" / "," / ";" / ":" / "@" - # - # ; RFC3986 - # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - # pct-encoded = "%" HEXDIG HEXDIG - HEADER_REGEXP = /\A(?(?:%\h\h|[!$'-.0-;@-Z_a-z~])*=(?:%\h\h|[!$'-.0-;@-Z_a-z~])*)(?:&\g)*\z/ - # practical regexp for email address - # https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address - EMAIL_REGEXP = /\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\z/ - # :startdoc: - - # - # == Description - # - # Creates a new Bundler::URI::MailTo object from components, with syntax checking. - # - # Components can be provided as an Array or Hash. If an Array is used, - # the components must be supplied as [to, headers]. - # - # If a Hash is used, the keys are the component names preceded by colons. - # - # The headers can be supplied as a pre-encoded string, such as - # "subject=subscribe&cc=address", or as an Array of Arrays - # like [['subject', 'subscribe'], ['cc', 'address']]. - # - # Examples: - # - # require 'bundler/vendor/uri/lib/uri' - # - # m1 = Bundler::URI::MailTo.build(['joe@example.com', 'subject=Ruby']) - # m1.to_s # => "mailto:joe@example.com?subject=Ruby" - # - # m2 = Bundler::URI::MailTo.build(['john@example.com', [['Subject', 'Ruby'], ['Cc', 'jack@example.com']]]) - # m2.to_s # => "mailto:john@example.com?Subject=Ruby&Cc=jack@example.com" - # - # m3 = Bundler::URI::MailTo.build({:to => 'listman@example.com', :headers => [['subject', 'subscribe']]}) - # m3.to_s # => "mailto:listman@example.com?subject=subscribe" - # - def self.build(args) - tmp = Util.make_components_hash(self, args) - - case tmp[:to] - when Array - tmp[:opaque] = tmp[:to].join(',') - when String - tmp[:opaque] = tmp[:to].dup - else - tmp[:opaque] = '' - end - - if tmp[:headers] - query = - case tmp[:headers] - when Array - tmp[:headers].collect { |x| - if x.kind_of?(Array) - x[0] + '=' + x[1..-1].join - else - x.to_s - end - }.join('&') - when Hash - tmp[:headers].collect { |h,v| - h + '=' + v - }.join('&') - else - tmp[:headers].to_s - end - unless query.empty? - tmp[:opaque] << '?' << query - end - end - - super(tmp) - end - - # - # == Description - # - # Creates a new Bundler::URI::MailTo object from generic URL components with - # no syntax checking. - # - # This method is usually called from Bundler::URI::parse, which checks - # the validity of each component. - # - def initialize(*arg) - super(*arg) - - @to = nil - @headers = [] - - # The RFC3986 parser does not normally populate opaque - @opaque = "?#{@query}" if @query && !@opaque - - unless @opaque - raise InvalidComponentError, - "missing opaque part for mailto URL" - end - to, header = @opaque.split('?', 2) - # allow semicolon as a addr-spec separator - # http://support.microsoft.com/kb/820868 - unless /\A(?:[^@,;]+@[^@,;]+(?:\z|[,;]))*\z/ =~ to - raise InvalidComponentError, - "unrecognised opaque part for mailtoURL: #{@opaque}" - end - - if arg[10] # arg_check - self.to = to - self.headers = header - else - set_to(to) - set_headers(header) - end - end - - # The primary e-mail address of the URL, as a String. - attr_reader :to - - # E-mail headers set by the URL, as an Array of Arrays. - attr_reader :headers - - # Checks the to +v+ component. - def check_to(v) - return true unless v - return true if v.size == 0 - - v.split(/[,;]/).each do |addr| - # check url safety as path-rootless - if /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*\z/ !~ addr - raise InvalidComponentError, - "an address in 'to' is invalid as Bundler::URI #{addr.dump}" - end - - # check addr-spec - # don't s/\+/ /g - addr.gsub!(/%\h\h/, Bundler::URI::TBLDECWWWCOMP_) - if EMAIL_REGEXP !~ addr - raise InvalidComponentError, - "an address in 'to' is invalid as uri-escaped addr-spec #{addr.dump}" - end - end - - true - end - private :check_to - - # Private setter for to +v+. - def set_to(v) - @to = v - end - protected :set_to - - # Setter for to +v+. - def to=(v) - check_to(v) - set_to(v) - v - end - - # Checks the headers +v+ component against either - # * HEADER_REGEXP - def check_headers(v) - return true unless v - return true if v.size == 0 - if HEADER_REGEXP !~ v - raise InvalidComponentError, - "bad component(expected opaque component): #{v}" - end - - true - end - private :check_headers - - # Private setter for headers +v+. - def set_headers(v) - @headers = [] - if v - v.split('&').each do |x| - @headers << x.split(/=/, 2) - end - end - end - protected :set_headers - - # Setter for headers +v+. - def headers=(v) - check_headers(v) - set_headers(v) - v - end - - # Constructs String from Bundler::URI. - def to_s - @scheme + ':' + - if @to - @to - else - '' - end + - if @headers.size > 0 - '?' + @headers.collect{|x| x.join('=')}.join('&') - else - '' - end + - if @fragment - '#' + @fragment - else - '' - end - end - - # Returns the RFC822 e-mail text equivalent of the URL, as a String. - # - # Example: - # - # require 'bundler/vendor/uri/lib/uri' - # - # uri = Bundler::URI.parse("mailto:ruby-list@ruby-lang.org?Subject=subscribe&cc=myaddr") - # uri.to_mailtext - # # => "To: ruby-list@ruby-lang.org\nSubject: subscribe\nCc: myaddr\n\n\n" - # - def to_mailtext - to = Bundler::URI.decode_www_form_component(@to) - head = '' - body = '' - @headers.each do |x| - case x[0] - when 'body' - body = Bundler::URI.decode_www_form_component(x[1]) - when 'to' - to << ', ' + Bundler::URI.decode_www_form_component(x[1]) - else - head << Bundler::URI.decode_www_form_component(x[0]).capitalize + ': ' + - Bundler::URI.decode_www_form_component(x[1]) + "\n" - end - end - - "To: #{to} -#{head} -#{body} -" - end - alias to_rfc822text to_mailtext - end - - register_scheme 'MAILTO', MailTo -end diff --git a/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb b/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb deleted file mode 100644 index 522113fe678f5c..00000000000000 --- a/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb +++ /dev/null @@ -1,547 +0,0 @@ -# frozen_string_literal: false -#-- -# = uri/common.rb -# -# Author:: Akira Yamada -# License:: -# You can redistribute it and/or modify it under the same term as Ruby. -# -# See Bundler::URI for general documentation -# - -module Bundler::URI - # - # Includes Bundler::URI::REGEXP::PATTERN - # - module RFC2396_REGEXP - # - # Patterns used to parse Bundler::URI's - # - module PATTERN - # :stopdoc: - - # RFC 2396 (Bundler::URI Generic Syntax) - # RFC 2732 (IPv6 Literal Addresses in URL's) - # RFC 2373 (IPv6 Addressing Architecture) - - # alpha = lowalpha | upalpha - ALPHA = "a-zA-Z" - # alphanum = alpha | digit - ALNUM = "#{ALPHA}\\d" - - # hex = digit | "A" | "B" | "C" | "D" | "E" | "F" | - # "a" | "b" | "c" | "d" | "e" | "f" - HEX = "a-fA-F\\d" - # escaped = "%" hex hex - ESCAPED = "%[#{HEX}]{2}" - # mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | - # "(" | ")" - # unreserved = alphanum | mark - UNRESERVED = "\\-_.!~*'()#{ALNUM}" - # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | - # "$" | "," - # reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | - # "$" | "," | "[" | "]" (RFC 2732) - RESERVED = ";/?:@&=+$,\\[\\]" - - # domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum - DOMLABEL = "(?:[#{ALNUM}](?:[-#{ALNUM}]*[#{ALNUM}])?)" - # toplabel = alpha | alpha *( alphanum | "-" ) alphanum - TOPLABEL = "(?:[#{ALPHA}](?:[-#{ALNUM}]*[#{ALNUM}])?)" - # hostname = *( domainlabel "." ) toplabel [ "." ] - HOSTNAME = "(?:#{DOMLABEL}\\.)*#{TOPLABEL}\\.?" - - # :startdoc: - end # PATTERN - - # :startdoc: - end # REGEXP - - # Class that parses String's into Bundler::URI's. - # - # It contains a Hash set of patterns and Regexp's that match and validate. - # - class RFC2396_Parser - include RFC2396_REGEXP - - # - # == Synopsis - # - # Bundler::URI::RFC2396_Parser.new([opts]) - # - # == Args - # - # The constructor accepts a hash as options for parser. - # Keys of options are pattern names of Bundler::URI components - # and values of options are pattern strings. - # The constructor generates set of regexps for parsing URIs. - # - # You can use the following keys: - # - # * :ESCAPED (Bundler::URI::PATTERN::ESCAPED in default) - # * :UNRESERVED (Bundler::URI::PATTERN::UNRESERVED in default) - # * :DOMLABEL (Bundler::URI::PATTERN::DOMLABEL in default) - # * :TOPLABEL (Bundler::URI::PATTERN::TOPLABEL in default) - # * :HOSTNAME (Bundler::URI::PATTERN::HOSTNAME in default) - # - # == Examples - # - # p = Bundler::URI::RFC2396_Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})") - # u = p.parse("http://example.jp/%uABCD") #=> # - # Bundler::URI.parse(u.to_s) #=> raises Bundler::URI::InvalidURIError - # - # s = "http://example.com/ABCD" - # u1 = p.parse(s) #=> # - # u2 = Bundler::URI.parse(s) #=> # - # u1 == u2 #=> true - # u1.eql?(u2) #=> false - # - def initialize(opts = {}) - @pattern = initialize_pattern(opts) - @pattern.each_value(&:freeze) - @pattern.freeze - - @regexp = initialize_regexp(@pattern) - @regexp.each_value(&:freeze) - @regexp.freeze - end - - # The Hash of patterns. - # - # See also #initialize_pattern. - attr_reader :pattern - - # The Hash of Regexp. - # - # See also #initialize_regexp. - attr_reader :regexp - - # Returns a split Bundler::URI against +regexp[:ABS_URI]+. - def split(uri) - case uri - when '' - # null uri - - when @regexp[:ABS_URI] - scheme, opaque, userinfo, host, port, - registry, path, query, fragment = $~[1..-1] - - # Bundler::URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] - - # absoluteURI = scheme ":" ( hier_part | opaque_part ) - # hier_part = ( net_path | abs_path ) [ "?" query ] - # opaque_part = uric_no_slash *uric - - # abs_path = "/" path_segments - # net_path = "//" authority [ abs_path ] - - # authority = server | reg_name - # server = [ [ userinfo "@" ] hostport ] - - if !scheme - raise InvalidURIError, - "bad Bundler::URI (absolute but no scheme): #{uri}" - end - if !opaque && (!path && (!host && !registry)) - raise InvalidURIError, - "bad Bundler::URI (absolute but no path): #{uri}" - end - - when @regexp[:REL_URI] - scheme = nil - opaque = nil - - userinfo, host, port, registry, - rel_segment, abs_path, query, fragment = $~[1..-1] - if rel_segment && abs_path - path = rel_segment + abs_path - elsif rel_segment - path = rel_segment - elsif abs_path - path = abs_path - end - - # Bundler::URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] - - # relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ] - - # net_path = "//" authority [ abs_path ] - # abs_path = "/" path_segments - # rel_path = rel_segment [ abs_path ] - - # authority = server | reg_name - # server = [ [ userinfo "@" ] hostport ] - - else - raise InvalidURIError, "bad Bundler::URI (is not Bundler::URI?): #{uri}" - end - - path = '' if !path && !opaque # (see RFC2396 Section 5.2) - ret = [ - scheme, - userinfo, host, port, # X - registry, # X - path, # Y - opaque, # Y - query, - fragment - ] - return ret - end - - # - # == Args - # - # +uri+:: - # String - # - # == Description - # - # Parses +uri+ and constructs either matching Bundler::URI scheme object - # (File, FTP, HTTP, HTTPS, LDAP, LDAPS, or MailTo) or Bundler::URI::Generic. - # - # == Usage - # - # Bundler::URI::RFC2396_PARSER.parse("ldap://ldap.example.com/dc=example?user=john") - # #=> # - # - def parse(uri) - Bundler::URI.for(*self.split(uri), self) - end - - # - # == Args - # - # +uris+:: - # an Array of Strings - # - # == Description - # - # Attempts to parse and merge a set of URIs. - # - def join(*uris) - uris[0] = convert_to_uri(uris[0]) - uris.inject :merge - end - - # - # :call-seq: - # extract( str ) - # extract( str, schemes ) - # extract( str, schemes ) {|item| block } - # - # == Args - # - # +str+:: - # String to search - # +schemes+:: - # Patterns to apply to +str+ - # - # == Description - # - # Attempts to parse and merge a set of URIs. - # If no +block+ given, then returns the result, - # else it calls +block+ for each element in result. - # - # See also #make_regexp. - # - def extract(str, schemes = nil) - if block_given? - str.scan(make_regexp(schemes)) { yield $& } - nil - else - result = [] - str.scan(make_regexp(schemes)) { result.push $& } - result - end - end - - # Returns Regexp that is default +self.regexp[:ABS_URI_REF]+, - # unless +schemes+ is provided. Then it is a Regexp.union with +self.pattern[:X_ABS_URI]+. - def make_regexp(schemes = nil) - unless schemes - @regexp[:ABS_URI_REF] - else - /(?=(?i:#{Regexp.union(*schemes).source}):)#{@pattern[:X_ABS_URI]}/x - end - end - - # - # :call-seq: - # escape( str ) - # escape( str, unsafe ) - # - # == Args - # - # +str+:: - # String to make safe - # +unsafe+:: - # Regexp to apply. Defaults to +self.regexp[:UNSAFE]+ - # - # == Description - # - # Constructs a safe String from +str+, removing unsafe characters, - # replacing them with codes. - # - def escape(str, unsafe = @regexp[:UNSAFE]) - unless unsafe.kind_of?(Regexp) - # perhaps unsafe is String object - unsafe = Regexp.new("[#{Regexp.quote(unsafe)}]", false) - end - str.gsub(unsafe) do - us = $& - tmp = '' - us.each_byte do |uc| - tmp << sprintf('%%%02X', uc) - end - tmp - end.force_encoding(Encoding::US_ASCII) - end - - # - # :call-seq: - # unescape( str ) - # unescape( str, escaped ) - # - # == Args - # - # +str+:: - # String to remove escapes from - # +escaped+:: - # Regexp to apply. Defaults to +self.regexp[:ESCAPED]+ - # - # == Description - # - # Removes escapes from +str+. - # - def unescape(str, escaped = @regexp[:ESCAPED]) - enc = str.encoding - enc = Encoding::UTF_8 if enc == Encoding::US_ASCII - str.gsub(escaped) { [$&[1, 2]].pack('H2').force_encoding(enc) } - end - - TO_S = Kernel.instance_method(:to_s) # :nodoc: - if TO_S.respond_to?(:bind_call) - def inspect # :nodoc: - TO_S.bind_call(self) - end - else - def inspect # :nodoc: - TO_S.bind(self).call - end - end - - private - - # Constructs the default Hash of patterns. - def initialize_pattern(opts = {}) - ret = {} - ret[:ESCAPED] = escaped = (opts.delete(:ESCAPED) || PATTERN::ESCAPED) - ret[:UNRESERVED] = unreserved = opts.delete(:UNRESERVED) || PATTERN::UNRESERVED - ret[:RESERVED] = reserved = opts.delete(:RESERVED) || PATTERN::RESERVED - ret[:DOMLABEL] = opts.delete(:DOMLABEL) || PATTERN::DOMLABEL - ret[:TOPLABEL] = opts.delete(:TOPLABEL) || PATTERN::TOPLABEL - ret[:HOSTNAME] = hostname = opts.delete(:HOSTNAME) - - # RFC 2396 (Bundler::URI Generic Syntax) - # RFC 2732 (IPv6 Literal Addresses in URL's) - # RFC 2373 (IPv6 Addressing Architecture) - - # uric = reserved | unreserved | escaped - ret[:URIC] = uric = "(?:[#{unreserved}#{reserved}]|#{escaped})" - # uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" | - # "&" | "=" | "+" | "$" | "," - ret[:URIC_NO_SLASH] = uric_no_slash = "(?:[#{unreserved};?:@&=+$,]|#{escaped})" - # query = *uric - ret[:QUERY] = query = "#{uric}*" - # fragment = *uric - ret[:FRAGMENT] = fragment = "#{uric}*" - - # hostname = *( domainlabel "." ) toplabel [ "." ] - # reg-name = *( unreserved / pct-encoded / sub-delims ) # RFC3986 - unless hostname - ret[:HOSTNAME] = hostname = "(?:[a-zA-Z0-9\\-.]|%\\h\\h)+" - end - - # RFC 2373, APPENDIX B: - # IPv6address = hexpart [ ":" IPv4address ] - # IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT - # hexpart = hexseq | hexseq "::" [ hexseq ] | "::" [ hexseq ] - # hexseq = hex4 *( ":" hex4) - # hex4 = 1*4HEXDIG - # - # XXX: This definition has a flaw. "::" + IPv4address must be - # allowed too. Here is a replacement. - # - # IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT - ret[:IPV4ADDR] = ipv4addr = "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}" - # hex4 = 1*4HEXDIG - hex4 = "[#{PATTERN::HEX}]{1,4}" - # lastpart = hex4 | IPv4address - lastpart = "(?:#{hex4}|#{ipv4addr})" - # hexseq1 = *( hex4 ":" ) hex4 - hexseq1 = "(?:#{hex4}:)*#{hex4}" - # hexseq2 = *( hex4 ":" ) lastpart - hexseq2 = "(?:#{hex4}:)*#{lastpart}" - # IPv6address = hexseq2 | [ hexseq1 ] "::" [ hexseq2 ] - ret[:IPV6ADDR] = ipv6addr = "(?:#{hexseq2}|(?:#{hexseq1})?::(?:#{hexseq2})?)" - - # IPv6prefix = ( hexseq1 | [ hexseq1 ] "::" [ hexseq1 ] ) "/" 1*2DIGIT - # unused - - # ipv6reference = "[" IPv6address "]" (RFC 2732) - ret[:IPV6REF] = ipv6ref = "\\[#{ipv6addr}\\]" - - # host = hostname | IPv4address - # host = hostname | IPv4address | IPv6reference (RFC 2732) - ret[:HOST] = host = "(?:#{hostname}|#{ipv4addr}|#{ipv6ref})" - # port = *digit - ret[:PORT] = port = '\d*' - # hostport = host [ ":" port ] - ret[:HOSTPORT] = hostport = "#{host}(?::#{port})?" - - # userinfo = *( unreserved | escaped | - # ";" | ":" | "&" | "=" | "+" | "$" | "," ) - ret[:USERINFO] = userinfo = "(?:[#{unreserved};:&=+$,]|#{escaped})*" - - # pchar = unreserved | escaped | - # ":" | "@" | "&" | "=" | "+" | "$" | "," - pchar = "(?:[#{unreserved}:@&=+$,]|#{escaped})" - # param = *pchar - param = "#{pchar}*" - # segment = *pchar *( ";" param ) - segment = "#{pchar}*(?:;#{param})*" - # path_segments = segment *( "/" segment ) - ret[:PATH_SEGMENTS] = path_segments = "#{segment}(?:/#{segment})*" - - # server = [ [ userinfo "@" ] hostport ] - server = "(?:#{userinfo}@)?#{hostport}" - # reg_name = 1*( unreserved | escaped | "$" | "," | - # ";" | ":" | "@" | "&" | "=" | "+" ) - ret[:REG_NAME] = reg_name = "(?:[#{unreserved}$,;:@&=+]|#{escaped})+" - # authority = server | reg_name - authority = "(?:#{server}|#{reg_name})" - - # rel_segment = 1*( unreserved | escaped | - # ";" | "@" | "&" | "=" | "+" | "$" | "," ) - ret[:REL_SEGMENT] = rel_segment = "(?:[#{unreserved};@&=+$,]|#{escaped})+" - - # scheme = alpha *( alpha | digit | "+" | "-" | "." ) - ret[:SCHEME] = scheme = "[#{PATTERN::ALPHA}][\\-+.#{PATTERN::ALPHA}\\d]*" - - # abs_path = "/" path_segments - ret[:ABS_PATH] = abs_path = "/#{path_segments}" - # rel_path = rel_segment [ abs_path ] - ret[:REL_PATH] = rel_path = "#{rel_segment}(?:#{abs_path})?" - # net_path = "//" authority [ abs_path ] - ret[:NET_PATH] = net_path = "//#{authority}(?:#{abs_path})?" - - # hier_part = ( net_path | abs_path ) [ "?" query ] - ret[:HIER_PART] = hier_part = "(?:#{net_path}|#{abs_path})(?:\\?(?:#{query}))?" - # opaque_part = uric_no_slash *uric - ret[:OPAQUE_PART] = opaque_part = "#{uric_no_slash}#{uric}*" - - # absoluteURI = scheme ":" ( hier_part | opaque_part ) - ret[:ABS_URI] = abs_uri = "#{scheme}:(?:#{hier_part}|#{opaque_part})" - # relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ] - ret[:REL_URI] = rel_uri = "(?:#{net_path}|#{abs_path}|#{rel_path})(?:\\?#{query})?" - - # Bundler::URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ] - ret[:URI_REF] = "(?:#{abs_uri}|#{rel_uri})?(?:##{fragment})?" - - ret[:X_ABS_URI] = " - (#{scheme}): (?# 1: scheme) - (?: - (#{opaque_part}) (?# 2: opaque) - | - (?:(?: - //(?: - (?:(?:(#{userinfo})@)? (?# 3: userinfo) - (?:(#{host})(?::(\\d*))?))? (?# 4: host, 5: port) - | - (#{reg_name}) (?# 6: registry) - ) - | - (?!//)) (?# XXX: '//' is the mark for hostport) - (#{abs_path})? (?# 7: path) - )(?:\\?(#{query}))? (?# 8: query) - ) - (?:\\#(#{fragment}))? (?# 9: fragment) - " - - ret[:X_REL_URI] = " - (?: - (?: - // - (?: - (?:(#{userinfo})@)? (?# 1: userinfo) - (#{host})?(?::(\\d*))? (?# 2: host, 3: port) - | - (#{reg_name}) (?# 4: registry) - ) - ) - | - (#{rel_segment}) (?# 5: rel_segment) - )? - (#{abs_path})? (?# 6: abs_path) - (?:\\?(#{query}))? (?# 7: query) - (?:\\#(#{fragment}))? (?# 8: fragment) - " - - ret - end - - # Constructs the default Hash of Regexp's. - def initialize_regexp(pattern) - ret = {} - - # for Bundler::URI::split - ret[:ABS_URI] = Regexp.new('\A\s*+' + pattern[:X_ABS_URI] + '\s*\z', Regexp::EXTENDED) - ret[:REL_URI] = Regexp.new('\A\s*+' + pattern[:X_REL_URI] + '\s*\z', Regexp::EXTENDED) - - # for Bundler::URI::extract - ret[:URI_REF] = Regexp.new(pattern[:URI_REF]) - ret[:ABS_URI_REF] = Regexp.new(pattern[:X_ABS_URI], Regexp::EXTENDED) - ret[:REL_URI_REF] = Regexp.new(pattern[:X_REL_URI], Regexp::EXTENDED) - - # for Bundler::URI::escape/unescape - ret[:ESCAPED] = Regexp.new(pattern[:ESCAPED]) - ret[:UNSAFE] = Regexp.new("[^#{pattern[:UNRESERVED]}#{pattern[:RESERVED]}]") - - # for Generic#initialize - ret[:SCHEME] = Regexp.new("\\A#{pattern[:SCHEME]}\\z") - ret[:USERINFO] = Regexp.new("\\A#{pattern[:USERINFO]}\\z") - ret[:HOST] = Regexp.new("\\A#{pattern[:HOST]}\\z") - ret[:PORT] = Regexp.new("\\A#{pattern[:PORT]}\\z") - ret[:OPAQUE] = Regexp.new("\\A#{pattern[:OPAQUE_PART]}\\z") - ret[:REGISTRY] = Regexp.new("\\A#{pattern[:REG_NAME]}\\z") - ret[:ABS_PATH] = Regexp.new("\\A#{pattern[:ABS_PATH]}\\z") - ret[:REL_PATH] = Regexp.new("\\A#{pattern[:REL_PATH]}\\z") - ret[:QUERY] = Regexp.new("\\A#{pattern[:QUERY]}\\z") - ret[:FRAGMENT] = Regexp.new("\\A#{pattern[:FRAGMENT]}\\z") - - ret - end - - # Returns +uri+ as-is if it is Bundler::URI, or convert it to Bundler::URI if it is - # a String. - def convert_to_uri(uri) - if uri.is_a?(Bundler::URI::Generic) - uri - elsif uri = String.try_convert(uri) - parse(uri) - else - raise ArgumentError, - "bad argument (expected Bundler::URI object or Bundler::URI string)" - end - end - - end # class Parser - - # Backward compatibility for Bundler::URI::REGEXP::PATTERN::* - RFC2396_Parser.new.pattern.each_pair do |sym, str| - unless RFC2396_REGEXP::PATTERN.const_defined?(sym, false) - RFC2396_REGEXP::PATTERN.const_set(sym, str) - end - end -end # module Bundler::URI diff --git a/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb b/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb deleted file mode 100644 index d1ff28df2338b6..00000000000000 --- a/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb +++ /dev/null @@ -1,206 +0,0 @@ -# frozen_string_literal: true -module Bundler::URI - class RFC3986_Parser # :nodoc: - # Bundler::URI defined in RFC3986 - HOST = %r[ - (?\[(?: - (? - (?:\h{1,4}:){6} - (?\h{1,4}:\h{1,4} - | (?(?[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d) - \.\g\.\g\.\g) - ) - | ::(?:\h{1,4}:){5}\g - | \h{1,4}?::(?:\h{1,4}:){4}\g - | (?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g - | (?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g - | (?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g - | (?:(?:\h{1,4}:){,4}\h{1,4})?::\g - | (?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4} - | (?:(?:\h{1,4}:){,6}\h{1,4})?:: - ) - | (?v\h++\.[!$&-.0-9:;=A-Z_a-z~]++) - )\]) - | \g - | (?(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])*+) - ]x - - USERINFO = /(?:%\h\h|[!$&-.0-9:;=A-Z_a-z~])*+/ - - SCHEME = %r[[A-Za-z][+\-.0-9A-Za-z]*+].source - SEG = %r[(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/])].source - SEG_NC = %r[(?:%\h\h|[!$&-.0-9;=@A-Z_a-z~])].source - FRAGMENT = %r[(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/?])*+].source - - RFC3986_URI = %r[\A - (?#{SEG}){0} - (? - (?#{SCHEME}): - (?// - (? - (?:(?#{USERINFO.source})@)? - (?#{HOST.source.delete(" \n")}) - (?::(?\d*+))? - ) - (?(?:/\g*+)?) - | (?/((?!/)\g++)?) - | (?(?!/)\g++) - | (?) - ) - (?:\?(?[^\#]*+))? - (?:\#(?#{FRAGMENT}))? - )\z]x - - RFC3986_relative_ref = %r[\A - (?#{SEG}){0} - (? - (?// - (? - (?:(?#{USERINFO.source})@)? - (?#{HOST.source.delete(" \n")}(?\d*+))? - ) - (?(?:/\g*+)?) - | (?/\g*+) - | (?#{SEG_NC}++(?:/\g*+)?) - | (?) - ) - (?:\?(?[^#]*+))? - (?:\#(?#{FRAGMENT}))? - )\z]x - attr_reader :regexp - - def initialize - @regexp = default_regexp.each_value(&:freeze).freeze - end - - def split(uri) #:nodoc: - begin - uri = uri.to_str - rescue NoMethodError - raise InvalidURIError, "bad Bundler::URI (is not Bundler::URI?): #{uri.inspect}" - end - uri.ascii_only? or - raise InvalidURIError, "Bundler::URI must be ascii only #{uri.dump}" - if m = RFC3986_URI.match(uri) - query = m["query"] - scheme = m["scheme"] - opaque = m["path-rootless"] - if opaque - opaque << "?#{query}" if query - [ scheme, - nil, # userinfo - nil, # host - nil, # port - nil, # registry - nil, # path - opaque, - nil, # query - m["fragment"] - ] - else # normal - [ scheme, - m["userinfo"], - m["host"], - m["port"], - nil, # registry - (m["path-abempty"] || - m["path-absolute"] || - m["path-empty"]), - nil, # opaque - query, - m["fragment"] - ] - end - elsif m = RFC3986_relative_ref.match(uri) - [ nil, # scheme - m["userinfo"], - m["host"], - m["port"], - nil, # registry, - (m["path-abempty"] || - m["path-absolute"] || - m["path-noscheme"] || - m["path-empty"]), - nil, # opaque - m["query"], - m["fragment"] - ] - else - raise InvalidURIError, "bad Bundler::URI (is not Bundler::URI?): #{uri.inspect}" - end - end - - def parse(uri) # :nodoc: - Bundler::URI.for(*self.split(uri), self) - end - - def join(*uris) # :nodoc: - uris[0] = convert_to_uri(uris[0]) - uris.inject :merge - end - - # Compatibility for RFC2396 parser - def extract(str, schemes = nil, &block) # :nodoc: - warn "Bundler::URI::RFC3986_PARSER.extract is obsolete. Use Bundler::URI::RFC2396_PARSER.extract explicitly.", uplevel: 1 if $VERBOSE - RFC2396_PARSER.extract(str, schemes, &block) - end - - # Compatibility for RFC2396 parser - def make_regexp(schemes = nil) # :nodoc: - warn "Bundler::URI::RFC3986_PARSER.make_regexp is obsolete. Use Bundler::URI::RFC2396_PARSER.make_regexp explicitly.", uplevel: 1 if $VERBOSE - RFC2396_PARSER.make_regexp(schemes) - end - - # Compatibility for RFC2396 parser - def escape(str, unsafe = nil) # :nodoc: - warn "Bundler::URI::RFC3986_PARSER.escape is obsolete. Use Bundler::URI::RFC2396_PARSER.escape explicitly.", uplevel: 1 if $VERBOSE - unsafe ? RFC2396_PARSER.escape(str, unsafe) : RFC2396_PARSER.escape(str) - end - - # Compatibility for RFC2396 parser - def unescape(str, escaped = nil) # :nodoc: - warn "Bundler::URI::RFC3986_PARSER.unescape is obsolete. Use Bundler::URI::RFC2396_PARSER.unescape explicitly.", uplevel: 1 if $VERBOSE - escaped ? RFC2396_PARSER.unescape(str, escaped) : RFC2396_PARSER.unescape(str) - end - - @@to_s = Kernel.instance_method(:to_s) - if @@to_s.respond_to?(:bind_call) - def inspect - @@to_s.bind_call(self) - end - else - def inspect - @@to_s.bind(self).call - end - end - - private - - def default_regexp # :nodoc: - { - SCHEME: %r[\A#{SCHEME}\z]o, - USERINFO: %r[\A#{USERINFO}\z]o, - HOST: %r[\A#{HOST}\z]o, - ABS_PATH: %r[\A/#{SEG}*+\z]o, - REL_PATH: %r[\A(?!/)#{SEG}++\z]o, - QUERY: %r[\A(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/?])*+\z], - FRAGMENT: %r[\A#{FRAGMENT}\z]o, - OPAQUE: %r[\A(?:[^/].*)?\z], - PORT: /\A[\x09\x0a\x0c\x0d ]*+\d*[\x09\x0a\x0c\x0d ]*\z/, - } - end - - def convert_to_uri(uri) - if uri.is_a?(Bundler::URI::Generic) - uri - elsif uri = String.try_convert(uri) - parse(uri) - else - raise ArgumentError, - "bad argument (expected Bundler::URI object or Bundler::URI string)" - end - end - - end # class Parser -end # module Bundler::URI diff --git a/lib/bundler/vendor/uri/lib/uri/version.rb b/lib/bundler/vendor/uri/lib/uri/version.rb deleted file mode 100644 index ad76308e81d1ba..00000000000000 --- a/lib/bundler/vendor/uri/lib/uri/version.rb +++ /dev/null @@ -1,6 +0,0 @@ -module Bundler::URI - # :stopdoc: - VERSION = '1.1.1'.freeze - VERSION_CODE = VERSION.split('.').map{|s| s.rjust(2, '0')}.join.freeze - # :startdoc: -end diff --git a/lib/bundler/vendor/uri/lib/uri/ws.rb b/lib/bundler/vendor/uri/lib/uri/ws.rb deleted file mode 100644 index 10ae6f58344cdb..00000000000000 --- a/lib/bundler/vendor/uri/lib/uri/ws.rb +++ /dev/null @@ -1,83 +0,0 @@ -# frozen_string_literal: false -# = uri/ws.rb -# -# Author:: Matt Muller -# License:: You can redistribute it and/or modify it under the same term as Ruby. -# -# See Bundler::URI for general documentation -# - -require_relative 'generic' - -module Bundler::URI - - # - # The syntax of WS URIs is defined in RFC6455 section 3. - # - # Note that the Ruby Bundler::URI library allows WS URLs containing usernames and - # passwords. This is not legal as per the RFC, but used to be - # supported in Internet Explorer 5 and 6, before the MS04-004 security - # update. See . - # - class WS < Generic - # A Default port of 80 for Bundler::URI::WS. - DEFAULT_PORT = 80 - - # An Array of the available components for Bundler::URI::WS. - COMPONENT = %i[ - scheme - userinfo host port - path - query - ].freeze - - # - # == Description - # - # Creates a new Bundler::URI::WS object from components, with syntax checking. - # - # The components accepted are userinfo, host, port, path, and query. - # - # The components should be provided either as an Array, or as a Hash - # with keys formed by preceding the component names with a colon. - # - # If an Array is used, the components must be passed in the - # order [userinfo, host, port, path, query]. - # - # Example: - # - # uri = Bundler::URI::WS.build(host: 'www.example.com', path: '/foo/bar') - # - # uri = Bundler::URI::WS.build([nil, "www.example.com", nil, "/path", "query"]) - # - # Currently, if passed userinfo components this method generates - # invalid WS URIs as per RFC 1738. - # - def self.build(args) - tmp = Util.make_components_hash(self, args) - super(tmp) - end - - # - # == Description - # - # Returns the full path for a WS Bundler::URI, as required by Net::HTTP::Get. - # - # If the Bundler::URI contains a query, the full path is Bundler::URI#path + '?' + Bundler::URI#query. - # Otherwise, the path is simply Bundler::URI#path. - # - # Example: - # - # uri = Bundler::URI::WS.build(path: '/foo/bar', query: 'test=true') - # uri.request_uri # => "/foo/bar?test=true" - # - def request_uri - return unless @path - - url = @query ? "#@path?#@query" : @path.dup - url.start_with?(?/.freeze) ? url : ?/ + url - end - end - - register_scheme 'WS', WS -end diff --git a/lib/bundler/vendor/uri/lib/uri/wss.rb b/lib/bundler/vendor/uri/lib/uri/wss.rb deleted file mode 100644 index e8db1ceabfd861..00000000000000 --- a/lib/bundler/vendor/uri/lib/uri/wss.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: false -# = uri/wss.rb -# -# Author:: Matt Muller -# License:: You can redistribute it and/or modify it under the same term as Ruby. -# -# See Bundler::URI for general documentation -# - -require_relative 'ws' - -module Bundler::URI - - # The default port for WSS URIs is 443, and the scheme is 'wss:' rather - # than 'ws:'. Other than that, WSS URIs are identical to WS URIs; - # see Bundler::URI::WS. - class WSS < WS - # A Default port of 443 for Bundler::URI::WSS - DEFAULT_PORT = 443 - end - - register_scheme 'WSS', WSS -end diff --git a/lib/bundler/vendored_uri.rb b/lib/bundler/vendored_uri.rb index 2efddc65f997a8..bd5f4cd0316294 100644 --- a/lib/bundler/vendored_uri.rb +++ b/lib/bundler/vendored_uri.rb @@ -1,21 +1,21 @@ # frozen_string_literal: true -module Bundler; end +# Reuse RubyGems' vendored URI (Gem::URI). The Bundler gem ships a copy under +# lib/rubygems/vendor, so this resolves even on RubyGems versions that predate +# it. Fall back to the stdlib only when no vendored copy is available at all. -# Use RubyGems vendored copy when available. Otherwise fallback to Bundler -# vendored copy. The vendored copy in Bundler can be removed once support for -# RubyGems 3.5 is dropped. +unless defined?(Gem::URI) + begin + require "rubygems/vendor/uri/lib/uri" + rescue LoadError + require "uri" + Gem::URI = URI -begin - require "rubygems/vendor/uri/lib/uri" -rescue LoadError - require_relative "vendor/uri/lib/uri" - Gem::URI = Bundler::URI - - module Gem - def URI(uri) # rubocop:disable Naming/MethodName - Bundler::URI(uri) + module Gem + def URI(uri) # rubocop:disable Naming/MethodName + Kernel.URI(uri) + end + module_function :URI end - module_function :URI end end From c21542940312c268eae1f763faa8ce0fa60d65f9 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 25 Jun 2026 11:11:09 +0900 Subject: [PATCH 05/22] [ruby/rubygems] Track the shipped RubyGems URI in the file invariant The Bundler gem now ships lib/rubygems/vendor/uri, so include it in tracked_files_glob to keep the "ships the correct set of files" spec in sync with the gemspec. https://github.com/ruby/rubygems/commit/d159cabc72 Co-Authored-By: Claude Opus 4.8 --- spec/bundler/support/path.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb index 6b81cb19963dcf..c7d9196bf3fb09 100644 --- a/spec/bundler/support/path.rb +++ b/spec/bundler/support/path.rb @@ -345,7 +345,7 @@ def git_ls_files(glob) end def tracked_files_glob - ruby_core? ? "libexec/bundle* lib/bundler lib/bundler.rb spec/bundler man/bundle*" : "exe/bundle exe/bundler lib/bundler lib/bundler.rb bundler.gemspec CHANGELOG-bundler.md LICENSE-bundler.md README-bundler.md" + ruby_core? ? "libexec/bundle* lib/bundler lib/bundler.rb lib/rubygems/vendor/uri spec/bundler man/bundle*" : "exe/bundle exe/bundler lib/bundler lib/bundler.rb lib/rubygems/vendor/uri bundler.gemspec CHANGELOG-bundler.md LICENSE-bundler.md README-bundler.md" end def lib_tracked_files_glob From 37fa88d75bfe49ca48fd830c735415dbfd69d009 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 24 Jun 2026 23:37:17 +0100 Subject: [PATCH 06/22] Remove flaky test_escape_html_allocates_same_as_escapeHTML It's causing ruby CI to flake, because it's a badly designed test, a lot of thing can cause acouple extra allocation there and there so even if you invoke the exact same code twice, you might still allocate a bit more or a bit less. --- test/cgi/test_cgi_escape.rb | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/test/cgi/test_cgi_escape.rb b/test/cgi/test_cgi_escape.rb index ddd692b7ea5bb0..fa08953e9ba067 100644 --- a/test/cgi/test_cgi_escape.rb +++ b/test/cgi/test_cgi_escape.rb @@ -353,30 +353,6 @@ def test_escape_html_uses_native_implementation assert_equal CGI::EscapeExt, CGI.method(:h).owner assert_equal CGI::EscapeExt, CGI.method(:unescape_html).owner end - - def test_escape_html_allocates_same_as_escapeHTML - omit "C extension not available" unless defined?(CGI::EscapeExt) && CGI::EscapeExt.method_defined?(:escapeHTML) - - input = "'&\"<>hello world" - n = 100 - - # Warm up - 2.times { n.times { CGI.escapeHTML(input) } } - 2.times { n.times { CGI.escape_html(input) } } - - GC.disable - before = GC.stat(:total_allocated_objects) - n.times { CGI.escapeHTML(input) } - camel_allocs = GC.stat(:total_allocated_objects) - before - - before = GC.stat(:total_allocated_objects) - n.times { CGI.escape_html(input) } - snake_allocs = GC.stat(:total_allocated_objects) - before - GC.enable - - assert_equal camel_allocs, snake_allocs, - "escape_html allocated #{snake_allocs} objects vs escapeHTML #{camel_allocs} — alias may not be using the C extension" - end end class CGIEscapePureRubyTest < Test::Unit::TestCase From 713d4e1521db19b7ef87e74ec6490a8116cd3935 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 16 Jun 2026 08:55:17 +0900 Subject: [PATCH 07/22] [ruby/rubygems] Pass locked platforms to materialization instead of mutating candidates The locked platform set is a property of the candidates being materialized together, so compute it in Materialization and thread it through to candidate_platforms rather than writing it back onto each shared LazySpecification from the constructor. This drops the respond_to?(:locked_platforms=) guard and the now-unused accessor. https://github.com/ruby/rubygems/commit/f6fe69903e Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/bundler/lazy_specification.rb | 25 +++++++++++++------------ lib/bundler/match_platform.rb | 4 ++-- lib/bundler/materialization.rb | 13 ++----------- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb index 86b2da45169ab8..aad15176d6ee13 100644 --- a/lib/bundler/lazy_specification.rb +++ b/lib/bundler/lazy_specification.rb @@ -10,7 +10,7 @@ class LazySpecification attr_reader :name, :version, :platform, :materialization attr_accessor :source, :remote, :force_ruby_platform, :dependencies, :required_ruby_version, :required_rubygems_version - attr_accessor :overrides, :locked_platforms + attr_accessor :overrides # # For backwards compatibility with existing lockfiles, if the most specific @@ -50,7 +50,6 @@ def initialize(name, version, platform, source = nil, **materialization_options) @force_ruby_platform = default_force_ruby_platform @most_specific_locked_platform = nil @materialization = nil - @locked_platforms = nil end def missing? @@ -131,13 +130,13 @@ def materialize_for_cache materialize(self, &:first) end - def materialized_for_installation - @materialization = materialize_for_installation + def materialized_for_installation(locked_platforms = nil) + @materialization = materialize_for_installation(locked_platforms) self unless incomplete? end - def materialize_for_installation + def materialize_for_installation(locked_platforms) source.local! if use_exact_resolved_specifications? @@ -147,7 +146,7 @@ def materialize_for_installation # Exact spec is incompatible; in frozen mode, try to find a compatible platform variant # In non-frozen mode, return nil to trigger re-resolution and lockfile update if Bundler.frozen_bundle? - materialize([name, version]) {|specs| resolve_best_platform(specs, locked_platforms_only: true) } + materialize([name, version]) {|specs| resolve_best_platform(specs, locked_platforms: locked_platforms) } end else materialize([name, version]) {|specs| resolve_best_platform(specs) } @@ -188,12 +187,12 @@ def use_exact_resolved_specifications? # Try platforms in order of preference until finding a compatible spec. # Used for legacy lockfiles and as a fallback when the exact locked spec # is incompatible. Falls back to frozen bundle behavior if none match. - def resolve_best_platform(specs, locked_platforms_only: false) - find_compatible_platform_spec(specs, locked_platforms_only: locked_platforms_only) || frozen_bundle_fallback(specs) + def resolve_best_platform(specs, locked_platforms: nil) + find_compatible_platform_spec(specs, locked_platforms: locked_platforms) || frozen_bundle_fallback(specs) end - def find_compatible_platform_spec(specs, locked_platforms_only: false) - candidate_platforms(locked_platforms_only: locked_platforms_only).each do |plat| + def find_compatible_platform_spec(specs, locked_platforms: nil) + candidate_platforms(locked_platforms).each do |plat| candidates = MatchPlatform.select_best_platform_match(specs, plat) spec = choose_compatible(candidates, fallback_to_non_installable: false) return spec if spec @@ -203,10 +202,12 @@ def find_compatible_platform_spec(specs, locked_platforms_only: false) # Platforms to try in order of preference. Ruby platform is last since it # requires compilation, but works when precompiled gems are incompatible. - def candidate_platforms(locked_platforms_only: false) + # When a set of locked platforms is given (frozen mode), restrict the + # candidates to those, so we never materialize a platform that wasn't locked. + def candidate_platforms(locked_platforms = nil) target = source.is_a?(Source::Path) ? platform : Bundler.local_platform platforms = [target, platform, Gem::Platform::RUBY].uniq - return platforms unless locked_platforms_only && locked_platforms + return platforms unless locked_platforms platforms & locked_platforms end diff --git a/lib/bundler/match_platform.rb b/lib/bundler/match_platform.rb index 479818e5ec9e09..11d510ba6c47f2 100644 --- a/lib/bundler/match_platform.rb +++ b/lib/bundler/match_platform.rb @@ -15,9 +15,9 @@ def self.select_best_platform_match(specs, platform, force_ruby: false, prefer_l Gem::Platform.sort_and_filter_best_platform_match(matching, platform) end - def self.select_best_local_platform_match(specs, force_ruby: false) + def self.select_best_local_platform_match(specs, force_ruby: false, locked_platforms: nil) local = Bundler.local_platform - matching = select_all_platform_match(specs, local, force_ruby: force_ruby).filter_map(&:materialized_for_installation) + matching = select_all_platform_match(specs, local, force_ruby: force_ruby).filter_map {|spec| spec.materialized_for_installation(locked_platforms) } Gem::Platform.sort_best_platform_match(matching, local) end diff --git a/lib/bundler/materialization.rb b/lib/bundler/materialization.rb index d73e9124a82375..988d8751ac455e 100644 --- a/lib/bundler/materialization.rb +++ b/lib/bundler/materialization.rb @@ -12,7 +12,7 @@ def initialize(dep, platform, candidates:) @dep = dep @platform = platform @candidates = candidates - set_locked_platforms + @locked_platforms = candidates&.map(&:platform) end def complete? @@ -25,7 +25,7 @@ def specs elsif platform MatchPlatform.select_best_platform_match(@candidates, platform, force_ruby: dep.force_ruby_platform) else - MatchPlatform.select_best_local_platform_match(@candidates, force_ruby: dep.force_ruby_platform || dep.default_force_ruby_platform) + MatchPlatform.select_best_local_platform_match(@candidates, force_ruby: dep.force_ruby_platform || dep.default_force_ruby_platform, locked_platforms: @locked_platforms) end end @@ -56,14 +56,5 @@ def incomplete_specs private attr_reader :dep, :platform - - def set_locked_platforms - return unless @candidates - - platforms = @candidates.map(&:platform) - @candidates.each do |candidate| - candidate.locked_platforms = platforms if candidate.respond_to?(:locked_platforms=) - end - end end end From fc013062f923046dfd94c5d5190bd9ca94d512d1 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 16 Jun 2026 08:55:24 +0900 Subject: [PATCH 08/22] [ruby/rubygems] Guide users to the ruby fallback variant on frozen install errors When a platform-specific variant is the only thing locked and it is incompatible with the current Ruby, frozen mode fails at install time with no hint about the fix. Point users at re-resolving outside frozen mode, which adds the ruby variant to the lockfile as a fallback. https://github.com/ruby/rubygems/commit/7ad8850e47 Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/bundler/installer.rb | 21 +++++++++++++++---- .../install/gemfile/specific_platform_spec.rb | 2 ++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb index aac91087cd20a2..0f6d59a7161c61 100644 --- a/lib/bundler/installer.rb +++ b/lib/bundler/installer.rb @@ -225,16 +225,29 @@ def ensure_specs_are_compatible! overrides = @definition.overrides @definition.specs.each do |spec| unless spec.matches_current_ruby_with_overrides?(overrides) - raise InstallError, "#{spec.full_name} requires ruby version #{spec.required_ruby_version}, " \ - "which is incompatible with the current version, #{Gem.ruby_version}" + raise InstallError, incompatibility_message(spec, "requires ruby version #{spec.required_ruby_version}, " \ + "which is incompatible with the current version, #{Gem.ruby_version}") end unless spec.matches_current_rubygems_with_overrides?(overrides) - raise InstallError, "#{spec.full_name} requires rubygems version #{spec.required_rubygems_version}, " \ - "which is incompatible with the current version, #{Gem.rubygems_version}" + raise InstallError, incompatibility_message(spec, "requires rubygems version #{spec.required_rubygems_version}, " \ + "which is incompatible with the current version, #{Gem.rubygems_version}") end end end + # Build the error raised when a locked spec can't run on the current Ruby or + # RubyGems. When the offending spec is a platform-specific variant locked in + # frozen mode, point users at the fix: re-resolving outside frozen mode adds + # the Ruby (non platform-specific) fallback variant to the lockfile. + def incompatibility_message(spec, reason) + message = "#{spec.full_name} #{reason}" + return message unless Bundler.frozen_bundle? && spec.platform != Gem::Platform::RUBY + + message + "\n\nThe lockfile only includes the #{spec.platform} variant of #{spec.name}. " \ + "If a compatible Ruby variant is available, run `bundle install` outside of frozen/deployment mode " \ + "to add it to the lockfile as a fallback, then commit the updated #{SharedHelpers.relative_lockfile_path}." + end + def lock @definition.lock end diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb index eeda764c39f5d7..8b48cc83c1b6fe 100644 --- a/spec/bundler/install/gemfile/specific_platform_spec.rb +++ b/spec/bundler/install/gemfile/specific_platform_spec.rb @@ -352,6 +352,8 @@ bundle "install --verbose", env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false expect(exitstatus).not_to eq(0) expect(err).to include("nokogiri-1.18.10-x86_64-linux requires ruby version < #{Gem.ruby_version}") + expect(err).to include("The lockfile only includes the x86_64-linux variant of nokogiri") + expect(err).to include("run `bundle install` outside of frozen/deployment mode") end end end From c818da03e66848882a84582240965db3997f8d15 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 25 Jun 2026 11:37:00 +0900 Subject: [PATCH 09/22] [ruby/rubygems] Pass locked_platforms by keyword to candidate_platforms Keeps the locked platform argument keyworded along the whole resolve_best_platform path instead of switching to a positional at the last hop. https://github.com/ruby/rubygems/commit/1d5f35ad0e Co-Authored-By: Claude Opus 4.8 (1M context) --- lib/bundler/lazy_specification.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb index aad15176d6ee13..13d7588cd01170 100644 --- a/lib/bundler/lazy_specification.rb +++ b/lib/bundler/lazy_specification.rb @@ -192,7 +192,7 @@ def resolve_best_platform(specs, locked_platforms: nil) end def find_compatible_platform_spec(specs, locked_platforms: nil) - candidate_platforms(locked_platforms).each do |plat| + candidate_platforms(locked_platforms: locked_platforms).each do |plat| candidates = MatchPlatform.select_best_platform_match(specs, plat) spec = choose_compatible(candidates, fallback_to_non_installable: false) return spec if spec @@ -204,7 +204,7 @@ def find_compatible_platform_spec(specs, locked_platforms: nil) # requires compilation, but works when precompiled gems are incompatible. # When a set of locked platforms is given (frozen mode), restrict the # candidates to those, so we never materialize a platform that wasn't locked. - def candidate_platforms(locked_platforms = nil) + def candidate_platforms(locked_platforms: nil) target = source.is_a?(Source::Path) ? platform : Bundler.local_platform platforms = [target, platform, Gem::Platform::RUBY].uniq return platforms unless locked_platforms From 4ea11c600da205d4b5bfd26a93596d534f87baae Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 25 Jun 2026 11:37:00 +0900 Subject: [PATCH 10/22] [ruby/rubygems] Cover that the ruby fallback hint is skipped for ruby variants When the only locked variant is already the ruby one, the install error should not point at a ruby fallback that doesn't exist. Guards the early return in incompatibility_message against regressions. https://github.com/ruby/rubygems/commit/58bb74c7ba Co-Authored-By: Claude Opus 4.8 (1M context) --- .../install/gemfile/specific_platform_spec.rb | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb index 8b48cc83c1b6fe..ab194dbcc52dbc 100644 --- a/spec/bundler/install/gemfile/specific_platform_spec.rb +++ b/spec/bundler/install/gemfile/specific_platform_spec.rb @@ -356,6 +356,43 @@ expect(err).to include("run `bundle install` outside of frozen/deployment mode") end end + + it "does not suggest a ruby fallback variant when the locked incompatible variant is already the ruby variant" do + build_repo4 do + build_gem "nokogiri", "1.18.10" do |s| + s.required_ruby_version = "< #{Gem.ruby_version}" + end + end + + gemfile <<~G + source "https://gem.repo4" + + gem "nokogiri" + G + + lockfile <<-L + GEM + remote: https://gem.repo4/ + specs: + nokogiri (1.18.10) + + PLATFORMS + x86_64-linux + + DEPENDENCIES + nokogiri + + BUNDLED WITH + #{Bundler::VERSION} + L + + simulate_platform "x86_64-linux" do + bundle "install --verbose", env: { "BUNDLE_FROZEN" => "true" }, raise_on_error: false + expect(exitstatus).not_to eq(0) + expect(err).to include("nokogiri-1.18.10 requires ruby version < #{Gem.ruby_version}") + expect(err).not_to include("The lockfile only includes") + end + end end it "doesn't discard previously installed platform specific gem and fall back to ruby on subsequent bundles" do From 12ef8a66ab8433bfd5ef0248dd76ba0cfd592cc8 Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Wed, 24 Jun 2026 11:23:56 -0700 Subject: [PATCH 11/22] [ruby/error_highlight] Return nil instead of raising NotImplementedError for def/lambda/block args ErrorHighlight.spot defaults point_type to :args for ArgumentError. When an ArgumentError originates from argument binding (e.g. a missing required keyword), its first backtrace location maps to a def/lambda/block node. The spotter had no implementation for the :args point type on those nodes and did `raise NotImplementedError`. This is a regression from 0.7.0 (and in Ruby 4.0 compared to Ruby 3.4). ErrorHighlight.spot only rescues SyntaxError/SystemCallError/ArgumentError, and NotImplementedError is not a StandardError, so it escaped through CoreExt#generate_snippet -> Exception#detailed_message / #full_message and replaced the original exception. This is normally hidden because the VM's "missing keyword" message matches the keyword regex in generate_snippet and takes the safe :name branch, but it surfaces as soon as the ArgumentError is re-raised/wrapped with a custom message that falls into the :args else branch. Return nil (the documented "no spot" result) for these cases instead, matching the graceful-degradation behavior of the surrounding feature. The fix is applied consistently across both compilers: - prism: def_node / lambda_node / block_node - parse.y: DEFN / DEFS (raised NotImplementedError) and LAMBDA / ITER (silently returned a spot for :args, inconsistent with prism) All of these branches were introduced together by the experimental "wrong number of arguments" snippet feature and are only intended for point_type :name, so returning nil for any other point_type keeps the two parsers in sync. https://github.com/ruby/error_highlight/commit/ca126db2db --- lib/error_highlight/base.rb | 21 +++++-- test/error_highlight/test_error_highlight.rb | 63 ++++++++++++++++++++ 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/lib/error_highlight/base.rb b/lib/error_highlight/base.rb index 5fffe5ec34e360..e1a9c0e46d51e3 100644 --- a/lib/error_highlight/base.rb +++ b/lib/error_highlight/base.rb @@ -235,17 +235,21 @@ def spot spot_op_cdecl when :DEFN - raise NotImplementedError if @point_type != :name + # There is nothing to highlight for the arguments of a method + # definition, so just return nil instead of raising. + return nil if @point_type != :name spot_defn when :DEFS - raise NotImplementedError if @point_type != :name + return nil if @point_type != :name spot_defs when :LAMBDA + return nil if @point_type != :name spot_lambda when :ITER + return nil if @point_type != :name spot_iter when :call_node @@ -294,7 +298,12 @@ def spot when :name prism_spot_def_for_name when :args - raise NotImplementedError + # There is nothing to highlight for the arguments of a method + # definition (e.g. an ArgumentError for a missing required keyword + # is spotted with point_type: :args). Return nil instead of raising + # NotImplementedError, which would otherwise escape + # Exception#detailed_message / #full_message. + return nil end when :lambda_node @@ -302,7 +311,8 @@ def spot when :name prism_spot_lambda_for_name when :args - raise NotImplementedError + # See the comment for :def_node above. + return nil end when :block_node @@ -310,7 +320,8 @@ def spot when :name prism_spot_block_for_name when :args - raise NotImplementedError + # See the comment for :def_node above. + return nil end end diff --git a/test/error_highlight/test_error_highlight.rb b/test/error_highlight/test_error_highlight.rb index 5f664de502e5e7..5de70f59c0cc76 100644 --- a/test/error_highlight/test_error_highlight.rb +++ b/test/error_highlight/test_error_highlight.rb @@ -1752,6 +1752,69 @@ def test_singleton_method_multiple_missing_keywords end end + def def_with_required_keyword(x:) + x + end + + def test_spot_with_args_point_type_on_def_node_returns_nil + # A method with a required keyword argument called without it raises an + # ArgumentError whose first backtrace location maps to the `def` node. + # ErrorHighlight.spot defaults point_type to :args for ArgumentError, and + # there is no way to highlight "the arguments" of a definition, so spot + # must return nil instead of raising NotImplementedError. + begin + def_with_required_keyword + rescue ArgumentError => exc + end + + assert_nil(ErrorHighlight.spot(exc)) + end + + def test_spot_with_args_point_type_on_lambda_node_returns_nil + l = lambda { |x:| x } + begin + l.call + rescue ArgumentError => exc + end + + assert_nil(ErrorHighlight.spot(exc)) + end + + define_method(:block_with_required_keyword) { |x:| x } + + def test_spot_with_args_point_type_on_block_node_returns_nil + begin + block_with_required_keyword + rescue ArgumentError => exc + end + + assert_nil(ErrorHighlight.spot(exc)) + end + + def test_detailed_message_does_not_raise_when_argument_error_is_rewrapped + # This reproduces a real-world crash: an ArgumentError originating from a + # missing required keyword (which maps to the `def` node) is re-raised with + # a custom message. The custom message no longer matches the keyword regex + # in CoreExt#generate_snippet, so the :args branch is taken. The spotter + # must not raise NotImplementedError (which is not a StandardError and would + # escape detailed_message / full_message). + begin + def_with_required_keyword + rescue ArgumentError => original + exc = original.exception("a custom message that is not a keyword error") + end + + msg = nil + assert_nothing_raised do + msg = exc.detailed_message(highlight: false) + end + assert_match("a custom message that is not a keyword error", msg) + + assert_nothing_raised do + exc.full_message(highlight: false) + end + end + private def find_node_by_id(node, node_id) From b3cf0974da77a2c43139d9a2be5561ac13cb04cc Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 25 Jun 2026 07:33:27 +0100 Subject: [PATCH 12/22] re.c: Remove unecessary RBOOL in `rb_reg_equal` --- re.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/re.c b/re.c index ee7f3ba3eb3e8c..2005bdde147945 100644 --- a/re.c +++ b/re.c @@ -3637,7 +3637,7 @@ rb_reg_equal(VALUE re1, VALUE re2) // src is a fstring, so a pointer comparison is enough RUBY_ASSERT(FL_TEST_RAW(RREGEXP_SRC(re1), RSTRING_FSTR)); RUBY_ASSERT(FL_TEST_RAW(RREGEXP_SRC(re2), RSTRING_FSTR)); - if (RBOOL(RREGEXP_SRC(re1) != RREGEXP_SRC(re2))) return Qfalse; + if (RREGEXP_SRC(re1) != RREGEXP_SRC(re2)) return Qfalse; if (FL_TEST(re1, KCODE_FIXED) != FL_TEST(re2, KCODE_FIXED)) return Qfalse; if (RREGEXP_PTR(re1)->options != RREGEXP_PTR(re2)->options) return Qfalse; From f400c872d72b68f02d080e20f9e4630b46366618 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Thu, 25 Jun 2026 14:24:37 +0900 Subject: [PATCH 13/22] [ruby/json] Fix wrong call-seq for `JSON::ResumableParser#eos?` https://github.com/ruby/json/commit/e1ee4b0e22 --- ext/json/parser/parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 9a86cd58b7fc43..757c58701bf3a0 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -2712,7 +2712,7 @@ static VALUE cResumableParser_rest(VALUE self) } /* - * call-seq: value? -> true or false + * call-seq: eos? -> true or false * * Returns whether the internal buffer has been entirely consumed. */ From a746dd83748376ba3fb0cdcb56666a728e48cf4c Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 24 Jun 2026 11:32:29 +0200 Subject: [PATCH 14/22] [ruby/json] Declare types as RUBY_TYPED_THREAD_SAFE_FREE Ref: https://github.com/ruby/ruby/pull/17395 https://github.com/ruby/json/commit/fa69e94b66 --- ext/json/generator/generator.c | 2 +- ext/json/json.h | 4 ++++ ext/json/parser/parser.c | 8 ++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 08c1fd0db6d894..d4164051f78b3f 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -739,7 +739,7 @@ static const rb_data_type_t JSON_Generator_State_type = { .dsize = State_memsize, .dcompact = State_compact, }, - .flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE, + .flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE, }; static void state_init(JSON_Generator_State *state) diff --git a/ext/json/json.h b/ext/json/json.h index f39e795a4e5081..afddbe2a2070c4 100644 --- a/ext/json/json.h +++ b/ext/json/json.h @@ -31,6 +31,10 @@ /* shims */ +#ifndef RUBY_TYPED_THREAD_SAFE_FREE +#define RUBY_TYPED_THREAD_SAFE_FREE RUBY_TYPED_FREE_IMMEDIATELY +#endif + #ifndef UNDEF_P #define UNDEF_P(val) (val == Qundef) #endif diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index 757c58701bf3a0..b297363f49d0c1 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -314,7 +314,7 @@ static const rb_data_type_t JSON_Parser_rvalue_stack_type = { }, // We deliberately don't declare rvalue_stack as RUBY_TYPED_WB_PROTECTED // because it churns a lot of values so trigering write barriers every time is very costly. - .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_EMBEDDABLE, + .flags = RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_EMBEDDABLE, }; static rvalue_stack *rvalue_stack_spill(rvalue_stack *old_stack, VALUE *handle, rvalue_stack **stack_ref) @@ -511,7 +511,7 @@ static const rb_data_type_t JSON_Parser_frame_stack_type = { .dfree = json_frame_stack_free, .dsize = json_frame_stack_memsize, }, - .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE, + .flags = RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE, }; static json_frame_stack *json_frame_stack_spill(json_frame_stack *old_stack, VALUE *handle, json_frame_stack **stack_ref) @@ -2180,7 +2180,7 @@ static const rb_data_type_t JSON_ParserConfig_type = { .dsize = JSON_ParserConfig_memsize, .dcompact = JSON_ParserConfig_compact, }, - .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE, + .flags = RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FROZEN_SHAREABLE | RUBY_TYPED_EMBEDDABLE, }; static VALUE cJSON_parser_s_allocate(VALUE klass) @@ -2265,7 +2265,7 @@ static const rb_data_type_t JSON_ResumableParser_type = { // RUBY_TYPED_WB_PROTECTED is deliberately not declared because // this is a superset of JSON_Parser_rvalue_stack_type, so we'd need // to trigger a lot of write barriers. - .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_EMBEDDABLE, + .flags = RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_EMBEDDABLE, }; static VALUE cResumableParser_allocate(VALUE klass) From ac135260de1d1302e989b31e9d83cf1cab9aa281 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 25 Jun 2026 01:54:36 +0900 Subject: [PATCH 15/22] Fix unused-but-set-global warnings in clang-23 --- complex.c | 3 --- hash.c | 6 ++++++ io.c | 7 +++---- marshal.c | 4 +--- rational.c | 4 +--- regparse.c | 1 + scheduler.c | 6 +++++- 7 files changed, 17 insertions(+), 14 deletions(-) diff --git a/complex.c b/complex.c index 8c661f20191515..9913a3fa1e7c82 100644 --- a/complex.c +++ b/complex.c @@ -40,7 +40,6 @@ static VALUE RFLOAT_0; VALUE rb_cComplex; static ID id_abs, id_arg, - id_denominator, id_numerator, id_real_p, id_i_real, id_i_imag, id_finite_p, id_infinite_p, id_rationalize, id_PI; @@ -2696,8 +2695,6 @@ Init_Complex(void) VALUE compat; id_abs = rb_intern_const("abs"); id_arg = rb_intern_const("arg"); - id_denominator = rb_intern_const("denominator"); - id_numerator = rb_intern_const("numerator"); id_real_p = rb_intern_const("real?"); id_i_real = rb_intern_const("@real"); id_i_imag = rb_intern_const("@image"); /* @image, not @imag */ diff --git a/hash.c b/hash.c index 506b2afe7925f2..182f41a288adab 100644 --- a/hash.c +++ b/hash.c @@ -5138,7 +5138,11 @@ rb_hash_new_with_bulk_insert(long argc, const VALUE *argv) return val; } +#undef USE_ORIGENVIRON +#if !defined(_WIN32) && !(defined(HAVE_SETENV) && defined(HAVE_UNSETENV)) +# define USE_ORIGENVIRON 1 static char **origenviron; +#endif #ifdef _WIN32 #define GET_ENVIRON(e) ((e) = rb_w32_get_environ()) #define FREE_ENVIRON(e) rb_w32_free_environ(e) @@ -7678,7 +7682,9 @@ Init_Hash(void) * Hack to get RDoc to regard ENV as a class: * envtbl = rb_define_class("ENV", rb_cObject); */ +#ifdef USE_ORIGENVIRON origenviron = environ; +#endif envtbl = TypedData_Wrap_Struct(rb_cObject, &env_data_type, NULL); rb_extend_object(envtbl, rb_mEnumerable); RB_OBJ_SET_SHAREABLE(envtbl); diff --git a/io.c b/io.c index f03bcb3be06b23..27357dd44928f2 100644 --- a/io.c +++ b/io.c @@ -193,8 +193,10 @@ VALUE rb_mWaitWritable; static VALUE rb_eEAGAINWaitReadable; static VALUE rb_eEAGAINWaitWritable; +#if EAGAIN != EWOULDBLOCK static VALUE rb_eEWOULDBLOCKWaitReadable; static VALUE rb_eEWOULDBLOCKWaitWritable; +#endif static VALUE rb_eEINPROGRESSWaitWritable; static VALUE rb_eEINPROGRESSWaitReadable; @@ -208,7 +210,7 @@ VALUE rb_default_rs; static VALUE argf; -static ID id_write, id_read, id_getc, id_flush, id_readpartial, id_set_encoding, id_fileno; +static ID id_write, id_read, id_flush, id_readpartial, id_set_encoding, id_fileno; static VALUE sym_mode, sym_perm, sym_flags, sym_extenc, sym_intenc, sym_encoding, sym_open_args; static VALUE sym_textmode, sym_binmode, sym_autoclose; static VALUE sym_SET, sym_CUR, sym_END; @@ -15698,7 +15700,6 @@ Init_IO(void) id_write = rb_intern_const("write"); id_read = rb_intern_const("read"); - id_getc = rb_intern_const("getc"); id_flush = rb_intern_const("flush"); id_readpartial = rb_intern_const("readpartial"); id_set_encoding = rb_intern_const("set_encoding"); @@ -15746,10 +15747,8 @@ Init_IO(void) rb_eEAGAINWaitWritable = rb_define_class_under(rb_cIO, "EAGAINWaitWritable", rb_eEAGAIN); rb_include_module(rb_eEAGAINWaitWritable, rb_mWaitWritable); #if EAGAIN == EWOULDBLOCK - rb_eEWOULDBLOCKWaitReadable = rb_eEAGAINWaitReadable; /* same as IO::EAGAINWaitReadable */ rb_define_const(rb_cIO, "EWOULDBLOCKWaitReadable", rb_eEAGAINWaitReadable); - rb_eEWOULDBLOCKWaitWritable = rb_eEAGAINWaitWritable; /* same as IO::EAGAINWaitWritable */ rb_define_const(rb_cIO, "EWOULDBLOCKWaitWritable", rb_eEAGAINWaitWritable); #else diff --git a/marshal.c b/marshal.c index 39b25552429ff3..3175ce4c9bf0ea 100644 --- a/marshal.c +++ b/marshal.c @@ -99,7 +99,7 @@ shortlen(size_t len, BDIGIT *ds) #define TYPE_LINK '@' static ID s_dump, s_load, s_mdump, s_mload; -static ID s_dump_data, s_load_data, s_alloc, s_call; +static ID s_dump_data, s_load_data, s_call; static ID s_getbyte, s_read, s_write, s_binmode; static ID s_encoding_short, s_ruby2_keywords_flag; #define s_encoding_long rb_id_encoding() @@ -110,7 +110,6 @@ static ID s_encoding_short, s_ruby2_keywords_flag; #define name_s_mload "marshal_load" #define name_s_dump_data "_dump_data" #define name_s_load_data "_load_data" -#define name_s_alloc "_alloc" #define name_s_call "call" #define name_s_getbyte "getbyte" #define name_s_read "read" @@ -2577,7 +2576,6 @@ Init_marshal(void) set_id(s_mload); set_id(s_dump_data); set_id(s_load_data); - set_id(s_alloc); set_id(s_call); set_id(s_getbyte); set_id(s_read); diff --git a/rational.c b/rational.c index b031838d6968c4..f40c412506e273 100644 --- a/rational.c +++ b/rational.c @@ -53,8 +53,7 @@ RBIMPL_WARNING_POP() VALUE rb_cRational; -static ID id_abs, id_integer_p, - id_i_num, id_i_den; +static ID id_abs, id_i_num, id_i_den; #define id_idiv idDiv #define id_to_i idTo_i @@ -2769,7 +2768,6 @@ Init_Rational(void) { VALUE compat; id_abs = rb_intern_const("abs"); - id_integer_p = rb_intern_const("integer?"); id_i_num = rb_intern_const("@numerator"); id_i_den = rb_intern_const("@denominator"); diff --git a/regparse.c b/regparse.c index 123b3015a5a936..52f4ea193a8581 100644 --- a/regparse.c +++ b/regparse.c @@ -114,6 +114,7 @@ extern void onig_set_warn_func(OnigWarnFunc f) extern void onig_set_verb_warn_func(OnigWarnFunc f) { onig_verb_warn = f; + (void)onig_verb_warn; } static void CC_DUP_WARN(ScanEnv *env, OnigCodePoint from, OnigCodePoint to); diff --git a/scheduler.c b/scheduler.c index 7a2671fead39dc..007308d13b830d 100644 --- a/scheduler.c +++ b/scheduler.c @@ -30,7 +30,9 @@ static ID id_unblock; static ID id_yield; +#ifdef FIBER_SCHEDULER_CALL_TIMEOUT_AFTER static ID id_timeout_after; +#endif static ID id_kernel_sleep; static ID id_process_wait; @@ -314,7 +316,9 @@ Init_Fiber_Scheduler(void) id_unblock = rb_intern_const("unblock"); id_yield = rb_intern_const("yield"); +#ifdef FIBER_SCHEDULER_CALL_TIMEOUT_AFTER id_timeout_after = rb_intern_const("timeout_after"); +#endif id_kernel_sleep = rb_intern_const("kernel_sleep"); id_process_wait = rb_intern_const("process_wait"); @@ -555,7 +559,7 @@ rb_fiber_scheduler_yield(VALUE scheduler) return rb_fiber_scheduler_kernel_sleep(scheduler, RB_INT2NUM(0)); } -#if 0 +#ifdef FIBER_SCHEDULER_CALL_TIMEOUT_AFTER /* * Document-method: Fiber::Scheduler#timeout_after * call-seq: timeout_after(duration, exception_class, *exception_arguments, &block) -> result of block From 91edd1b4f095c5f5625c61d8b9052222ac3d928b Mon Sep 17 00:00:00 2001 From: git Date: Thu, 25 Jun 2026 07:58:24 +0000 Subject: [PATCH 16/22] [DOC] Update bundled gems list at ac135260de1d1302e989b31e9d83cf --- NEWS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 71f1b3f929ce2c..cb208d73446799 100644 --- a/NEWS.md +++ b/NEWS.md @@ -114,7 +114,7 @@ releases. * net-imap 0.6.4.1 * 0.6.2 to [v0.6.3][net-imap-v0.6.3], [v0.6.4][net-imap-v0.6.4], [v0.6.4.1][net-imap-v0.6.4.1] * rbs 4.0.3 - * 3.10.0 to [v3.10.1][rbs-v3.10.1], [v3.10.2][rbs-v3.10.2], [v3.10.3][rbs-v3.10.3], [v3.10.4][rbs-v3.10.4], [v4.0.0.dev.5][rbs-v4.0.0.dev.5], [v4.0.0][rbs-v4.0.0], [v4.0.2][rbs-v4.0.2] + * 3.10.0 to [v3.10.1][rbs-v3.10.1], [v3.10.2][rbs-v3.10.2], [v3.10.3][rbs-v3.10.3], [v3.10.4][rbs-v3.10.4], [v4.0.0.dev.5][rbs-v4.0.0.dev.5], [v4.0.0][rbs-v4.0.0], [v4.0.2][rbs-v4.0.2], [v4.0.3][rbs-v4.0.3] * typeprof 0.32.0 * mutex_m 0.3.0 * bigdecimal 4.1.2 @@ -271,6 +271,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [rbs-v4.0.0.dev.5]: https://github.com/ruby/rbs/releases/tag/v4.0.0.dev.5 [rbs-v4.0.0]: https://github.com/ruby/rbs/releases/tag/v4.0.0 [rbs-v4.0.2]: https://github.com/ruby/rbs/releases/tag/v4.0.2 +[rbs-v4.0.3]: https://github.com/ruby/rbs/releases/tag/v4.0.3 [bigdecimal-v4.1.0]: https://github.com/ruby/bigdecimal/releases/tag/v4.1.0 [bigdecimal-v4.1.1]: https://github.com/ruby/bigdecimal/releases/tag/v4.1.1 [bigdecimal-v4.1.2]: https://github.com/ruby/bigdecimal/releases/tag/v4.1.2 From 061b1bca1bb049e060b545c2aac082fafb27babf Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 25 Jun 2026 17:25:02 +0900 Subject: [PATCH 17/22] [ruby/json] Fix unused-but-set-global warnings in clang-23 https://github.com/ruby/json/commit/168095ccb8 --- ext/json/parser/parser.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index b297363f49d0c1..4d9fa25baa5f93 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -5,7 +5,10 @@ static VALUE mJSON, eNestingError, eParserError, Encoding_UTF_8; static VALUE CNaN, CInfinity, CMinusInfinity, JSON_empty_string; -static ID i_new, i_try_convert, i_uminus, i_encode, i_at_line, i_at_column; +static ID i_new, i_try_convert, i_encode, i_at_line, i_at_column; +#ifndef HAVE_RB_STR_TO_INTERNED_STR +static ID i_uminus; +#endif static VALUE sym_max_nesting, sym_allow_nan, sym_allow_trailing_comma, sym_allow_comments, sym_allow_control_characters, sym_allow_invalid_escape, sym_symbolize_names, @@ -2812,7 +2815,9 @@ void Init_parser(void) i_new = rb_intern("new"); i_try_convert = rb_intern("try_convert"); +#ifndef HAVE_RB_STR_TO_INTERNED_STR i_uminus = rb_intern("-@"); +#endif i_encode = rb_intern("encode"); i_at_line = rb_intern("@line"); i_at_column = rb_intern("@column"); From b5c564c85d8bae158ad67d5e63991cbb94b72caa Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 25 Jun 2026 17:27:09 +0900 Subject: [PATCH 18/22] [ruby/openssl] Fix unused-but-set-global warnings in clang-23 https://github.com/ruby/openssl/commit/44bb2aa070 --- ext/openssl/ossl_asn1.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ext/openssl/ossl_asn1.c b/ext/openssl/ossl_asn1.c index 517212b4d5c11b..16eb6bd59f36a3 100644 --- a/ext/openssl/ossl_asn1.c +++ b/ext/openssl/ossl_asn1.c @@ -697,11 +697,12 @@ to_der_internal(VALUE self, int constructed, int indef_len, VALUE body) int tag_number = ossl_asn1_tag(self); int default_tag_number = ossl_asn1_default_tag(self); int body_length, total_length; + VALUE tagging = ossl_asn1_get_tagging(self); VALUE str; unsigned char *p; body_length = RSTRING_LENINT(body); - if (ossl_asn1_get_tagging(self) == sym_EXPLICIT) { + if (tagging == sym_EXPLICIT) { int inner_length, e_encoding = indef_len ? 2 : 1; if (default_tag_number == -1) @@ -722,7 +723,7 @@ to_der_internal(VALUE self, int constructed, int indef_len, VALUE body) ASN1_put_eoc(&p); /* For wrapper object */ } } - else { + else if (NIL_P(tagging) || tagging == sym_IMPLICIT) { total_length = ASN1_object_size(encoding, body_length, tag_number); str = rb_str_new(NULL, total_length); p = (unsigned char *)RSTRING_PTR(str); @@ -732,6 +733,9 @@ to_der_internal(VALUE self, int constructed, int indef_len, VALUE body) if (indef_len) ASN1_put_eoc(&p); } + else { + ossl_raise(eASN1Error, "invalid tagging method"); + } assert(p - (unsigned char *)RSTRING_PTR(str) == total_length); return str; } From 6df734ca4cbaaf142b0b8af220ae1e7044fe3338 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 25 Jun 2026 17:28:27 +0900 Subject: [PATCH 19/22] [ruby/psych] Fix unused-but-set-global warnings in clang-23 https://github.com/ruby/psych/commit/7c1bd707a9 --- ext/psych/psych_parser.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/ext/psych/psych_parser.c b/ext/psych/psych_parser.c index 00a2207b58911c..05a8fa9eb0b6a5 100644 --- a/ext/psych/psych_parser.c +++ b/ext/psych/psych_parser.c @@ -3,7 +3,6 @@ VALUE cPsychParser; static ID id_read; -static ID id_path; static ID id_empty; static ID id_start_stream; static ID id_end_stream; @@ -559,7 +558,6 @@ void Init_psych_parser(void) rb_define_method(cPsychParser, "mark", mark, 0); id_read = rb_intern("read"); - id_path = rb_intern("path"); id_empty = rb_intern("empty"); id_start_stream = rb_intern("start_stream"); id_end_stream = rb_intern("end_stream"); From d534fd1208672e152949440e6b79b50175fd1597 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Thu, 25 Jun 2026 07:36:59 -0500 Subject: [PATCH 20/22] [DOC] Doc for Pathname.mkpath --- pathname_builtin.rb | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index a8a392d9eaa971..7c42bc6a53a84a 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -293,10 +293,25 @@ def inspect # :nodoc: "#<#{self.class}:#{@path}>" end - # Creates a full path, including any intermediate directories that don't yet - # exist. + # :markup: markdown + # + # call-seq: + # mkpath(permissions = 0775) -> self + # + # Creates a directory at the path in `self`; + # creates intermediate directories as needed: + # + # ```ruby + # pn = Pathname('foo/bar/baz') + # pn.directory? # => false + # pn.mkpath # Creates directories 'foo', 'foo/bar', 'foo/bar/baz'. + # pn.directory? # => true + # pn.rmtree # Clean up. + # ``` # - # See FileUtils.mkpath and FileUtils.mkdir_p + # Directories are created with the given permissions; + # see {File Permissions}[rdoc-ref:File@File+Permissions]. + # The permissions for already-existing directories are not changed. def mkpath(mode: nil) path = @path == '/' ? @path : @path.chomp('/') From 3c3269bd6c6ab0236ee117eac28c3045e3426834 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Thu, 25 Jun 2026 07:38:21 -0500 Subject: [PATCH 21/22] [DOC] Update Pathname.birthtime (#17368) --- pathname_builtin.rb | 55 ++++++++++++++++----------------------------- 1 file changed, 19 insertions(+), 36 deletions(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 7c42bc6a53a84a..74298406c6a5b4 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -1133,42 +1133,25 @@ def atime() File.atime(@path) end # see [File System Timestamps](rdoc-ref:file/timestamps.md): # # ```ruby - # # Work in a temporary directory. - # Pathname.mktmpdir do |tmpdirpath| - # # A subdirectory therein, and its Pathname. - # dirpath = File.join(tmpdirpath, 'subdir') - # dir_pn = Pathname(dirpath) - # puts "Create directory; directory birthtime established." - # dir_pn.mkdir - # puts " Directory birthtime: #{dir_pn.birthtime}" - # sleep(1) - # - # # A file in the subdirectory, and its Pathname. - # filepath = File.join(dirpath, 't.txt') - # file_pn = Pathname(filepath) - # puts "Create file; file birthtime established; directory birthtime not updated." - # file_pn.write('foo') - # puts " File birthtime: #{file_pn.birthtime}" - # puts " Directory birthtime: #{dir_pn.birthtime}" - # sleep(1) - # puts "Write file; neither birthtime updated." - # file_pn.write('bar') - # puts " File birthtime: #{file_pn.birthtime}" - # puts " Directory birthtime: #{dir_pn.birthtime}" - # end - # ``` - # - # Output: - # - # ```text - # Create directory; directory birthtime established. - # Directory birthtime: 2026-05-14 23:41:12 +0100 - # Create file; file birthtime established; directory birthtime not updated. - # File birthtime: 2026-05-14 23:41:13 +0100 - # Directory birthtime: 2026-05-14 23:41:12 +0100 - # Write file; neither birthtime updated. - # File birthtime: 2026-05-14 23:41:13 +0100 - # Directory birthtime: 2026-05-14 23:41:12 +0100 + # # A directory and its Pathname. + # dir_path = 'doc/foo' + # dir_pn = Pathname(dir_path) + # # Create directory; directory birthtime established. + # dir_pn.mkdir + # dir_pn.birthtime # => 2026-06-16 17:06:10.779192552 -0500 + # # A file therein and its Pathname. + # file_path = dir_pn.join('t.tmp') + # file_pn = Pathname(file_path) + # # Create file; file birthtime established; directory birthtime not updated. + # file_pn.write('foo') + # dir_pn.birthtime # => 2026-06-16 17:06:10.779192552 -0500 + # file_pn.birthtime # => 2026-06-16 17:07:59.339330622 -0500 + # # Modify file; neither birthtime updated. + # file_pn.write('bar') + # dir_pn.birthtime # => 2026-06-16 17:06:10.779192552 -0500 + # file_pn.birthtime # => 2026-06-16 17:07:59.339330622 -0500 + # # Clean up. + # dir_pn.rmtree # ``` # def birthtime() File.birthtime(@path) end From a31b13ecb6821ce558bd3990c5a8e39767583d43 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Thu, 25 Jun 2026 07:38:51 -0500 Subject: [PATCH 22/22] [DOC] Update for Pathname.ctime (#17370) --- pathname_builtin.rb | 67 ++++++++++++++++----------------------------- 1 file changed, 23 insertions(+), 44 deletions(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 74298406c6a5b4..b12432cfb286fe 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -1169,50 +1169,29 @@ def birthtime() File.birthtime(@path) end # see {File System Timestamps}[rdoc-ref:file/timestamps.md]: # # ```ruby - # # Work in a temporary directory. - # Pathname.mktmpdir do |tmpdirpath| - # # A subdirectory therein, and its Pathname. - # dirpath = File.join(tmpdirpath, 'subdir') - # dir_pn = Pathname(dirpath) - # puts "Create directory; directory ctime established." - # dir_pn.mkdir - # puts " Directory ctime: #{dir_pn.ctime}" - # sleep(1) - # - # # A file in the subdirectory, and its Pathname. - # filepath = File.join(dirpath, 't.txt') - # file_pn = Pathname(filepath) - # puts "Create file; file ctime established; directory ctime updated." - # file_pn.write('foo') - # puts " File ctime: #{file_pn.ctime}" - # puts " Directory ctime: #{dir_pn.ctime}" - # sleep(1) - # puts "Write file; file ctime updated; directory ctime not updated." - # file_pn.write('bar') - # puts " File ctime: #{file_pn.ctime}" - # puts " Directory ctime: #{dir_pn.ctime}" - # sleep(1) - # puts "Read file; neither ctime not updated." - # file_pn.read - # puts " File ctime: #{file_pn.ctime}" - # puts " Directory ctime: #{dir_pn.ctime}" - # end - # ``` - # - # Output: - # - # ```text - # Create directory; directory ctime established. - # Directory ctime: 2026-05-20 14:05:05 -0500 - # Create file; file ctime established; directory ctime updated. - # File ctime: 2026-05-20 14:05:06 -0500 - # Directory ctime: 2026-05-20 14:05:06 -0500 - # Write file; file ctime updated; directory ctime not updated. - # File ctime: 2026-05-20 14:05:07 -0500 - # Directory ctime: 2026-05-20 14:05:06 -0500 - # Read file; neither ctime not updated. - # File ctime: 2026-05-20 14:05:07 -0500 - # Directory ctime: 2026-05-20 14:05:06 -0500 + # # A directory and its Pathname. + # dir_path = 'doc/foo' + # dir_pn = Pathname(dir_path) + # # Create directory; directory ctime established. + # dir_pn.mkdir + # dir_pn.ctime # => 2026-06-16 16:44:15.86720572 -0500 + # # A file therein and its Pathname. + # file_path = dir_pn.join('t.tmp') + # file_pn = Pathname(file_path) + # # Create file; file ctime established; directory ctime updated. + # file_pn.write('foo') + # file_pn.ctime # => 2026-06-16 16:46:00.734974872 -0500 + # dir_pn.ctime # => 2026-06-16 16:46:00.734974872 -0500 + # # Write file; file ctime updated; directory ctime not updated. + # file_pn.write('bar') + # file_pn.ctime # => 2026-06-16 16:49:11.421204188 -0500 + # dir_pn.ctime # => 2026-06-16 16:46:00.734974872 -0500 + # # Read file; neither ctime updated. + # file_pn.read + # file_pn.ctime # => 2026-06-16 16:49:11.421204188 -0500 + # dir_pn.ctime # => 2026-06-16 16:46:00.734974872 -0500 + # # Clean up. + # dir_pn.rmtree # ``` # def ctime() File.ctime(@path) end