| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 | 
							- "use strict";
 
- const punycode = require("punycode");
 
- const regexes = require("./lib/regexes.js");
 
- const mappingTable = require("./lib/mappingTable.json");
 
- const { STATUS_MAPPING } = require("./lib/statusMapping.js");
 
- function containsNonASCII(str) {
 
-   return /[^\x00-\x7F]/.test(str);
 
- }
 
- function findStatus(val, { useSTD3ASCIIRules }) {
 
-   let start = 0;
 
-   let end = mappingTable.length - 1;
 
-   while (start <= end) {
 
-     const mid = Math.floor((start + end) / 2);
 
-     const target = mappingTable[mid];
 
-     const min = Array.isArray(target[0]) ? target[0][0] : target[0];
 
-     const max = Array.isArray(target[0]) ? target[0][1] : target[0];
 
-     if (min <= val && max >= val) {
 
-       if (useSTD3ASCIIRules &&
 
-           (target[1] === STATUS_MAPPING.disallowed_STD3_valid || target[1] === STATUS_MAPPING.disallowed_STD3_mapped)) {
 
-         return [STATUS_MAPPING.disallowed, ...target.slice(2)];
 
-       } else if (target[1] === STATUS_MAPPING.disallowed_STD3_valid) {
 
-         return [STATUS_MAPPING.valid, ...target.slice(2)];
 
-       } else if (target[1] === STATUS_MAPPING.disallowed_STD3_mapped) {
 
-         return [STATUS_MAPPING.mapped, ...target.slice(2)];
 
-       }
 
-       return target.slice(1);
 
-     } else if (min > val) {
 
-       end = mid - 1;
 
-     } else {
 
-       start = mid + 1;
 
-     }
 
-   }
 
-   return null;
 
- }
 
- function mapChars(domainName, { useSTD3ASCIIRules, processingOption }) {
 
-   let hasError = false;
 
-   let processed = "";
 
-   for (const ch of domainName) {
 
-     const [status, mapping] = findStatus(ch.codePointAt(0), { useSTD3ASCIIRules });
 
-     switch (status) {
 
-       case STATUS_MAPPING.disallowed:
 
-         hasError = true;
 
-         processed += ch;
 
-         break;
 
-       case STATUS_MAPPING.ignored:
 
-         break;
 
-       case STATUS_MAPPING.mapped:
 
-         processed += mapping;
 
-         break;
 
-       case STATUS_MAPPING.deviation:
 
-         if (processingOption === "transitional") {
 
-           processed += mapping;
 
-         } else {
 
-           processed += ch;
 
-         }
 
-         break;
 
-       case STATUS_MAPPING.valid:
 
-         processed += ch;
 
-         break;
 
-     }
 
-   }
 
-   return {
 
-     string: processed,
 
-     error: hasError
 
-   };
 
- }
 
- function validateLabel(label, { checkHyphens, checkBidi, checkJoiners, processingOption, useSTD3ASCIIRules }) {
 
-   if (label.normalize("NFC") !== label) {
 
-     return false;
 
-   }
 
-   const codePoints = Array.from(label);
 
-   if (checkHyphens) {
 
-     if ((codePoints[2] === "-" && codePoints[3] === "-") ||
 
-         (label.startsWith("-") || label.endsWith("-"))) {
 
-       return false;
 
-     }
 
-   }
 
-   if (label.includes(".") ||
 
-       (codePoints.length > 0 && regexes.combiningMarks.test(codePoints[0]))) {
 
-     return false;
 
-   }
 
-   for (const ch of codePoints) {
 
-     const [status] = findStatus(ch.codePointAt(0), { useSTD3ASCIIRules });
 
-     if ((processingOption === "transitional" && status !== STATUS_MAPPING.valid) ||
 
-         (processingOption === "nontransitional" &&
 
-          status !== STATUS_MAPPING.valid && status !== STATUS_MAPPING.deviation)) {
 
-       return false;
 
-     }
 
-   }
 
-   // https://tools.ietf.org/html/rfc5892#appendix-A
 
-   if (checkJoiners) {
 
-     let last = 0;
 
-     for (const [i, ch] of codePoints.entries()) {
 
-       if (ch === "\u200C" || ch === "\u200D") {
 
-         if (i > 0) {
 
-           if (regexes.combiningClassVirama.test(codePoints[i - 1])) {
 
-             continue;
 
-           }
 
-           if (ch === "\u200C") {
 
-             // TODO: make this more efficient
 
-             const next = codePoints.indexOf("\u200C", i + 1);
 
-             const test = next < 0 ? codePoints.slice(last) : codePoints.slice(last, next);
 
-             if (regexes.validZWNJ.test(test.join(""))) {
 
-               last = i + 1;
 
-               continue;
 
-             }
 
-           }
 
-         }
 
-         return false;
 
-       }
 
-     }
 
-   }
 
-   // https://tools.ietf.org/html/rfc5893#section-2
 
-   if (checkBidi) {
 
-     let rtl;
 
-     // 1
 
-     if (regexes.bidiS1LTR.test(codePoints[0])) {
 
-       rtl = false;
 
-     } else if (regexes.bidiS1RTL.test(codePoints[0])) {
 
-       rtl = true;
 
-     } else {
 
-       return false;
 
-     }
 
-     if (rtl) {
 
-       // 2-4
 
-       if (!regexes.bidiS2.test(label) ||
 
-           !regexes.bidiS3.test(label) ||
 
-           (regexes.bidiS4EN.test(label) && regexes.bidiS4AN.test(label))) {
 
-         return false;
 
-       }
 
-     } else if (!regexes.bidiS5.test(label) ||
 
-                !regexes.bidiS6.test(label)) { // 5-6
 
-       return false;
 
-     }
 
-   }
 
-   return true;
 
- }
 
- function isBidiDomain(labels) {
 
-   const domain = labels.map(label => {
 
-     if (label.startsWith("xn--")) {
 
-       try {
 
-         return punycode.decode(label.substring(4));
 
-       } catch (err) {
 
-         return "";
 
-       }
 
-     }
 
-     return label;
 
-   }).join(".");
 
-   return regexes.bidiDomain.test(domain);
 
- }
 
- function processing(domainName, options) {
 
-   const { processingOption } = options;
 
-   // 1. Map.
 
-   let { string, error } = mapChars(domainName, options);
 
-   // 2. Normalize.
 
-   string = string.normalize("NFC");
 
-   // 3. Break.
 
-   const labels = string.split(".");
 
-   const isBidi = isBidiDomain(labels);
 
-   // 4. Convert/Validate.
 
-   for (const [i, origLabel] of labels.entries()) {
 
-     let label = origLabel;
 
-     let curProcessing = processingOption;
 
-     if (label.startsWith("xn--")) {
 
-       try {
 
-         label = punycode.decode(label.substring(4));
 
-         labels[i] = label;
 
-       } catch (err) {
 
-         error = true;
 
-         continue;
 
-       }
 
-       curProcessing = "nontransitional";
 
-     }
 
-     // No need to validate if we already know there is an error.
 
-     if (error) {
 
-       continue;
 
-     }
 
-     const validation = validateLabel(label, Object.assign({}, options, {
 
-       processingOption: curProcessing,
 
-       checkBidi: options.checkBidi && isBidi
 
-     }));
 
-     if (!validation) {
 
-       error = true;
 
-     }
 
-   }
 
-   return {
 
-     string: labels.join("."),
 
-     error
 
-   };
 
- }
 
- function toASCII(domainName, {
 
-   checkHyphens = false,
 
-   checkBidi = false,
 
-   checkJoiners = false,
 
-   useSTD3ASCIIRules = false,
 
-   processingOption = "nontransitional",
 
-   verifyDNSLength = false
 
- } = {}) {
 
-   if (processingOption !== "transitional" && processingOption !== "nontransitional") {
 
-     throw new RangeError("processingOption must be either transitional or nontransitional");
 
-   }
 
-   const result = processing(domainName, {
 
-     processingOption,
 
-     checkHyphens,
 
-     checkBidi,
 
-     checkJoiners,
 
-     useSTD3ASCIIRules
 
-   });
 
-   let labels = result.string.split(".");
 
-   labels = labels.map(l => {
 
-     if (containsNonASCII(l)) {
 
-       try {
 
-         return "xn--" + punycode.encode(l);
 
-       } catch (e) {
 
-         result.error = true;
 
-       }
 
-     }
 
-     return l;
 
-   });
 
-   if (verifyDNSLength) {
 
-     const total = labels.join(".").length;
 
-     if (total > 253 || total === 0) {
 
-       result.error = true;
 
-     }
 
-     for (let i = 0; i < labels.length; ++i) {
 
-       if (labels[i].length > 63 || labels[i].length === 0) {
 
-         result.error = true;
 
-         break;
 
-       }
 
-     }
 
-   }
 
-   if (result.error) {
 
-     return null;
 
-   }
 
-   return labels.join(".");
 
- }
 
- function toUnicode(domainName, {
 
-   checkHyphens = false,
 
-   checkBidi = false,
 
-   checkJoiners = false,
 
-   useSTD3ASCIIRules = false,
 
-   processingOption = "nontransitional"
 
- } = {}) {
 
-   const result = processing(domainName, {
 
-     processingOption,
 
-     checkHyphens,
 
-     checkBidi,
 
-     checkJoiners,
 
-     useSTD3ASCIIRules
 
-   });
 
-   return {
 
-     domain: result.string,
 
-     error: result.error
 
-   };
 
- }
 
- module.exports = {
 
-   toASCII,
 
-   toUnicode
 
- };
 
 
  |