1 | import isIp from "is-ip";
|
2 |
|
3 |
|
4 | const LABEL_SEPARATOR = ".";
|
5 | const LABEL_ROOT = "";
|
6 | const LABEL_LENGTH_MIN = 1;
|
7 | const LABEL_LENGTH_MAX = 63;
|
8 | const DOMAIN_LENGTH_MAX = 253;
|
9 | export 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 = {}));
|
17 | export var SanitizationResultType;
|
18 | (function (SanitizationResultType) {
|
19 | SanitizationResultType["ValidIp"] = "VALID_IP";
|
20 | SanitizationResultType["ValidDomain"] = "VALID_DOMAIN";
|
21 | SanitizationResultType["Error"] = "ERROR";
|
22 | })(SanitizationResultType || (SanitizationResultType = {}));
|
23 | const 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 | };
|
30 | const 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 | };
|
38 | const 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 | };
|
46 | const 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 | };
|
54 | const 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 | };
|
61 | export const sanitize = (input) => {
|
62 |
|
63 | if (typeof input !== "string") {
|
64 | return {
|
65 | type: SanitizationResultType.Error,
|
66 | errors: [createNoHostnameError(input)],
|
67 | };
|
68 | }
|
69 | const inputTrimmed = input.trim();
|
70 |
|
71 |
|
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 |
|
90 | if (lastLabel === LABEL_ROOT) {
|
91 | labels.pop();
|
92 | }
|
93 | const labelValidationErrors = [];
|
94 | let column = 1;
|
95 | for (const label of labels) {
|
96 |
|
97 |
|
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 |
|
104 |
|
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 |
|
\ | No newline at end of file |