Class: EmailAddress::Local
- Inherits:
-
Object
- Object
- EmailAddress::Local
- Defined in:
- lib/email_address/local.rb
Overview
EmailAddress Local part consists of
-
comments
-
mailbox
-
tag
Parsing id provider-dependent, but RFC allows: Chars: A-Z a-z 0-9 . ! # $ % ‘ * + - / = ? ^G _ { | } ~ Quoted: space ( ) , : ; < > @ [ ] Quoted-Backslash-Escaped: \ “ Quote local part or dot-separated sub-parts x.”y“.z RFC-5321 warns ”a host that expects to receive mail SHOULD avoid defining mailboxes
where the Local-part requires (or uses) the Quoted-string form".
(comment)mailbox | mailbox(comment) . can not appear at beginning or end, or appear consecutively 8-bit/UTF-8: allowed but mail-system defined RFC 5321 also warns that “a host that expects to receive mail SHOULD avoid
defining mailboxes where the Local-part requires (or uses) the Quoted-string form".
Postmaster: must always be case-insensitive Case: sensitive, but usually treated as equivalent Local Parts: comment, mailbox tag Length: up to 64 characters Note: gmail does allow “..” against RFC because they are ignored. This will
be fixed by collapsing consecutive punctuation in conventional formats,
and consider them typos.
RFC5322 Rules (Oct 2008):
addr-spec = local-part “@” domain local-part = dot-atom / quoted-string / obs-local-part domain = dot-atom / domain-literal / obs-domain domain-literal = [CFWS] “[” *([FWS] dtext) [FWS] “]” [CFWS] dtext = %d33-90 / ; Printable US-ASCII
%d94-126 / ; characters not including
obs-dtext ; "[", "]", or "\"
atext = ALPHA / DIGIT / ; Printable US-ASCII
"!" / "#" / ; characters not including
"$" / "%" / ; specials. Used for atoms.
"&" / "'" /
"*" / "+" /
"-" / "/" /
"=" / "?" /
"^" / "_" /
"`" / "{" /
"|" / "}" /
"~"
atom = [CFWS] 1*atext [CFWS] dot-atom-text = 1*atext *(“.” 1*atext) dot-atom = [CFWS] dot-atom-text [CFWS] specials = “(” / “)” / ; Special characters that do
"<" / ">" / ; not appear in atext
"[" / "]" /
":" / ";" /
"@" / "\" /
"," / "." /
DQUOTE
qtext = %d33 / ; Printable US-ASCII
%d35-91 / ; characters not including
%d93-126 / ; "\" or the quote character
obs-qtext
qcontent = qtext / quoted-pair quoted-string = [CFWS]
DQUOTE *([FWS] qcontent) [FWS] DQUOTE
[CFWS]
Constant Summary collapse
- BUSINESS_MAILBOXES =
RFC-2142: MAILBOX NAMES FOR COMMON SERVICES, ROLES AND FUNCTIONS
%w[info marketing sales support]
- NETWORK_MAILBOXES =
%w[abuse noc security]
- SERVICE_MAILBOXES =
%w[postmaster hostmaster usenet news webmaster www uucp ftp]
- SYSTEM_MAILBOXES =
Not from RFC-2142
%w[help mailer-daemon root]
- ROLE_MAILBOXES =
Not from RFC-2142
%w[staff office orders billing careers jobs]
- SPECIAL_MAILBOXES =
BUSINESS_MAILBOXES + NETWORK_MAILBOXES + SERVICE_MAILBOXES + SYSTEM_MAILBOXES + ROLE_MAILBOXES
- STANDARD_MAX_SIZE =
64
- CONVENTIONAL_MAILBOX_REGEX =
Conventional : word(word)*
/\A [\p{L}\p{N}_]+ (?: [.\-+'_] [\p{L}\p{N}_]+ )* \z/x
- CONVENTIONAL_MAILBOX_WITHIN =
/[\p{L}\p{N}_]+ (?: [.\-+'_] [\p{L}\p{N}_]+ )*/x
- RELAXED_MAILBOX_WITHIN =
Relaxed: same characters, relaxed order
/[\p{L}\p{N}_]+ (?: [.\-+'_]+ [\p{L}\p{N}_]+ )*/x
- RELAXED_MAILBOX_REGEX =
/\A [\p{L}\p{N}_]+ (?: [.\-+'_]+ [\p{L}\p{N}_]+ )* \z/x
- STANDARD_LOCAL_WITHIN =
RFC5322 Token: token.“token”.token (dot-separated tokens)
Quoted Token can also have: SPACE \" \\ ( ) , : ; < > @ [ \ ] .
/ (?: [\p{L}\p{N}!\#$%&'*+\-\/=?\^_`{|}~()]+ | " (?: \\[" \\] | [\x20-\x21\x23-\x2F\x3A-\x40\x5B\x5D-\x60\x7B-\x7E\p{L}\p{N}] )+ " ) (?: \. (?: [\p{L}\p{N}!\#$%&'*+\-\/=?\^_`{|}~()]+ | " (?: \\[" \\] | [\x20-\x21\x23-\x2F\x3A-\x40\x5B\x5D-\x60\x7B-\x7E\p{L}\p{N}] )+ " ) )* /x
- STANDARD_LOCAL_REGEX =
/\A #{STANDARD_LOCAL_WITHIN} \z/x
- REDACTED_REGEX =
sha1
/\A \{ [0-9a-f]{40} \} \z/x
- CONVENTIONAL_TAG_REGEX =
AZaz09_!‘+-/=
%r{^([\w!'+\-/=.]+)$}i
- RELAXED_TAG_REGEX =
AZaz09_!#$%&‘*+-/=?^`{|}~
%r/^([\w.!\#$%&'*+\-\/=?\^`{|}~]+)$/i
Instance Attribute Summary collapse
-
#comment ⇒ Object
Returns the value of attribute comment.
-
#config ⇒ Object
Returns the value of attribute config.
-
#error_message ⇒ Object
readonly
Returns the value of attribute error_message.
-
#local ⇒ Object
Returns the value of attribute local.
-
#locale ⇒ Object
Returns the value of attribute locale.
-
#mailbox ⇒ Object
Returns the value of attribute mailbox.
-
#original ⇒ Object
Returns the value of attribute original.
-
#syntax ⇒ Object
Returns the value of attribute syntax.
-
#tag ⇒ Object
Returns the value of attribute tag.
Class Method Summary collapse
-
.redacted?(local) ⇒ Boolean
Returns true if the value matches the Redacted format.
Instance Method Summary collapse
-
#ascii? ⇒ Boolean
True if the the value contains only Latin characters (7-bit ASCII).
-
#canonical ⇒ Object
Returns a canonical form of the address.
-
#canonical! ⇒ Object
Sets the part to be the canonical form.
-
#conventional ⇒ Object
Returns a conventional form of the address.
-
#conventional! ⇒ Object
Sets the part to be the conventional form.
-
#conventional? ⇒ Boolean
True if the part matches the conventional format.
- #error ⇒ Object
-
#format(form = @config[:local_format] || :conventional) ⇒ Object
Builds the local string according to configurations.
-
#format? ⇒ Boolean
Returns the format of the address.
-
#initialize(local, config = {}, host = nil, locale = "en") ⇒ Local
constructor
A new instance of Local.
-
#matches?(*rules) ⇒ Boolean
Matches configured formated form against File glob strings given.
-
#munge ⇒ Object
Returns the munged form of the address, like “ma*****”.
- #parse(raw) ⇒ Object
-
#parse_comment(raw) ⇒ Object
“(comment)mailbox” or “mailbox(comment)”, only one comment RFC Doesn’t say what to do if 2 comments occur, so last wins.
- #parse_tag(raw) ⇒ Object
-
#redacted? ⇒ Boolean
Returns true if the value matches the Redacted format.
-
#relax ⇒ Object
Relaxed format: mailbox and tag, no comment, no extended character set.
-
#relax! ⇒ Object
Dropps unusual parts of Standard form to form a relaxed version.
-
#relaxed? ⇒ Boolean
Relaxed conventional is not so strict about character order.
-
#root_name ⇒ Object
Mailbox with trailing numbers removed.
- #set_error(err, reason = nil) ⇒ Object
-
#special? ⇒ Boolean
Is the address for a common system or business role account?.
-
#standard ⇒ Object
Returns a normalized version of the standard address parts.
-
#standard? ⇒ Boolean
True if the part matches the RFC standard format.
- #to_s ⇒ Object
-
#unicode? ⇒ Boolean
True if the the value contains non-Latin Unicde characters.
-
#valid?(format = @config[:local_format] || :conventional) ⇒ Boolean
True if the part is valid according to the configurations.
- #valid_encoding?(enc = @config[:local_encoding] || :ascii) ⇒ Boolean
- #valid_size? ⇒ Boolean
- #valid_size_checks(range) ⇒ Object
Constructor Details
#initialize(local, config = {}, host = nil, locale = "en") ⇒ Local
Returns a new instance of Local.
109 110 111 112 113 114 115 |
# File 'lib/email_address/local.rb', line 109 def initialize(local, config = {}, host = nil, locale = "en") @config = config.is_a?(Hash) ? Config.new(config) : config self.local = local @host = host @locale = locale @error = @error_message = nil end |
Instance Attribute Details
#comment ⇒ Object
Returns the value of attribute comment.
71 72 73 |
# File 'lib/email_address/local.rb', line 71 def comment @comment end |
#config ⇒ Object
Returns the value of attribute config.
71 72 73 |
# File 'lib/email_address/local.rb', line 71 def config @config end |
#error_message ⇒ Object (readonly)
Returns the value of attribute error_message.
397 398 399 |
# File 'lib/email_address/local.rb', line 397 def @error_message end |
#local ⇒ Object
Returns the value of attribute local.
70 71 72 |
# File 'lib/email_address/local.rb', line 70 def local @local end |
#locale ⇒ Object
Returns the value of attribute locale.
72 73 74 |
# File 'lib/email_address/local.rb', line 72 def locale @locale end |
#mailbox ⇒ Object
Returns the value of attribute mailbox.
71 72 73 |
# File 'lib/email_address/local.rb', line 71 def mailbox @mailbox end |
#original ⇒ Object
Returns the value of attribute original.
71 72 73 |
# File 'lib/email_address/local.rb', line 71 def original @original end |
#syntax ⇒ Object
Returns the value of attribute syntax.
72 73 74 |
# File 'lib/email_address/local.rb', line 72 def syntax @syntax end |
#tag ⇒ Object
Returns the value of attribute tag.
71 72 73 |
# File 'lib/email_address/local.rb', line 71 def tag @tag end |
Class Method Details
.redacted?(local) ⇒ Boolean
Returns true if the value matches the Redacted format
180 181 182 |
# File 'lib/email_address/local.rb', line 180 def self.redacted?(local) REDACTED_REGEX.match?(local) end |
Instance Method Details
#ascii? ⇒ Boolean
True if the the value contains only Latin characters (7-bit ASCII)
165 166 167 |
# File 'lib/email_address/local.rb', line 165 def ascii? !unicode? end |
#canonical ⇒ Object
Returns a canonical form of the address
218 219 220 221 222 223 224 |
# File 'lib/email_address/local.rb', line 218 def canonical if @config[:mailbox_canonical] @config[:mailbox_canonical].call(mailbox) else mailbox.downcase end end |
#canonical! ⇒ Object
Sets the part to be the canonical form
251 252 253 |
# File 'lib/email_address/local.rb', line 251 def canonical! self.local = canonical end |
#conventional ⇒ Object
Returns a conventional form of the address
209 210 211 212 213 214 215 |
# File 'lib/email_address/local.rb', line 209 def conventional if tag [mailbox, tag].join(@config[:tag_separator]) else mailbox end end |
#conventional! ⇒ Object
Sets the part to be the conventional form
246 247 248 |
# File 'lib/email_address/local.rb', line 246 def conventional! self.local = conventional end |
#conventional? ⇒ Boolean
True if the part matches the conventional format
334 335 336 337 338 339 340 341 342 343 344 345 346 |
# File 'lib/email_address/local.rb', line 334 def conventional? self.syntax = :invalid if tag return false unless mailbox =~ CONVENTIONAL_MAILBOX_REGEX && tag =~ CONVENTIONAL_TAG_REGEX else return false unless CONVENTIONAL_MAILBOX_REGEX.match?(local) end valid_size? or return false valid_encoding? or return false self.syntax = :conventional true end |
#error ⇒ Object
399 400 401 |
# File 'lib/email_address/local.rb', line 399 def error valid? ? nil : (@error || :local_invalid) end |
#format(form = @config[:local_format] || :conventional) ⇒ Object
Builds the local string according to configurations
194 195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/email_address/local.rb', line 194 def format(form = @config[:local_format] || :conventional) if @config[:local_format].is_a?(Proc) @config[:local_format].call(self) elsif form == :conventional conventional elsif form == :canonical canonical elsif form == :relaxed relax elsif form == :standard standard end end |
#format? ⇒ Boolean
Returns the format of the address
296 297 298 299 300 301 302 303 304 305 306 307 308 309 |
# File 'lib/email_address/local.rb', line 296 def format? # if :custom if conventional? :conventional elsif relaxed? :relax elsif redacted? :redacted elsif standard? :standard else :invalid end end |
#matches?(*rules) ⇒ Boolean
Matches configured formated form against File glob strings given. Rules must end in @ to distinguish themselves from other email part matches.
381 382 383 384 385 386 387 388 |
# File 'lib/email_address/local.rb', line 381 def matches?(*rules) rules.flatten.each do |r| if r =~ /(.+)@\z/ return r if File.fnmatch?($1, local) end end false end |
#munge ⇒ Object
Returns the munged form of the address, like “ma*****”
261 262 263 |
# File 'lib/email_address/local.rb', line 261 def munge to_s.sub(/\A(.{1,2}).*/) { |m| $1 + @config[:munge_string] } end |
#parse(raw) ⇒ Object
131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/email_address/local.rb', line 131 def parse(raw) if raw =~ /\A"(.*)"\z/ # Quoted raw = $1 raw = raw.gsub(/\\(.)/, '\1') # Unescape elsif @config[:local_fix] && @config[:local_format] != :standard raw = raw.delete(" ") raw = raw.tr(",", ".") # raw.gsub!(/([^\p{L}\p{N}]{2,10})/) {|s| s[0] } # Stutter punctuation typo end raw, comment = parse_comment(raw) mailbox, tag = parse_tag(raw) mailbox ||= "" [mailbox, tag, comment] end |
#parse_comment(raw) ⇒ Object
“(comment)mailbox” or “mailbox(comment)”, only one comment RFC Doesn’t say what to do if 2 comments occur, so last wins
148 149 150 151 152 153 154 155 156 157 |
# File 'lib/email_address/local.rb', line 148 def parse_comment(raw) c = nil if raw =~ /\A\((.+?)\)(.+)\z/ c, raw = [$2, $1] end if raw =~ /\A(.+)\((.+?)\)\z/ raw, c = [$1, $2] end [raw, c] end |
#parse_tag(raw) ⇒ Object
159 160 161 162 |
# File 'lib/email_address/local.rb', line 159 def parse_tag(raw) separator = @config[:tag_separator] ||= "+" raw.split(separator, 2) end |
#redacted? ⇒ Boolean
Returns true if the value matches the Redacted format
175 176 177 |
# File 'lib/email_address/local.rb', line 175 def redacted? REDACTED_REGEX.match?(local) end |
#relax ⇒ Object
Relaxed format: mailbox and tag, no comment, no extended character set
227 228 229 230 231 |
# File 'lib/email_address/local.rb', line 227 def relax form = mailbox form += @config[:tag_separator] + tag if tag form.gsub(/[ "(),:<>@\[\]\\]/, "") end |
#relax! ⇒ Object
Dropps unusual parts of Standard form to form a relaxed version.
256 257 258 |
# File 'lib/email_address/local.rb', line 256 def relax! self.local = relax end |
#relaxed? ⇒ Boolean
Relaxed conventional is not so strict about character order.
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 |
# File 'lib/email_address/local.rb', line 349 def relaxed? self.syntax = :invalid valid_size? or return false valid_encoding? or return false if tag return false unless RELAXED_MAILBOX_REGEX.match?(mailbox) && RELAXED_TAG_REGEX.match?(tag) self.syntax = :relaxed true elsif RELAXED_MAILBOX_REGEX.match?(local) self.syntax = :relaxed true else false end end |
#root_name ⇒ Object
Mailbox with trailing numbers removed
266 267 268 |
# File 'lib/email_address/local.rb', line 266 def root_name mailbox =~ /\A(.+?)\d+\z/ ? $1 : mailbox end |
#set_error(err, reason = nil) ⇒ Object
390 391 392 393 394 395 |
# File 'lib/email_address/local.rb', line 390 def set_error(err, reason = nil) @error = err @reason = reason @error_message = Config.(err, locale) false end |
#special? ⇒ Boolean
Is the address for a common system or business role account?
185 186 187 |
# File 'lib/email_address/local.rb', line 185 def special? SPECIAL_MAILBOXES.include?(mailbox) end |
#standard ⇒ Object
Returns a normalized version of the standard address parts.
234 235 236 237 238 239 240 241 242 243 |
# File 'lib/email_address/local.rb', line 234 def standard form = mailbox form += @config[:tag_separator] + tag if tag form += "(" + comment + ")" if comment form = form.gsub(/([\\"])/, '\\\1') # Escape \ and " if /[ "(),:<>@\[\\\]]/.match?(form) # Space and "(),:;<>@[\] form = %("#{form}") end form end |
#standard? ⇒ Boolean
True if the part matches the RFC standard format
367 368 369 370 371 372 373 374 375 376 377 |
# File 'lib/email_address/local.rb', line 367 def standard? self.syntax = :invalid valid_size? or return false valid_encoding? or return false if STANDARD_LOCAL_REGEX.match?(local) self.syntax = :standard true else false end end |
#to_s ⇒ Object
189 190 191 |
# File 'lib/email_address/local.rb', line 189 def to_s self.format end |
#unicode? ⇒ Boolean
True if the the value contains non-Latin Unicde characters
170 171 172 |
# File 'lib/email_address/local.rb', line 170 def unicode? /[^\p{InBasicLatin}]/.match?(local) end |
#valid?(format = @config[:local_format] || :conventional) ⇒ Boolean
True if the part is valid according to the configurations
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
# File 'lib/email_address/local.rb', line 275 def valid?(format = @config[:local_format] || :conventional) if @config[:mailbox_validator].is_a?(Proc) @config[:mailbox_validator].call(mailbox, tag) elsif format.is_a?(Proc) format.call(self) elsif format == :conventional conventional? elsif format == :relaxed relaxed? elsif format == :redacted redacted? elsif format == :standard standard? elsif format == :none true else raise "Unknown format #{format}" end end |
#valid_encoding?(enc = @config[:local_encoding] || :ascii) ⇒ Boolean
328 329 330 331 |
# File 'lib/email_address/local.rb', line 328 def valid_encoding?(enc = @config[:local_encoding] || :ascii) return false if enc == :ascii && unicode? true end |
#valid_size? ⇒ Boolean
311 312 313 314 315 316 317 318 319 320 |
# File 'lib/email_address/local.rb', line 311 def valid_size? return set_error(:local_size_long) if local.size > STANDARD_MAX_SIZE if @host&.hosted_service? return false if @config[:local_private_size] && !valid_size_checks(@config[:local_private_size]) elsif @config[:local_size] && !valid_size_checks(@config[:local_size]) return false end return false if @config[:mailbox_size] && !valid_size_checks(@config[:mailbox_size]) true end |
#valid_size_checks(range) ⇒ Object
322 323 324 325 326 |
# File 'lib/email_address/local.rb', line 322 def valid_size_checks(range) return set_error(:local_size_short) if mailbox.size < range.first return set_error(:local_size_long) if mailbox.size > range.last true end |