Skip to content

Commit 922f4a1

Browse files
authored
🔀 Merge pull request #100 from ruby/stringprep-move
🚚 Move the StringPrep module out of SASL
2 parents 7e3a373 + 05e9741 commit 922f4a1

File tree

13 files changed

+220
-185
lines changed

13 files changed

+220
-185
lines changed

benchmarks/stringprep.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ prelude: |
2424
$LOAD_PATH.unshift "./lib"
2525
require "net/imap"
2626
def net_imap_saslprep(string)
27-
Net::IMAP::SASL::SASLprep.saslprep string, exception: false
27+
Net::IMAP::StringPrep::SASLprep.saslprep string, exception: false
2828
end
2929
3030
def libidn_saslprep(string)

lib/net/imap.rb

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,9 @@ module Net
705705
class IMAP < Protocol
706706
VERSION = "0.3.4"
707707

708+
autoload :SASL, File.expand_path("imap/sasl", __dir__)
709+
autoload :StringPrep, File.expand_path("imap/stringprep", __dir__)
710+
708711
include MonitorMixin
709712
if defined?(OpenSSL::SSL)
710713
include OpenSSL
@@ -2513,6 +2516,16 @@ def start_tls_session(params = {})
25132516
end
25142517
end
25152518

2519+
#--
2520+
# We could get the saslprep method by extending the SASLprep module
2521+
# directly. It's done indirectly, so SASLprep can be lazily autoloaded,
2522+
# because most users won't need it.
2523+
#++
2524+
# Delegates to Net::IMAP::StringPrep::SASLprep#saslprep.
2525+
def self.saslprep(string, **opts)
2526+
Net::IMAP::StringPrep::SASLprep.saslprep(string, **opts)
2527+
end
2528+
25162529
end
25172530
end
25182531

@@ -2523,4 +2536,3 @@ def start_tls_session(params = {})
25232536
require_relative "imap/response_data"
25242537
require_relative "imap/response_parser"
25252538
require_relative "imap/authenticators"
2526-
require_relative "imap/sasl"

lib/net/imap/sasl.rb

Lines changed: 9 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -25,54 +25,21 @@ module SASL
2525

2626
# autoloading to avoid loading all of the regexps when they aren't used.
2727

28-
autoload :StringPrep, File.expand_path("sasl/stringprep", __dir__)
29-
autoload :SASLprep, File.expand_path("#{__dir__}/sasl/saslprep", __dir__)
28+
sasl_stringprep_rb = File.expand_path("sasl/stringprep", __dir__)
29+
autoload :StringPrep, sasl_stringprep_rb
30+
autoload :SASLprep, sasl_stringprep_rb
31+
autoload :StringPrepError, sasl_stringprep_rb
32+
autoload :ProhibitedCodepoint, sasl_stringprep_rb
33+
autoload :BidiStringError, sasl_stringprep_rb
3034

31-
# ArgumentError raised when +string+ is invalid for the stringprep
32-
# +profile+.
33-
class StringPrepError < ArgumentError
34-
attr_reader :string, :profile
35+
module_function
3536

36-
def initialize(*args, string: nil, profile: nil)
37-
@string = -string.to_str unless string.nil?
38-
@profile = -profile.to_str unless profile.nil?
39-
super(*args)
40-
end
41-
end
42-
43-
# StringPrepError raised when +string+ contains a codepoint prohibited by
44-
# +table+.
45-
class ProhibitedCodepoint < StringPrepError
46-
attr_reader :table
47-
48-
def initialize(table, *args, **kwargs)
49-
@table = -table.to_str
50-
details = (title = StringPrep::TABLE_TITLES[table]) ?
51-
"%s [%s]" % [title, table] : table
52-
message = "String contains a prohibited codepoint: %s" % [details]
53-
super(message, *args, **kwargs)
54-
end
55-
end
56-
57-
# StringPrepError raised when +string+ contains bidirectional characters
58-
# which violate the StringPrep requirements.
59-
class BidiStringError < StringPrepError
60-
end
61-
62-
#--
63-
# We could just extend SASLprep module directly. It's done this way so
64-
# SASLprep can be lazily autoloaded. Most users won't need it.
65-
#++
66-
extend self
67-
68-
# See SASLprep#saslprep.
37+
# See Net::IMAP::StringPrep::SASLprep#saslprep.
6938
def saslprep(string, **opts)
70-
SASLprep.saslprep(string, **opts)
39+
Net::IMAP::StringPrep::SASLprep.saslprep(string, **opts)
7140
end
7241

7342
end
7443
end
7544

7645
end
77-
78-
Net::IMAP.extend Net::IMAP::SASL

lib/net/imap/sasl/saslprep.rb

Lines changed: 0 additions & 55 deletions
This file was deleted.

lib/net/imap/sasl/stringprep.rb

Lines changed: 6 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,12 @@
11
# frozen_string_literal: true
22

3-
require_relative "stringprep_tables"
4-
53
module Net::IMAP::SASL
64

7-
# Regexps and utility methods for implementing stringprep profiles. The
8-
# \StringPrep algorithm is defined by
9-
# {RFC-3454}[https://www.rfc-editor.org/rfc/rfc3454.html]. Each
10-
# codepoint table defined in the RFC-3454 appendices is matched by a Regexp
11-
# defined in this module.
12-
#--
13-
# TODO: generic StringPrep mapping (not needed for SASLprep implementation)
14-
#++
15-
module StringPrep
16-
17-
# Returns a Regexp matching the given +table+ name.
18-
def self.[](table)
19-
TABLE_REGEXPS.fetch(table)
20-
end
21-
22-
module_function
23-
24-
# Checks +string+ for any codepoint in +tables+. Raises a
25-
# ProhibitedCodepoint describing the first matching table.
26-
#
27-
# Also checks bidirectional characters, when <tt>bidi: true</tt>, which may
28-
# raise a BidiStringError.
29-
#
30-
# +profile+ is an optional string which will be added to any exception that
31-
# is raised (it does not affect behavior).
32-
def check_prohibited!(string, *tables, bidi: false, profile: nil)
33-
tables = TABLE_TITLES.keys.grep(/^C/) if tables.empty?
34-
tables |= %w[C.8] if bidi
35-
table = tables.find {|t| TABLE_REGEXPS[t].match?(string) }
36-
raise ProhibitedCodepoint.new(
37-
table, string: string, profile: nil
38-
) if table
39-
check_bidi!(string, profile: profile) if bidi
40-
end
41-
42-
# Checks that +string+ obeys all of the "Bidirectional Characters"
43-
# requirements in RFC-3454, §6:
44-
#
45-
# * The characters in \StringPrep\[\"C.8\"] MUST be prohibited
46-
# * If a string contains any RandALCat character, the string MUST NOT
47-
# contain any LCat character.
48-
# * If a string contains any RandALCat character, a RandALCat
49-
# character MUST be the first character of the string, and a
50-
# RandALCat character MUST be the last character of the string.
51-
#
52-
# This is usually combined with #check_prohibited!, so table "C.8" is only
53-
# checked when <tt>c_8: true</tt>.
54-
#
55-
# Raises either ProhibitedCodepoint or BidiStringError unless all
56-
# requirements are met. +profile+ is an optional string which will be
57-
# added to any exception that is raised (it does not affect behavior).
58-
def check_bidi!(string, c_8: false, profile: nil)
59-
check_prohibited!(string, "C.8", profile: profile) if c_8
60-
if BIDI_FAILS_REQ2.match?(string)
61-
raise BidiStringError.new(
62-
BIDI_DESC_REQ2, string: string, profile: profile,
63-
)
64-
elsif BIDI_FAILS_REQ3.match?(string)
65-
raise BidiStringError.new(
66-
BIDI_DESC_REQ3, string: string, profile: profile,
67-
)
68-
end
69-
end
5+
# Alias for Net::IMAP::StringPrep::SASLPrep.
6+
SASLprep = Net::IMAP::StringPrep::SASLprep
7+
StringPrep = Net::IMAP::StringPrep # :nodoc:
8+
BidiStringError = Net::IMAP::StringPrep::BidiStringError # :nodoc:
9+
ProhibitedCodepoint = Net::IMAP::StringPrep::ProhibitedCodepoint # :nodoc:
10+
StringPrepError = Net::IMAP::StringPrep::StringPrepError # :nodoc:
7011

71-
end
7212
end

lib/net/imap/stringprep.rb

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# frozen_string_literal: true
2+
3+
module Net
4+
class IMAP < Protocol
5+
6+
# Regexps and utility methods for implementing stringprep profiles. The
7+
# \StringPrep algorithm is defined by
8+
# {RFC-3454}[https://www.rfc-editor.org/rfc/rfc3454.html]. Each
9+
# codepoint table defined in the RFC-3454 appendices is matched by a Regexp
10+
# defined in this module.
11+
module StringPrep
12+
autoload :SASLprep, File.expand_path("stringprep/saslprep", __dir__)
13+
autoload :Tables, File.expand_path("stringprep/tables", __dir__)
14+
15+
# ArgumentError raised when +string+ is invalid for the stringprep
16+
# +profile+.
17+
class StringPrepError < ArgumentError
18+
attr_reader :string, :profile
19+
20+
def initialize(*args, string: nil, profile: nil)
21+
@string = -string.to_str unless string.nil?
22+
@profile = -profile.to_str unless profile.nil?
23+
super(*args)
24+
end
25+
end
26+
27+
# StringPrepError raised when +string+ contains a codepoint prohibited by
28+
# +table+.
29+
class ProhibitedCodepoint < StringPrepError
30+
attr_reader :table
31+
32+
def initialize(table, *args, **kwargs)
33+
@table = table
34+
details = (title = Tables::TITLES[table]) ?
35+
"%s [%s]" % [title, table] : table
36+
message = "String contains a prohibited codepoint: %s" % [details]
37+
super(message, *args, **kwargs)
38+
end
39+
end
40+
41+
# StringPrepError raised when +string+ contains bidirectional characters
42+
# which violate the StringPrep requirements.
43+
class BidiStringError < StringPrepError
44+
end
45+
46+
# Returns a Regexp matching the given +table+ name.
47+
def self.[](table)
48+
Tables::REGEXPS.fetch(table)
49+
end
50+
51+
module_function
52+
53+
# Checks +string+ for any codepoint in +tables+. Raises a
54+
# ProhibitedCodepoint describing the first matching table.
55+
#
56+
# Also checks bidirectional characters, when <tt>bidi: true</tt>, which may
57+
# raise a BidiStringError.
58+
#
59+
# +profile+ is an optional string which will be added to any exception that
60+
# is raised (it does not affect behavior).
61+
def check_prohibited!(string, *tables, bidi: false, profile: nil)
62+
tables = TABLE_TITLES.keys.grep(/^C/) if tables.empty?
63+
tables |= %w[C.8] if bidi
64+
table = tables.find {|t| Tables::REGEXPS[t].match?(string) }
65+
raise ProhibitedCodepoint.new(
66+
table, string: string, profile: nil
67+
) if table
68+
check_bidi!(string, profile: profile) if bidi
69+
end
70+
71+
# Checks that +string+ obeys all of the "Bidirectional Characters"
72+
# requirements in RFC-3454, §6:
73+
#
74+
# * The characters in \StringPrep\[\"C.8\"] MUST be prohibited
75+
# * If a string contains any RandALCat character, the string MUST NOT
76+
# contain any LCat character.
77+
# * If a string contains any RandALCat character, a RandALCat
78+
# character MUST be the first character of the string, and a
79+
# RandALCat character MUST be the last character of the string.
80+
#
81+
# This is usually combined with #check_prohibited!, so table "C.8" is only
82+
# checked when <tt>c_8: true</tt>.
83+
#
84+
# Raises either ProhibitedCodepoint or BidiStringError unless all
85+
# requirements are met. +profile+ is an optional string which will be
86+
# added to any exception that is raised (it does not affect behavior).
87+
def check_bidi!(string, c_8: false, profile: nil)
88+
check_prohibited!(string, "C.8", profile: profile) if c_8
89+
if Tables::BIDI_FAILS_REQ2.match?(string)
90+
raise BidiStringError.new(
91+
Tables::BIDI_DESC_REQ2, string: string, profile: profile,
92+
)
93+
elsif Tables::BIDI_FAILS_REQ3.match?(string)
94+
raise BidiStringError.new(
95+
Tables::BIDI_DESC_REQ3, string: string, profile: profile,
96+
)
97+
end
98+
end
99+
100+
end
101+
end
102+
end

0 commit comments

Comments
 (0)