Class: Ruby::Rego::Builtins::Codecs::JsonSchema::Formats::MailAddress
- Inherits:
-
Object
- Object
- Ruby::Rego::Builtins::Codecs::JsonSchema::Formats::MailAddress
- Defined in:
- lib/ruby/rego/builtins/codecs/json_schema_email.rb
Overview
A port of Go’s net/mail ParseAddress (src/net/mail/message.go, go1.26.4 — the Go OPA 1.17.1 is
built with), reduced to the boolean gojsonschema’s email/idn-email checkers need: does the
value parse as exactly one RFC 5322 address. There is no Ruby gem matching Go’s specific
acceptance boundary, so this is hand-rolled and differentially verified against opa eval.
Faithful to Go’s quirks: a single-member group g: a@b; parses (parseSingleAddress) but a
multi-member one does not; a display name + angle-addr Foo <a@b> parses; comments are CFWS only
as a trailing run or via skipCFWS, so a LEADING comment fails while a trailing one passes; the
local-part/@ boundary skips plain space but not comments (a (c) @b fails); a domain-literal
[x] requires net.ParseIP(x) (so [127.0.0.1] and [::1] pass but [IPv6:::1] fails);
atext/qtext/dtext allow any rune >= U+0080 (RFC 6532). RFC 2047 encoded-word decoding in phrases
IS replicated to the extent it affects acceptance (Rfc2047.status + consume_phrase): Go’s
consumePhrase runs mime.WordDecoder.Decode on each atom word. A =?charset?enc?text?= whose
payload decodes but whose charset is not utf-8/iso-8859-1/us-ascii makes Decode error (rejecting
as the first word, else truncating); an empty payload decodes to “” and is dropped; a malformed or
undecodable word is kept as raw text. Go 1.26 also buffers a run of consecutive encoded-words and
only flushes them to its words slice on a following raw word, so a comment after a lone
encoded-word run ends the phrase (the CFWS skip is gated on len(words)). Verified differentially
against opa across the cross-product (19000+ encoded-word cases).
The input is required UTF-8-valid by the caller (Formats#email?), so Go’s invalid-UTF-8 error paths can’t fire mid-parse. Parsing walks a char-array index (see #initialize) so it stays linear on multibyte input, not string re-slicing or O(@pos) codepoint indexing.
rubocop:disable Metrics/ClassLength, Naming/PredicateMethod – a faithful recursive-descent port; the consume_/skip_ methods mutate @pos and return success bools (not pure predicates).
Constant Summary collapse
- SPECIALS =
["(", ")", "<", ">", "[", "]", ":", ";", "@", "\\", ",", "\""].freeze
- WSP =
Frozen so the per-character predicates scan a shared array rather than allocating a literal each call (a hot-path GC concern on large inputs).
[" ", "\t"].freeze
- QTEXT_EXCLUDED =
not qtext: backslash, double-quote
["\\", "\""].freeze
- DTEXT_EXCLUDED =
not dtext: brackets, backslash
["[", "]", "\\"].freeze
Class Method Summary collapse
-
.valid?(string) ⇒ bool
Whether
stringis exactly one Go-parseable address.
Instance Method Summary collapse
-
#initialize(string) ⇒ MailAddress
constructor
A new instance of MailAddress.
-
#single_address? ⇒ Boolean
Port of parseSingleAddress: one address, then trailing CFWS, then nothing left; a group must have exactly one member.
Constructor Details
#initialize(string) ⇒ MailAddress
Returns a new instance of MailAddress.
50 51 52 53 54 55 56 57 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_email.rb', line 50 def initialize(string) # Index a char ARRAY, not the String: CRuby's String#[] by codepoint is O(@pos) for a # non-ASCII (multibyte) string, which would make the scan loops O(n²) — a DoS on the RFC 6532 # UTF-8 input that is explicitly in scope. Array#[] is O(1), so the whole parse stays linear. @chars = string.chars @pos = 0 @len = @chars.length end |
Class Method Details
.valid?(string) ⇒ bool
Returns whether string is exactly one Go-parseable address.
46 47 48 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_email.rb', line 46 def self.valid?(string) new(string).single_address? end |
Instance Method Details
#single_address? ⇒ Boolean
Port of parseSingleAddress: one address, then trailing CFWS, then nothing left; a group must have exactly one member. :reek:NilCheck
62 63 64 65 66 67 68 69 |
# File 'lib/ruby/rego/builtins/codecs/json_schema_email.rb', line 62 def single_address? count = parse_address(handle_group: true) return false if count.nil? return false unless skip_cfws return false unless empty? count == 1 end |