“use strict”;
var punycode = require(“punycode”); var mappingTable = require(“./lib/mappingTable.json”);
var PROCESSING_OPTIONS = {
TRANSITIONAL: 0, NONTRANSITIONAL: 1
};
function normalize(str) { // fix bug in v8
return str.split('\u0000').map(function (s) { return s.normalize('NFC'); }).join('\u0000');
}
function findStatus(val) {
var start = 0; var end = mappingTable.length - 1; while (start <= end) { var mid = Math.floor((start + end) / 2); var target = mappingTable[mid]; if (target[0][0] <= val && target[0][1] >= val) { return target; } else if (target[0][0] > val) { end = mid - 1; } else { start = mid + 1; } } return null;
}
var regexAstralSymbols = /[uD800-uDBFF]/g;
function countSymbols(string) {
return string // replace every surrogate pair with a BMP symbol .replace(regexAstralSymbols, '_') // then get the length .length;
}
function mapChars(domain_name, useSTD3, processing_option) {
var hasError = false; var processed = ""; var len = countSymbols(domain_name); for (var i = 0; i < len; ++i) { var codePoint = domain_name.codePointAt(i); var status = findStatus(codePoint); switch (status[1]) { case "disallowed": hasError = true; processed += String.fromCodePoint(codePoint); break; case "ignored": break; case "mapped": processed += String.fromCodePoint.apply(String, status[2]); break; case "deviation": if (processing_option === PROCESSING_OPTIONS.TRANSITIONAL) { processed += String.fromCodePoint.apply(String, status[2]); } else { processed += String.fromCodePoint(codePoint); } break; case "valid": processed += String.fromCodePoint(codePoint); break; case "disallowed_STD3_mapped": if (useSTD3) { hasError = true; processed += String.fromCodePoint(codePoint); } else { processed += String.fromCodePoint.apply(String, status[2]); } break; case "disallowed_STD3_valid": if (useSTD3) { hasError = true; } processed += String.fromCodePoint(codePoint); break; } } return { string: processed, error: hasError };
}
var combiningMarksRegex = /[u0300-u036Fu0483-u0489u0591-u05BDu05BFu05C1u05C2u05C4u05C5u05C7u0610-u061Au064B-u065Fu0670u06D6-u06DCu06DF-u06E4u06E7u06E8u06EA-u06EDu0711u0730-u074Au07A6-u07B0u07EB-u07F3u0816-u0819u081B-u0823u0825-u0827u0829-u082Du0859-u085Bu08E4-u0903u093A-u093Cu093E-u094Fu0951-u0957u0962u0963u0981-u0983u09BCu09BE-u09C4u09C7u09C8u09CB-u09CDu09D7u09E2u09E3u0A01-u0A03u0A3Cu0A3E-u0A42u0A47u0A48u0A4B-u0A4Du0A51u0A70u0A71u0A75u0A81-u0A83u0ABCu0ABE-u0AC5u0AC7-u0AC9u0ACB-u0ACDu0AE2u0AE3u0B01-u0B03u0B3Cu0B3E-u0B44u0B47u0B48u0B4B-u0B4Du0B56u0B57u0B62u0B63u0B82u0BBE-u0BC2u0BC6-u0BC8u0BCA-u0BCDu0BD7u0C00-u0C03u0C3E-u0C44u0C46-u0C48u0C4A-u0C4Du0C55u0C56u0C62u0C63u0C81-u0C83u0CBCu0CBE-u0CC4u0CC6-u0CC8u0CCA-u0CCDu0CD5u0CD6u0CE2u0CE3u0D01-u0D03u0D3E-u0D44u0D46-u0D48u0D4A-u0D4Du0D57u0D62u0D63u0D82u0D83u0DCAu0DCF-u0DD4u0DD6u0DD8-u0DDFu0DF2u0DF3u0E31u0E34-u0E3Au0E47-u0E4Eu0EB1u0EB4-u0EB9u0EBBu0EBCu0EC8-u0ECDu0F18u0F19u0F35u0F37u0F39u0F3Eu0F3Fu0F71-u0F84u0F86u0F87u0F8D-u0F97u0F99-u0FBCu0FC6u102B-u103Eu1056-u1059u105E-u1060u1062-u1064u1067-u106Du1071-u1074u1082-u108Du108Fu109A-u109Du135D-u135Fu1712-u1714u1732-u1734u1752u1753u1772u1773u17B4-u17D3u17DDu180B-u180Du18A9u1920-u192Bu1930-u193Bu19B0-u19C0u19C8u19C9u1A17-u1A1Bu1A55-u1A5Eu1A60-u1A7Cu1A7Fu1AB0-u1ABEu1B00-u1B04u1B34-u1B44u1B6B-u1B73u1B80-u1B82u1BA1-u1BADu1BE6-u1BF3u1C24-u1C37u1CD0-u1CD2u1CD4-u1CE8u1CEDu1CF2-u1CF4u1CF8u1CF9u1DC0-u1DF5u1DFC-u1DFFu20D0-u20F0u2CEF-u2CF1u2D7Fu2DE0-u2DFFu302A-u302Fu3099u309AuA66F-uA672uA674-uA67DuA69FuA6F0uA6F1uA802uA806uA80BuA823-uA827uA880uA881uA8B4-uA8C4uA8E0-uA8F1uA926-uA92DuA947-uA953uA980-uA983uA9B3-uA9C0uA9E5uAA29-uAA36uAA43uAA4CuAA4DuAA7B-uAA7DuAAB0uAAB2-uAAB4uAAB7uAAB8uAABEuAABFuAAC1uAAEB-uAAEFuAAF5uAAF6uABE3-uABEAuABECuABEDuFB1EuFE00-uFE0FuFE20-uFE2D]|uD800|uD802|uD804|uD805|uD81A|uD81B|uD82F|uD834|uD83A|uDB40/;
function validateLabel(label, processing_option) {
if (label.substr(0, 4) === "xn--") { label = punycode.toUnicode(label); processing_option = PROCESSING_OPTIONS.NONTRANSITIONAL; } var error = false; if (normalize(label) !== label || (label[3] === "-" && label[4] === "-") || label[0] === "-" || label[label.length - 1] === "-" || label.indexOf(".") !== -1 || label.search(combiningMarksRegex) === 0) { error = true; } var len = countSymbols(label); for (var i = 0; i < len; ++i) { var status = findStatus(label.codePointAt(i)); if ((processing === PROCESSING_OPTIONS.TRANSITIONAL && status[1] !== "valid") || (processing === PROCESSING_OPTIONS.NONTRANSITIONAL && status[1] !== "valid" && status[1] !== "deviation")) { error = true; break; } } return { label: label, error: error };
}
function processing(domain_name, useSTD3, processing_option) {
var result = mapChars(domain_name, useSTD3, processing_option); result.string = normalize(result.string); var labels = result.string.split("."); for (var i = 0; i < labels.length; ++i) { try { var validation = validateLabel(labels[i]); labels[i] = validation.label; result.error = result.error || validation.error; } catch(e) { result.error = true; } } return { string: labels.join("."), error: result.error };
}
module.exports.toASCII = function(domain_name, useSTD3, processing_option, verifyDnsLength) {
var result = processing(domain_name, useSTD3, processing_option); var labels = result.string.split("."); labels = labels.map(function(l) { try { return punycode.toASCII(l); } catch(e) { result.error = true; return l; } }); if (verifyDnsLength) { var total = labels.slice(0, labels.length - 1).join(".").length; if (total.length > 253 || total.length === 0) { result.error = true; } for (var i=0; i < labels.length; ++i) { if (labels.length > 63 || labels.length === 0) { result.error = true; break; } } } if (result.error) return null; return labels.join(".");
};
module.exports.toUnicode = function(domain_name, useSTD3) {
var result = processing(domain_name, useSTD3, PROCESSING_OPTIONS.NONTRANSITIONAL); return { domain: result.string, error: result.error };
};
module.exports.PROCESSING_OPTIONS = PROCESSING_OPTIONS;