UNPKG

4.85 kBJavaScriptView Raw
1import isIp from "is-ip";
2// See https://en.wikipedia.org/wiki/Domain_name
3// See https://tools.ietf.org/html/rfc1034
4const LABEL_SEPARATOR = ".";
5const LABEL_ROOT = "";
6const LABEL_LENGTH_MIN = 1;
7const LABEL_LENGTH_MAX = 63;
8const DOMAIN_LENGTH_MAX = 253;
9export var ValidationErrorType;
10(function (ValidationErrorType) {
11 ValidationErrorType["NoHostname"] = "NO_HOSTNAME";
12 ValidationErrorType["DomainMaxLength"] = "DOMAIN_MAX_LENGTH";
13 ValidationErrorType["LabelMinLength"] = "LABEL_MIN_LENGTH";
14 ValidationErrorType["LabelMaxLength"] = "LABEL_MAX_LENGTH";
15 ValidationErrorType["LabelInvalidCharacter"] = "LABEL_INVALID_CHARACTER";
16})(ValidationErrorType || (ValidationErrorType = {}));
17export var SanitizationResultType;
18(function (SanitizationResultType) {
19 SanitizationResultType["ValidIp"] = "VALID_IP";
20 SanitizationResultType["ValidDomain"] = "VALID_DOMAIN";
21 SanitizationResultType["Error"] = "ERROR";
22})(SanitizationResultType || (SanitizationResultType = {}));
23const createNoHostnameError = (input) => {
24 return {
25 type: ValidationErrorType.NoHostname,
26 message: `The given input ${String(input)} does not look like a hostname.`,
27 column: 1,
28 };
29};
30const createDomainMaxLengthError = (domain) => {
31 const length = domain.length;
32 return {
33 type: ValidationErrorType.DomainMaxLength,
34 message: `Domain "${domain}" is too long. Domain is ${length} octets long but should not be longer than ${DOMAIN_LENGTH_MAX}.`,
35 column: length,
36 };
37};
38const createLabelMinLengthError = (label, column) => {
39 const length = label.length;
40 return {
41 type: ValidationErrorType.LabelMinLength,
42 message: `Label "${label}" is too short. Label is ${length} octets long but should be at least ${LABEL_LENGTH_MIN}.`,
43 column,
44 };
45};
46const createLabelMaxLengthError = (label, column) => {
47 const length = label.length;
48 return {
49 type: ValidationErrorType.LabelMaxLength,
50 message: `Label "${label}" is too long. Label is ${length} octets long but should not be longer than ${LABEL_LENGTH_MAX}.`,
51 column,
52 };
53};
54const createLabelInvalidCharacterError = (label, invalidCharacter, column) => {
55 return {
56 type: ValidationErrorType.LabelInvalidCharacter,
57 message: `Label "${label}" contains invalid character "${invalidCharacter}" at column ${column}.`,
58 column,
59 };
60};
61export const sanitize = (input) => {
62 // Extra check for non-TypeScript users
63 if (typeof input !== "string") {
64 return {
65 type: SanitizationResultType.Error,
66 errors: [createNoHostnameError(input)],
67 };
68 }
69 const inputTrimmed = input.trim();
70 // IPv6 addresses are surrounded by square brackets in URLs
71 // See https://tools.ietf.org/html/rfc3986#section-3.2.2
72 const inputTrimmedAsIp = inputTrimmed.replace(/^\[|]$/g, "");
73 const ipVersion = isIp.version(inputTrimmedAsIp);
74 if (ipVersion !== undefined) {
75 return {
76 type: SanitizationResultType.ValidIp,
77 ip: inputTrimmedAsIp,
78 ipVersion,
79 };
80 }
81 if (inputTrimmed.length > DOMAIN_LENGTH_MAX) {
82 return {
83 type: SanitizationResultType.Error,
84 errors: [createDomainMaxLengthError(inputTrimmed)],
85 };
86 }
87 const labels = inputTrimmed.split(LABEL_SEPARATOR);
88 const lastLabel = labels[labels.length - 1];
89 // If the last label is the special root label, ignore it
90 if (lastLabel === LABEL_ROOT) {
91 labels.pop();
92 }
93 const labelValidationErrors = [];
94 let column = 1;
95 for (const label of labels) {
96 // According to https://tools.ietf.org/html/rfc6761 labels should
97 // only contain ASCII letters, digits and hyphens (LDH).
98 const invalidCharacter = /[^\da-z-]/iu.exec(label);
99 if (invalidCharacter) {
100 labelValidationErrors.push(createLabelInvalidCharacterError(label, invalidCharacter[0], invalidCharacter.index + 1));
101 }
102 else if (
103 // We can use .length here to check for the octet size because
104 // label can only contain ASCII LDH characters at this point.
105 label.length < LABEL_LENGTH_MIN) {
106 labelValidationErrors.push(createLabelMinLengthError(label, column));
107 }
108 else if (label.length > LABEL_LENGTH_MAX) {
109 labelValidationErrors.push(createLabelMaxLengthError(label, column));
110 }
111 column += label.length + LABEL_SEPARATOR.length;
112 }
113 if (labelValidationErrors.length > 0) {
114 return {
115 type: SanitizationResultType.Error,
116 errors: labelValidationErrors,
117 };
118 }
119 return {
120 type: SanitizationResultType.ValidDomain,
121 domain: inputTrimmed,
122 labels,
123 };
124};
125//# sourceMappingURL=sanitize.js.map
\No newline at end of file