|
1
|
|
|
|
|
2
|
|
|
module MailAddress |
|
3
|
|
|
|
|
4
|
|
|
def self.parse(*addresses) |
|
5
|
|
|
lines = addresses.grep(String) |
|
6
|
|
|
line = lines.join('').strip |
|
7
|
|
|
|
|
8
|
|
|
# empty or <> or < or > |
|
9
|
|
|
if line.empty? || line.match(/\A[<>;, ]+\z/) |
|
10
|
|
|
return [ MailAddress::Address.new(line, nil, line) ] |
|
11
|
|
|
end |
|
12
|
|
|
|
|
13
|
|
|
# undisclosed-recipient |
|
14
|
|
|
if line.match(/undisclosed[ \-]recipients?: ?;?/i) |
|
15
|
|
|
return [ MailAddress::Address.new(line, nil, line) ] |
|
16
|
|
|
end |
|
17
|
|
|
|
|
18
|
|
|
phrase, address, objs = [], [], [] |
|
19
|
|
|
original = '' |
|
20
|
|
|
depth, idx, end_paren_idx = 0, 0, 0 |
|
21
|
|
|
|
|
22
|
|
|
tokens = _tokenize lines |
|
23
|
|
|
len = tokens.length |
|
24
|
|
|
_next = _find_next idx, tokens, len |
|
25
|
|
|
|
|
26
|
|
|
for idx in 0 ... len do |
|
27
|
|
|
|
|
28
|
|
|
token = tokens[idx] |
|
29
|
|
|
substr = token[0, 1] |
|
30
|
|
|
original << token |
|
31
|
|
|
|
|
32
|
|
|
if (end_paren_idx > 0 && end_paren_idx >= idx) |
|
33
|
|
|
next |
|
34
|
|
|
end |
|
35
|
|
|
|
|
36
|
|
|
if (substr == '(' && !address.empty?) then |
|
37
|
|
|
end_paren_idx = _find_next_paren(idx, tokens, len) |
|
38
|
|
|
if end_paren_idx == -1 |
|
39
|
|
|
# end paren doesn't exist |
|
40
|
|
|
# but nothing to do |
|
41
|
|
|
end |
|
42
|
|
|
rem = tokens[idx .. end_paren_idx] |
|
43
|
|
|
phrase.push(rem.join('')) |
|
44
|
|
|
elsif (substr == '<') then |
|
45
|
|
|
depth += 1 |
|
46
|
|
|
elsif (substr == '>') then |
|
47
|
|
|
depth -= 1 if depth > 0 |
|
48
|
|
|
elsif (substr == ',' || substr == ';') then |
|
49
|
|
|
original.sub!(/[,;]\s*\z/, '') |
|
50
|
|
|
|
|
51
|
|
|
if depth > 0 |
|
52
|
|
|
# raise "Unmatched '<>' in line" |
|
53
|
|
|
o = MailAddress::Address.new(original, nil, original) |
|
54
|
|
|
phrase.clear; address.clear |
|
55
|
|
|
else |
|
56
|
|
|
o = _complete(phrase, address, original) |
|
57
|
|
|
end |
|
58
|
|
|
|
|
59
|
|
|
objs.push(o) if o |
|
60
|
|
|
depth = 0 |
|
61
|
|
|
end_paren_idx = 0 |
|
62
|
|
|
original = '' |
|
63
|
|
|
_next = _find_next idx+1, tokens, len |
|
64
|
|
|
elsif (depth > 0) then |
|
65
|
|
|
token.strip! |
|
66
|
|
|
address.push(token) |
|
67
|
|
|
elsif (_next == '<') then |
|
68
|
|
|
phrase.push(token) |
|
69
|
|
|
elsif ( token.match(/^[.\@:;]/) || address.empty? || address[-1].match(/^[.\@:;]/) ) then |
|
70
|
|
|
token.strip! |
|
71
|
|
|
address.push(token) |
|
72
|
|
|
else |
|
73
|
|
|
phrase.push(token) |
|
74
|
|
|
end |
|
75
|
|
|
end |
|
76
|
|
|
objs |
|
77
|
|
|
end |
|
78
|
|
|
|
|
79
|
|
|
private |
|
80
|
|
|
|
|
81
|
|
|
def self._tokenize(addresses) |
|
82
|
|
|
line = addresses.join(',') # $_ |
|
83
|
|
|
words = [] |
|
84
|
|
|
|
|
85
|
|
|
line.gsub!(/\\/, '') |
|
86
|
|
|
line.sub!(/\A\s+/, '') |
|
87
|
|
|
line.gsub!(/[\r\n]+/,' ') |
|
88
|
|
|
|
|
89
|
|
|
while (line != '') |
|
90
|
|
|
tmp = nil |
|
91
|
|
|
if ( |
|
92
|
|
|
line.match(/"[^"]+"/) && line.sub!(/\A(\\?"(?:[^"\\]+|\\.)*")(\s*)/, '') || # "..." |
|
93
|
|
|
line.sub!(/\A([^\s()<>\@,;:\\".\[\]]+)(\s*)/, '') || |
|
94
|
|
|
line.sub!(/\A([()<>\@,;:\\".\[\]])(\s*)/, '') |
|
95
|
|
|
) |
|
96
|
|
|
words.push("#{$1}#{$2}") |
|
97
|
|
|
next |
|
98
|
|
|
end |
|
99
|
|
|
raise "Unrecognized line: #{line}" |
|
100
|
|
|
end |
|
101
|
|
|
|
|
102
|
|
|
words.push(',') |
|
103
|
|
|
words |
|
104
|
|
|
end |
|
105
|
|
|
|
|
106
|
|
|
def self._find_next(idx, tokens, len) |
|
107
|
|
|
while (idx < len) |
|
108
|
|
|
c = tokens[idx].strip |
|
109
|
|
|
return c if c == ',' || c == ';' || c == '<' |
|
110
|
|
|
idx += 1 |
|
111
|
|
|
end |
|
112
|
|
|
"" |
|
113
|
|
|
end |
|
114
|
|
|
|
|
115
|
|
|
# find next ending parenthesis |
|
116
|
|
|
def self._find_next_paren(idx, tokens, len) |
|
117
|
|
|
while (idx < len) |
|
118
|
|
|
c = tokens[idx].strip |
|
119
|
|
|
return idx if c.include?(')') |
|
120
|
|
|
idx += 1 |
|
121
|
|
|
end |
|
122
|
|
|
-1 |
|
123
|
|
|
end |
|
124
|
|
|
|
|
125
|
|
|
def self._complete (phrase, address, original) |
|
126
|
|
|
phrase.length > 0 || address.length > 0 or return nil |
|
127
|
|
|
|
|
128
|
|
|
name = phrase.join('').strip |
|
129
|
|
|
|
|
130
|
|
|
name = self.collapse_whitespace(name) |
|
131
|
|
|
name = name[1 .. -2] if name.start_with?('\'') && name.end_with?('\'') |
|
132
|
|
|
name = name[1 .. -2] if name.start_with?('"') && name.end_with?('"') |
|
133
|
|
|
|
|
134
|
|
|
# Replace escaped quotes and slashes. |
|
135
|
|
|
name = name.gsub(ESCAPED_DOUBLE_QUOTES_, '"') |
|
136
|
|
|
name = name.gsub(ESCAPED_BACKSLASHES_, '\\') |
|
137
|
|
|
|
|
138
|
|
|
new_address = MailAddress::Address.new(name, address.join(''), original) |
|
139
|
|
|
phrase.clear; address.clear |
|
140
|
|
|
new_address |
|
141
|
|
|
end |
|
142
|
|
|
|
|
143
|
|
|
end |
|
144
|
|
|
|