1/* globals define, module, jQuery */ 2 3/* 4 * Mailcheck https://github.com/mailcheck/mailcheck 5 * Author 6 * Derrick Ko (@derrickko) 7 * 8 * Released under the MIT License. 9 * 10 * v 1.1.2 11 */ 12 13var Mailcheck = { 14 domainThreshold: 2, 15 secondLevelThreshold: 2, 16 topLevelThreshold: 2, 17 18 defaultDomains: ['msn.com', 'bellsouth.net', 19 'telus.net', 'comcast.net', 'optusnet.com.au', 20 'earthlink.net', 'qq.com', 'sky.com', 'icloud.com', 21 'mac.com', 'sympatico.ca', 'googlemail.com', 22 'att.net', 'xtra.co.nz', 'web.de', 23 'cox.net', 'gmail.com', 'ymail.com', 24 'aim.com', 'rogers.com', 'verizon.net', 25 'rocketmail.com', 'google.com', 'optonline.net', 26 'sbcglobal.net', 'aol.com', 'me.com', 'btinternet.com', 27 'charter.net', 'shaw.ca'], 28 29 defaultSecondLevelDomains: ["yahoo", "hotmail", "mail", "live", "outlook", "gmx"], 30 31 defaultTopLevelDomains: ["com", "com.au", "com.tw", "ca", "co.nz", "co.uk", "de", 32 "fr", "it", "ru", "net", "org", "edu", "gov", "jp", "nl", "kr", "se", "eu", 33 "ie", "co.il", "us", "at", "be", "dk", "hk", "es", "gr", "ch", "no", "cz", 34 "in", "net", "net.au", "info", "biz", "mil", "co.jp", "sg", "hu", "uk"], 35 36 run: function(opts) { 37 opts.domains = opts.domains || Mailcheck.defaultDomains; 38 opts.secondLevelDomains = opts.secondLevelDomains || Mailcheck.defaultSecondLevelDomains; 39 opts.topLevelDomains = opts.topLevelDomains || Mailcheck.defaultTopLevelDomains; 40 opts.distanceFunction = opts.distanceFunction || Mailcheck.sift4Distance; 41 42 var defaultCallback = function(result){ return result; }; 43 var suggestedCallback = opts.suggested || defaultCallback; 44 var emptyCallback = opts.empty || defaultCallback; 45 46 var result = Mailcheck.suggest(Mailcheck.encodeEmail(opts.email), opts.domains, opts.secondLevelDomains, opts.topLevelDomains, opts.distanceFunction); 47 48 return result ? suggestedCallback(result) : emptyCallback(); 49 }, 50 51 suggest: function(email, domains, secondLevelDomains, topLevelDomains, distanceFunction) { 52 email = email.toLowerCase(); 53 54 var emailParts = this.splitEmail(email); 55 56 if (secondLevelDomains && topLevelDomains) { 57 // If the email is a valid 2nd-level + top-level, do not suggest anything. 58 if (secondLevelDomains.indexOf(emailParts.secondLevelDomain) !== -1 && topLevelDomains.indexOf(emailParts.topLevelDomain) !== -1) { 59 return false; 60 } 61 } 62 63 var closestDomain = this.findClosestDomain(emailParts.domain, domains, distanceFunction, this.domainThreshold); 64 65 if (closestDomain) { 66 if (closestDomain == emailParts.domain) { 67 // The email address exactly matches one of the supplied domains; do not return a suggestion. 68 return false; 69 } else { 70 // The email address closely matches one of the supplied domains; return a suggestion 71 return { address: emailParts.address, domain: closestDomain, full: emailParts.address + "@" + closestDomain }; 72 } 73 } 74 75 // The email address does not closely match one of the supplied domains 76 var closestSecondLevelDomain = this.findClosestDomain(emailParts.secondLevelDomain, secondLevelDomains, distanceFunction, this.secondLevelThreshold); 77 var closestTopLevelDomain = this.findClosestDomain(emailParts.topLevelDomain, topLevelDomains, distanceFunction, this.topLevelThreshold); 78 79 if (emailParts.domain) { 80 closestDomain = emailParts.domain; 81 var rtrn = false; 82 83 if(closestSecondLevelDomain && closestSecondLevelDomain != emailParts.secondLevelDomain) { 84 // The email address may have a mispelled second-level domain; return a suggestion 85 closestDomain = closestDomain.replace(emailParts.secondLevelDomain, closestSecondLevelDomain); 86 rtrn = true; 87 } 88 89 if(closestTopLevelDomain && closestTopLevelDomain != emailParts.topLevelDomain && emailParts.secondLevelDomain !== '') { 90 // The email address may have a mispelled top-level domain; return a suggestion 91 closestDomain = closestDomain.replace(new RegExp(emailParts.topLevelDomain + "$"), closestTopLevelDomain); 92 rtrn = true; 93 } 94 95 if (rtrn) { 96 return { address: emailParts.address, domain: closestDomain, full: emailParts.address + "@" + closestDomain }; 97 } 98 } 99 100 /* The email address exactly matches one of the supplied domains, does not closely 101 * match any domain and does not appear to simply have a mispelled top-level domain,
102 * or is an invalid email address; do not return a suggestion. 103 */ 104 return false; 105 }, 106 107 findClosestDomain: function(domain, domains, distanceFunction, threshold) { 108 threshold = threshold || this.topLevelThreshold; 109 var dist; 110 var minDist = Infinity; 111 var closestDomain = null; 112 113 if (!domain || !domains) { 114 return false; 115 } 116 if(!distanceFunction) { 117 distanceFunction = this.sift4Distance; 118 } 119 120 for (var i = 0; i < domains.length; i++) { 121 if (domain === domains[i]) { 122 return domain; 123 } 124 dist = distanceFunction(domain, domains[i]); 125 if (dist < minDist) { 126 minDist = dist; 127 closestDomain = domains[i]; 128 } 129 } 130 131 if (minDist <= threshold && closestDomain !== null) { 132 return closestDomain; 133 } else { 134 return false; 135 } 136 }, 137 138 sift4Distance: function(s1, s2, maxOffset) { 139 // sift4: https://siderite.blogspot.com/2014/11/super-fast-and-accurate-string-distance.html 140 if (maxOffset === undefined) { 141 maxOffset = 5; //default 142 } 143 144 if (!s1||!s1.length) { 145 if (!s2) { 146 return 0; 147 } 148 return s2.length; 149 } 150 151 if (!s2||!s2.length) { 152 return s1.length; 153 } 154 155 var l1=s1.length; 156 var l2=s2.length; 157 158 var c1 = 0; //cursor for string 1 159 var c2 = 0; //cursor for string 2 160 var lcss = 0; //largest common subsequence 161 var local_cs = 0; //local common substring 162 var trans = 0; //number of transpositions ('ab' vs 'ba') 163 var offset_arr=[]; //offset pair array, for computing the transpositions 164 165 while ((c1 < l1) && (c2 < l2)) { 166 if (s1.charAt(c1) == s2.charAt(c2)) { 167 local_cs++; 168 var isTrans=false; 169 //see if current match is a transposition 170 var i=0; 171 while (i<offset_arr.length) { 172 var ofs=offset_arr[i]; 173 if (c1<=ofs.c1 || c2 <= ofs.c2) { 174 // when two matches cross, the one considered a transposition is the one with the largest difference in offsets 175 isTrans=Math.abs(c2-c1)>=Math.abs(ofs.c2-ofs.c1); 176 if (isTrans) 177 { 178 trans++; 179 } else 180 { 181 if (!ofs.trans) { 182 ofs.trans=true; 183 trans++; 184 } 185 } 186 break; 187 } else { 188 if (c1>ofs.c2 && c2>ofs.c1) { 189 offset_arr.splice(i,1); 190 } else { 191 i++; 192 } 193 } 194 } 195 offset_arr.push({ 196 c1:c1, 197 c2:c2, 198 trans:isTrans 199 }); 200 } else { 201 lcss+=local_cs; 202 local_cs=0; 203 if (c1!=c2) { 204 c1=c2=Math.min(c1,c2); //using min allows the computation of transpositions 205 } 206 //if matching characters are found, remove 1 from both cursors (they get incremented at the end of the loop) 207 //so that we can have only one code block handling matches 208 for (var j = 0; j < maxOffset && (c1+j<l1 || c2+j<l2); j++) { 209 if ((c1 + j < l1) && (s1.charAt(c1 + j) == s2.charAt(c2))) { 210 c1+= j-1; 211 c2--; 212 break; 213 } 214 if ((c2 + j < l2) && (s1.charAt(c1) == s2.charAt(c2 + j))) { 215 c1--; 216 c2+= j-1; 217 break; 218 } 219 } 220 } 221 c1++; 222 c2++; 223 // this covers the case where the last match is on the last token in list, so that it can compute transpositions correctly 224 if ((c1 >= l1) || (c2 >= l2)) { 225 lcss+=local_cs; 226 local_cs=0; 227 c1=c2=Math.min(c1,c2); 228 } 229 } 230 lcss+=local_cs; 231 return Math.round(Math.max(l1,l2)- lcss +trans); //add the cost of transpositions to the final result 232 }, 233 234 splitEmail: function(email) { 235 email = email !== null ? (email.replace(/^\s*/, '').replace(/\s*$/, '')) : null; // trim() not exist in old IE! 236 var parts = email.split('@'); 237 238 if (parts.length < 2) { 239 return false; 240 } 241 242 for (var i = 0; i < parts.length; i++) { 243 if (parts[i] === '') { 244 return false; 245 } 246 } 247 248 var domain = parts.pop(); 249 var domainParts = domain.split('.'); 250 var sld = ''; 251 var tld = ''; 252 253 if (domainParts.length === 0) { 254 // The address does not have a top-level domain 255 return false; 256 } else if (domainParts.length == 1) { 257 // The address has only a top-level domain (valid under RFC) 258 tld = domainParts[0]; 259 } else { 260 // The address has a domain and a top-level domain 261 sld = domainParts[0]; 262 for (var j = 1; j < domainParts.length; j++) { 263 tld += domainParts[j] + '.'; 264 } 265 tld = tld.substring(0, tld.length - 1); 266 } 267 268 return { 269 topLevelDomain: tld, 270 secondLevelDomain: sld, 271 domain: domain, 272 address: parts.join('@') 273 }; 274 }, 275 276 // Encode the email address to prevent XSS but leave in valid 277 // characters, following this official spec: 278 // http://en.wikipedia.org/wiki/Email_address#Syntax 279 encodeEmail: function(email) { 280 var result = encodeURI(email); 281 result = result.replace('%20', ' ').replace('%25', '%').replace('%5E', '^') 282 .replace('%60', '`').replace('%7B', '{').replace('%7C', '|') 283 .replace('%7D', '}'); 284 return result; 285 } 286}; 287 288// Export the mailcheck object if we're in a CommonJS env (e.g. Node). 289// Modeled off of Underscore.js. 290if (typeof module !== 'undefined' && module.exports) { 291 module.exports = Mailcheck; 292} 293 294// Support AMD style definitions 295// Based on jQuery (see http://stackoverflow.com/a/17954882/1322410) 296if (typeof define === "function" && define.amd) { 297 define("mailcheck", [], function() { 298 return Mailcheck; 299 }); 300} 301 302if (typeof window !== 'undefined' && window.jQuery) { 303 (function($){ 304 $.fn.mailcheck = function(opts) { 305 var self = this; 306 if (opts.suggested) { 307 var oldSuggested = opts.suggested; 308 opts.suggested = function(result) { 309 oldSuggested(self, result); 310 }; 311 } 312 313 if (opts.empty) { 314 var oldEmpty = opts.empty; 315 opts.empty = function() { 316 oldEmpty.call(null, self); 317 }; 318 } 319 320 opts.email = this.val(); 321 Mailcheck.run(opts); 322 }; 323 })(jQuery); 324}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.