416 lines
8.3 KiB
JavaScript
416 lines
8.3 KiB
JavaScript
/*
|
|
* Email address parsing code.
|
|
* rewritten with python's (2.7) email/_parseaddr.py as the starting point
|
|
*/
|
|
|
|
var SPACE = ' ';
|
|
var EMPTYSTRING = '';
|
|
var COMMASPACE = ', ';
|
|
|
|
var quote = function(str)
|
|
{
|
|
// Add quotes around a string.
|
|
return str.replace(/\\\\/g, '\\\\').replace(/"/g, '\\"');
|
|
};
|
|
|
|
/*
|
|
* To understand what this class does, it helps to have a copy of RFC 2822 in
|
|
* front of you.
|
|
*/
|
|
|
|
var Address = function(field)
|
|
{
|
|
/*
|
|
* Initialize a new instance.
|
|
* `field' is an unparsed address header field, containing
|
|
* one or more addresses.
|
|
*/
|
|
|
|
this.specials = '()<>@,:;.\"[]';
|
|
this.pos = 0;
|
|
this.LWS = ' \t';
|
|
this.CR = '\r\n';
|
|
this.FWS = this.LWS + this.CR;
|
|
this.atomends = this.specials + this.LWS + this.CR;
|
|
|
|
// Note that RFC 2822 now specifies `.' as obs-phrase, meaning that it
|
|
// is obsolete syntax. RFC 2822 requires that we recognize obsolete
|
|
// syntax, so allow dots in phrases.
|
|
|
|
this.phraseends = this.atomends.replace(/\./g, '');
|
|
this.field = field || "";
|
|
this.commentlist = [];
|
|
};
|
|
|
|
Address.prototype =
|
|
{
|
|
gotonext: function()
|
|
{
|
|
//Parse up to the start of the next address.
|
|
while(this.pos < this.field.length)
|
|
{
|
|
if((this.LWS + '\n\r').indexOf(this.field[this.pos]) != -1)
|
|
this.pos++;
|
|
|
|
else if(this.field[this.pos] == '(')
|
|
this.commentlist.push(this.getcomment());
|
|
|
|
else
|
|
break;
|
|
}
|
|
},
|
|
|
|
getlist: function()
|
|
{
|
|
// Parse all addresses. Returns a list containing all of the addresses
|
|
var result = [], ad;
|
|
|
|
while(this.pos < this.field.length)
|
|
{
|
|
ad = this.get();
|
|
|
|
if(ad)
|
|
result.push(ad);
|
|
|
|
else
|
|
result.push({label:'', address:''});
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
get: function()
|
|
{
|
|
// Parse the next address
|
|
this.commentlist = [];
|
|
this.gotonext();
|
|
|
|
var oldpos = this.pos, oldcl = this.commentlist, plist = this.getphraselist(), returnlist = [],
|
|
addrspec, fieldlen, routeaddr;
|
|
|
|
this.gotonext();
|
|
|
|
if(this.pos >= this.field.length)
|
|
{
|
|
// Bad email address, no domain
|
|
if(plist.length)
|
|
returnlist = [{label:this.commentlist.join(SPACE), address:plist[0]}];
|
|
}
|
|
|
|
else if('.@'.indexOf(this.field[this.pos]) != -1)
|
|
{
|
|
// email address is just an addrspec
|
|
// this isn't very efficient since we start over
|
|
this.pos = oldpos;
|
|
this.commentlist = oldcl;
|
|
addrspec = this.getspec();
|
|
returnlist = {label:this.commentlist.join(SPACE), address:addrspec};
|
|
}
|
|
|
|
else if(this.field[this.pos] == ':')
|
|
{
|
|
// address is a group
|
|
returnlist = [];
|
|
fieldlen = this.field.length;
|
|
this.pos++;
|
|
|
|
while(this.pos < this.field.length)
|
|
{
|
|
this.gotonext();
|
|
|
|
if(this.pos < fieldlen && this.field[this.pos] == ';')
|
|
{
|
|
this.pos += 1;
|
|
break;
|
|
}
|
|
|
|
returnlist = returnlist.push(this.get());
|
|
}
|
|
}
|
|
|
|
else if(this.field[this.pos] == '<')
|
|
{
|
|
// Address is a prhase then a route addr
|
|
routeaddr = this.getroute();
|
|
|
|
if(this.commentlist.length)
|
|
returnlist = {label:plist.join(SPACE) + ' (' + this.commentlist.join(SPACE) + ')', address:routeaddr};
|
|
|
|
else
|
|
returnlist = {label:plist.join(SPACE), address:routeaddr};
|
|
}
|
|
|
|
else
|
|
{
|
|
if(plist.length)
|
|
returnlist = {label:this.commentlist.join(SPACE), address:plist[0]};
|
|
|
|
else if(this.specials.indexOf(this.field[this.pos]) != -1)
|
|
this.pos++;
|
|
}
|
|
|
|
this.gotonext();
|
|
|
|
if(this.pos < this.field.length && this.field[this.pos] == ',')
|
|
this.pos++;
|
|
|
|
return returnlist;
|
|
},
|
|
|
|
getroute: function()
|
|
{
|
|
// Parse a route address. this method skips all route stuff and returns addrspec
|
|
if(this.field[this.pos] != '<')
|
|
return '';
|
|
|
|
var expectroute = false, adlist = '';
|
|
|
|
this.pos++;
|
|
this.gotonext();
|
|
|
|
while(this.pos < this.field.length)
|
|
{
|
|
if(expectroute)
|
|
{
|
|
this.getdomain();
|
|
expectroute = false;
|
|
}
|
|
else if(this.field[this.pos] == '>')
|
|
{
|
|
this.pos += 1;
|
|
break;
|
|
}
|
|
else if(this.field[this.pos] == '@')
|
|
{
|
|
this.pos += 1;
|
|
expectroute = true;
|
|
}
|
|
else if(this.field[this.pos] == ':')
|
|
{
|
|
this.pos++;
|
|
}
|
|
else
|
|
{
|
|
adlist = this.getspec();
|
|
this.pos++;
|
|
break;
|
|
}
|
|
|
|
this.gotonext();
|
|
}
|
|
|
|
return adlist;
|
|
},
|
|
|
|
getspec: function()
|
|
{
|
|
//parse an RFC 2822 addr-spec
|
|
var aslist = [];
|
|
|
|
this.gotonext();
|
|
|
|
while(this.pos < this.field.length)
|
|
{
|
|
if(this.field[this.pos] == '.')
|
|
{
|
|
aslist.push('.');
|
|
this.pos++;
|
|
}
|
|
|
|
else if(this.field[this.pos] == '"')
|
|
aslist.push('"' + this.getquote() + '"');
|
|
|
|
else if(this.atomends.indexOf(this.field[this.pos]) != -1)
|
|
break;
|
|
|
|
else
|
|
aslist.push(this.getatom());
|
|
|
|
this.gotonext();
|
|
}
|
|
|
|
if(this.pos >= this.field.length || this.field[this.pos] != '@')
|
|
return aslist.join(EMPTYSTRING);
|
|
|
|
aslist.push('@');
|
|
this.pos++;
|
|
this.gotonext();
|
|
|
|
return aslist.join(EMPTYSTRING) + this.getdomain();
|
|
},
|
|
|
|
getdomain: function()
|
|
{
|
|
// get the complete domain name from an address
|
|
var sdlist = [];
|
|
|
|
while(this.pos < this.field.length)
|
|
{
|
|
if(this.LWS.indexOf(this.field[this.pos]) != -1)
|
|
this.pos++;
|
|
|
|
else if(this.field[this.pos] == '(')
|
|
this.commentlist.push(this.getcomment());
|
|
|
|
else if(this.field[this.pos] == '[')
|
|
sdlist.push(this.getdomainliteral());
|
|
|
|
else if(this.field[this.pos] == '.')
|
|
{
|
|
this.pos++;
|
|
sdlist.push('.');
|
|
}
|
|
|
|
else if(this.atomends.indexOf(this.field[this.pos]) != -1)
|
|
break;
|
|
|
|
else
|
|
sdlist.push(this.getatom());
|
|
}
|
|
|
|
return sdlist.join(EMPTYSTRING);
|
|
},
|
|
|
|
getdelimited: function(beginchar, endchars, allowcomments)
|
|
{
|
|
/*
|
|
* Parse a header fragment delimited by special characters.
|
|
*
|
|
* `beginchar' is the start character for the fragment.
|
|
* If self is not looking at an instance of `beginchar' then
|
|
* getdelimited returns the empty string.
|
|
*
|
|
* `endchars' is a sequence of allowable end-delimiting characters.
|
|
* Parsing stops when one of these is encountered.
|
|
*
|
|
* If `allowcomments' is non-zero, embedded RFC 2822 comments are allowed
|
|
* within the parsed fragment.
|
|
*/
|
|
|
|
if(this.field[this.pos] != beginchar)
|
|
return '';
|
|
|
|
allowcomments = (allowcomments === false) ? false : true;
|
|
var slist = [''], quote = false;
|
|
this.pos++;
|
|
|
|
while(this.pos < this.field.length)
|
|
{
|
|
if(quote)
|
|
{
|
|
slist.push(this.field[this.pos]);
|
|
quote = false;
|
|
}
|
|
else if(endchars.indexOf(this.field[this.pos]) != -1)
|
|
{
|
|
this.pos++;
|
|
break;
|
|
}
|
|
else if(allowcomments && this.field[this.pos] == '(')
|
|
{
|
|
slist.push(this.getcomment());
|
|
continue;
|
|
}
|
|
|
|
else if(this.field[this.pos] == '\\')
|
|
quote = true;
|
|
|
|
else
|
|
slist.push(this.field[this.pos]);
|
|
|
|
this.pos++;
|
|
|
|
}
|
|
|
|
return slist.join(EMPTYSTRING);
|
|
},
|
|
|
|
getquote: function()
|
|
{
|
|
// get a quote-delimited fragment from self's field
|
|
return this.getdelimited('"', '"\r', false);
|
|
},
|
|
|
|
getcomment: function()
|
|
{
|
|
// Get a parenthesis-delimited fragment from self's field.
|
|
return this.getdelimited('(', ')\r', true);
|
|
},
|
|
|
|
getdomainliteral: function()
|
|
{
|
|
// parse an rfc 2822 domain literal
|
|
return '[' + this.getdelimited('[', ']\r', false) + ']';
|
|
},
|
|
|
|
getatom: function(atomends)
|
|
{
|
|
/*
|
|
* Parse an RFC 2822 atom.
|
|
*
|
|
* Optional atomends specifies a different set of end token delimiters
|
|
* (the default is to use this.atomends). This is used e.g. in
|
|
* getphraselist() since phrase endings must not include the `.' (which
|
|
* is legal in phrases).
|
|
*/
|
|
|
|
var atomlist = [''];
|
|
|
|
if(atomends === undefined)
|
|
atomends = this.atomends;
|
|
|
|
while(this.pos < this.field.length)
|
|
{
|
|
if(atomends.indexOf(this.field[this.pos]) != -1)
|
|
break;
|
|
|
|
else
|
|
atomlist.push(this.field[this.pos]);
|
|
|
|
this.pos++;
|
|
}
|
|
|
|
return atomlist.join(EMPTYSTRING);
|
|
},
|
|
|
|
getphraselist: function()
|
|
{
|
|
/*
|
|
* Parse a sequence of RFC 2822 phrases.
|
|
*
|
|
* A phrase is a sequence of words, which are in turn either RFC 2822
|
|
* atoms or quoted-strings. Phrases are canonicalized by squeezing all
|
|
* runs of continuous whitespace into one space.
|
|
*/
|
|
|
|
var plist = [];
|
|
|
|
while(this.pos < this.field.length)
|
|
{
|
|
if(this.FWS.indexOf(this.field[this.pos]) != -1)
|
|
this.pos++;
|
|
|
|
else if(this.field[this.pos] == '"')
|
|
plist.push(this.getquote());
|
|
|
|
else if(this.field[this.pos] == '(')
|
|
this.commentlist.push(this.getcomment());
|
|
|
|
else if(this.phraseends.indexOf(this.field[this.pos]) != -1)
|
|
break;
|
|
|
|
else
|
|
plist.push(this.getatom(this.phraseends));
|
|
}
|
|
|
|
return plist;
|
|
}
|
|
};
|
|
|
|
exports.Address = Address;
|
|
exports.parse = function(field)
|
|
{
|
|
var addresses = (new Address(field)).getlist();
|
|
|
|
return addresses.length ? addresses : [];
|
|
};
|