Class: EmailAddress::Local

Inherits:
Object
  • Object
show all
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_REGEX =

Relaxed: same characters, relaxed order

/\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 \! \x23-\x5B \x5D-\x7E \p{L} \p{N}] )+ \" )
      ( \.  ( [\p{L}\p{N}\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\{\|\}\~\(\)]+
| \" ( \\[\" \\] | [\x20 \! \x23-\x5B \x5D-\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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(local, config = {}) ⇒ Local

Returns a new instance of Local.



99
100
101
102
# File 'lib/email_address/local.rb', line 99

def initialize(local, config={})
  self.config   = config.empty? ? EmailAddress::Config.all_settings : config
  self.local    = local
end

Instance Attribute Details

#commentObject

Returns the value of attribute comment.



68
69
70
# File 'lib/email_address/local.rb', line 68

def comment
  @comment
end

#configObject

Returns the value of attribute config.



68
69
70
# File 'lib/email_address/local.rb', line 68

def config
  @config
end

#localObject

Returns the value of attribute local.



68
69
70
# File 'lib/email_address/local.rb', line 68

def local
  @local
end

#mailboxObject

Returns the value of attribute mailbox.



68
69
70
# File 'lib/email_address/local.rb', line 68

def mailbox
  @mailbox
end

#originalObject

Returns the value of attribute original.



68
69
70
# File 'lib/email_address/local.rb', line 68

def original
  @original
end

#syntaxObject

Returns the value of attribute syntax.



69
70
71
# File 'lib/email_address/local.rb', line 69

def syntax
  @syntax
end

#tagObject

Returns the value of attribute tag.



68
69
70
# File 'lib/email_address/local.rb', line 68

def tag
  @tag
end

Class Method Details

.redacted?(local) ⇒ Boolean

Returns true if the value matches the Redacted format

Returns:

  • (Boolean)


167
168
169
# File 'lib/email_address/local.rb', line 167

def self.redacted?(local)
  local =~ REDACTED_REGEX ? true : false
end

Instance Method Details

#ascii?Boolean

True if the the value contains only Latin characters (7-bit ASCII)

Returns:

  • (Boolean)


152
153
154
# File 'lib/email_address/local.rb', line 152

def ascii?
  ! self.unicode?
end

#canonicalObject

Returns a canonical form of the address



205
206
207
208
209
210
211
# File 'lib/email_address/local.rb', line 205

def canonical
  if @config[:mailbox_canonical]
    @config[:mailbox_canonical].call(self.mailbox)
  else
    self.mailbox.downcase
  end
end

#canonical!Object

Sets the part to be the canonical form



239
240
241
# File 'lib/email_address/local.rb', line 239

def canonical!
  self.local = self.canonical
end

#conventionalObject

Returns a conventional form of the address



196
197
198
199
200
201
202
# File 'lib/email_address/local.rb', line 196

def conventional
  if self.tag
    [self.mailbox, self.tag].join(@config[:tag_separator])
  else
    self.mailbox
  end
end

#conventional!Object

Sets the part to be the conventional form



234
235
236
# File 'lib/email_address/local.rb', line 234

def conventional!
  self.local = self.conventional
end

#conventional?Boolean

True if the part matches the conventional format

Returns:

  • (Boolean)


313
314
315
316
317
318
319
320
# File 'lib/email_address/local.rb', line 313

def conventional?
  self.syntax = :invalid
  self.local =~ CONVENTIONAL_MAILBOX_REGEX or return false
  self.valid_size? or return false
  self.valid_encoding? or return false
  self.syntax = :conventional
  true
end

#format(form = @config[:local_format]||:conventional) ⇒ Object

Builds the local string according to configurations



181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/email_address/local.rb', line 181

def format(form=@config[:local_format]||:conventional)
  if @config[:local_format].is_a?(Proc)
    @config[:local_format].call(self)
  elsif form == :conventional
    self.conventional
  elsif form == :canonical
    self.canonical
  elsif form == :relax
    self.relax
  elsif form == :standard
    self.standard
  end
end

#format?Boolean

Returns the format of the address

Returns:

  • (Boolean)


284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/email_address/local.rb', line 284

def format?
  # if :custom
  if self.conventional?
    :conventional
  elsif self.relaxed?
    :relax
  elsif self.redacted?
    :redacted
  elsif self.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.

Returns:

  • (Boolean)


350
351
352
353
354
355
356
357
# File 'lib/email_address/local.rb', line 350

def matches?(*rules)
  rules.flatten.each do |r|
    if r =~ /(.+)@\z/
      return r if File.fnmatch?($1, self.local)
    end
  end
  false
end

#mungeObject

Returns the munged form of the address, like “ma*****”



249
250
251
# File 'lib/email_address/local.rb', line 249

def munge
  self.to_s.sub(/\A(.{1,2}).*/) { |m| $1 + @config[:munge_string] }
end

#parse(raw) ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/email_address/local.rb', line 118

def parse(raw)
  if raw =~ /\A\"(.*)\"\z/ # Quoted
    raw = $1
    raw.gsub!(/\\(.)/, '\1') # Unescape
  elsif @config[:local_fix]
    raw.gsub!(' ','')
    raw.gsub!(',','.')
    raw.gsub!(/([^\p{L}\p{N}]{2,10})/) {|s| s[0] } # Stutter punctuation typo
  end
  raw, comment = self.parse_comment(raw)
  mailbox, tag = self.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



135
136
137
138
139
140
141
142
143
144
# File 'lib/email_address/local.rb', line 135

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



146
147
148
149
# File 'lib/email_address/local.rb', line 146

def parse_tag(raw)
  separator = @config[:tag_separator] ||= '+'
  raw.split(separator, 2)
end

#redacted?Boolean

Returns true if the value matches the Redacted format

Returns:

  • (Boolean)


162
163
164
# File 'lib/email_address/local.rb', line 162

def redacted?
  self.local =~ REDACTED_REGEX ? true : false
end

#relaxObject

Relaxed format: mailbox and tag, no comment, no extended character set



214
215
216
217
218
219
# File 'lib/email_address/local.rb', line 214

def relax
  form = self.mailbox
  form += @config[:tag_separator] + self.tag if self.tag
  form.gsub!(/[ \"\(\),:<>@\[\]\\]/,'')
  form
end

#relax!Object

Dropps unusual parts of Standard form to form a relaxed version.



244
245
246
# File 'lib/email_address/local.rb', line 244

def relax!
  self.local = self.relax
end

#relaxed?Boolean

Relaxed conventional is not so strict about character order.

Returns:

  • (Boolean)


323
324
325
326
327
328
329
330
331
332
333
# File 'lib/email_address/local.rb', line 323

def relaxed?
  self.syntax = :invalid
  self.valid_size? or return false
  self.valid_encoding? or return false
  if self.local =~ RELAXED_MAILBOX_REGEX
    self.syntax = :relaxed
    true
  else
    false
  end
end

#root_nameObject

Mailbox with trailing numbers removed



254
255
256
# File 'lib/email_address/local.rb', line 254

def root_name
  self.mailbox =~ /\A(.+?)\d+\z/ ? $1 : self.mailbox
end

#special?Boolean

Is the address for a common system or business role account?

Returns:

  • (Boolean)


172
173
174
# File 'lib/email_address/local.rb', line 172

def special?
  SPECIAL_MAILBOXES.include?(mailbox)
end

#standardObject

Returns a normalized version of the standard address parts.



222
223
224
225
226
227
228
229
230
231
# File 'lib/email_address/local.rb', line 222

def standard
  form = self.mailbox
  form += @config[:tag_separator] + self.tag if self.tag
  form += "(" + self.comment + ")" if self.comment
  form.gsub!(/([\\\"])/, '\\\1') # Escape \ and "
  if form =~ /[ \"\(\),:<>@\[\\\]]/ # Space and "(),:;<>@[\]
    form = %Q("#{form}")
  end
  form
end

#standard?Boolean

True if the part matches the RFC standard format

Returns:

  • (Boolean)


336
337
338
339
340
341
342
343
344
345
346
# File 'lib/email_address/local.rb', line 336

def standard?
  self.syntax = :invalid
  self.valid_size? or return false
  self.valid_encoding? or return false
  if self.local =~ STANDARD_LOCAL_REGEX
    self.syntax = :standard
    true
  else
    false
  end
end

#to_sObject



176
177
178
# File 'lib/email_address/local.rb', line 176

def to_s
  self.format
end

#unicode?Boolean

True if the the value contains non-Latin Unicde characters

Returns:

  • (Boolean)


157
158
159
# File 'lib/email_address/local.rb', line 157

def unicode?
  self.local =~ /[^\p{InBasicLatin}]/ ? true : false
end

#valid?(format = @config[:local_format]||:conventional) ⇒ Boolean

True if the part is valid according to the configurations

Returns:

  • (Boolean)


263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/email_address/local.rb', line 263

def valid?(format=@config[:local_format]||:conventional)
  if @config[:mailbox_validator].is_a?(Proc)
    @config[:mailbox_validator].call(self.mailbox, self.tag)
  elsif format.is_a?(Proc)
    format.call(self)
  elsif format == :conventional
    self.conventional?
  elsif format == :relaxed
    self.relaxed?
  elsif format == :redacted
    self.redacted?
  elsif format == :standard
    self.standard?
  elsif format == :none
    true
  else
    raise "Unknown format #{format}"
  end
end

#valid_encoding?(enc = @config[:local_encoding]||:ascii) ⇒ Boolean

Returns:

  • (Boolean)


306
307
308
309
310
# File 'lib/email_address/local.rb', line 306

def valid_encoding?(enc=@config[:local_encoding]||:ascii)
  return false if enc == :ascii && self.unicode?
  return false if enc == :unicode && self.ascii?
  true
end

#valid_size?Boolean

Returns:

  • (Boolean)


299
300
301
302
303
304
# File 'lib/email_address/local.rb', line 299

def valid_size?
  return false if @config[:local_size] && !@config[:local_size].include?(self.local.size)
  return false if @config[:mailbox_size] && !@config[:mailbox_size].include?(self.mailbox.size)
  return false if self.local.size > STANDARD_MAX_SIZE
  true
end