Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Escape reserved characters in scheme name #148

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 38 additions & 8 deletions lib/uri/common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,40 @@ def make_components_hash(klass, array_hash)
end

module Schemes
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 characater as scheme - #{name}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
raise ArgumentError, "invalid characater as scheme - #{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

Expand All @@ -100,7 +134,7 @@ module Schemes
# Note that after calling String#upcase on +scheme+, it must be a valid
# constant name.
def self.register_scheme(scheme, klass)
Schemes.const_set(scheme.to_s.upcase, klass)
Schemes.register(scheme, klass)
end

# Returns a hash of the defined schemes:
Expand All @@ -118,9 +152,7 @@ def self.register_scheme(scheme, klass)
#
# Related: URI.register_scheme.
def self.scheme_list
Schemes.constants.map { |name|
[name.to_s.upcase, Schemes.const_get(name)]
}.to_h
Schemes.list
end

INITIAL_SCHEMES = scheme_list
Expand All @@ -144,12 +176,10 @@ def self.scheme_list
# # => #<URI::HTTP foo://[email protected]:123/forum/questions/?tag=networking&order=newest#top>
#
def self.for(scheme, *arguments, default: Generic)
const_name = scheme.to_s.upcase
const_name = Schemes.escape(scheme)

uri_class = INITIAL_SCHEMES[const_name]
uri_class ||= if /\A[A-Z]\w*\z/.match?(const_name) && Schemes.const_defined?(const_name, false)
Schemes.const_get(const_name, false)
end
uri_class ||= Schemes.find(const_name)
uri_class ||= default

return uri_class.new(scheme, *arguments)
Expand Down
21 changes: 11 additions & 10 deletions test/uri/test_common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,17 +113,18 @@ def test_register_scheme_lowercase

def test_register_scheme_with_symbols
# Valid schemes from https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml
some_uri_class = Class.new(URI::Generic)
assert_raise(NameError) { URI.register_scheme 'ms-search', some_uri_class }
assert_raise(NameError) { URI.register_scheme 'microsoft.windows.camera', some_uri_class }
assert_raise(NameError) { URI.register_scheme 'coaps+ws', some_uri_class }
list = []
%w[ms-search microsoft.windows.camera coaps+ws].each {|name|
list << [name, URI.register_scheme(name, Class.new(URI::Generic))]
}

ms_search_class = Class.new(URI::Generic)
URI.register_scheme 'MS_SEARCH', ms_search_class
begin
assert_equal URI::Generic, URI.parse('ms-search://localhost').class
ensure
URI.const_get(:Schemes).send(:remove_const, :MS_SEARCH)
list.each do |scheme, uri_class|
assert_equal uri_class, URI.parse("#{scheme}://localhost").class
end
ensure
schemes = URI.const_get(:Schemes)
list.each do |scheme, |
schemes.send(:remove_const, schemes.escape(scheme))
end
end

Expand Down
Loading