@arcis/node 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -222
- package/dist/core/index.d.mts +4 -4
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +13 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +13 -2
- package/dist/core/index.mjs.map +1 -1
- package/dist/index-A-m-pPeW.d.mts +340 -0
- package/dist/index-CgK94hY_.d.mts +532 -0
- package/dist/index-Co5kPRZz.d.ts +340 -0
- package/dist/index-D_bdJcF0.d.ts +532 -0
- package/dist/index.d.mts +144 -108
- package/dist/index.d.ts +144 -108
- package/dist/index.js +1541 -211
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1515 -212
- package/dist/index.mjs.map +1 -1
- package/dist/logging/index.d.mts +1 -1
- package/dist/logging/index.d.ts +1 -1
- package/dist/logging/index.js +12 -1
- package/dist/logging/index.js.map +1 -1
- package/dist/logging/index.mjs +12 -1
- package/dist/logging/index.mjs.map +1 -1
- package/dist/middleware/index.d.mts +2 -2
- package/dist/middleware/index.d.ts +2 -2
- package/dist/middleware/index.js +524 -4
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/index.mjs +517 -5
- package/dist/middleware/index.mjs.map +1 -1
- package/dist/{headers-DBQedhrb.d.mts → pii-CXcHMlnX.d.mts} +156 -2
- package/dist/{headers-BJq2OA0i.d.ts → pii-DhNpl7M3.d.ts} +156 -2
- package/dist/sanitizers/index.d.mts +2 -2
- package/dist/sanitizers/index.d.ts +2 -2
- package/dist/sanitizers/index.js +331 -3
- package/dist/sanitizers/index.js.map +1 -1
- package/dist/sanitizers/index.mjs +321 -4
- package/dist/sanitizers/index.mjs.map +1 -1
- package/dist/stores/index.d.mts +1 -1
- package/dist/stores/index.d.ts +1 -1
- package/dist/stores/index.js.map +1 -1
- package/dist/stores/index.mjs.map +1 -1
- package/dist/{types-BOdL3ZWo.d.mts → types-CsOFHoD9.d.mts} +6 -1
- package/dist/{types-BOdL3ZWo.d.ts → types-CsOFHoD9.d.ts} +6 -1
- package/dist/validation/index.d.mts +2 -2
- package/dist/validation/index.d.ts +2 -2
- package/dist/validation/index.js +504 -2
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/index.mjs +498 -3
- package/dist/validation/index.mjs.map +1 -1
- package/package.json +114 -109
- package/dist/index-BgHPM7LC.d.ts +0 -129
- package/dist/index-BpT7flAQ.d.ts +0 -255
- package/dist/index-JaFOUKyK.d.mts +0 -255
- package/dist/index-nAgXexwD.d.mts +0 -129
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { promises } from 'dns';
|
|
2
|
+
|
|
1
3
|
// src/core/constants.ts
|
|
2
4
|
var INPUT = {
|
|
3
5
|
/** Default maximum input size (1MB) */
|
|
@@ -49,7 +51,11 @@ var SQL_PATTERNS = [
|
|
|
49
51
|
/** Time-based blind: SLEEP() */
|
|
50
52
|
/\bSLEEP\s*\(\s*\d+\s*\)/gi,
|
|
51
53
|
/** Time-based blind: BENCHMARK() */
|
|
52
|
-
/\bBENCHMARK\s*\(/gi
|
|
54
|
+
/\bBENCHMARK\s*\(/gi,
|
|
55
|
+
/** Time-based blind: PostgreSQL pg_sleep() */
|
|
56
|
+
/\bpg_sleep\s*\(/gi,
|
|
57
|
+
/** Time-based blind: MSSQL WAITFOR DELAY */
|
|
58
|
+
/\bWAITFOR\s+DELAY\b/gi
|
|
53
59
|
];
|
|
54
60
|
var PATH_PATTERNS = [
|
|
55
61
|
/** Unix path traversal */
|
|
@@ -67,6 +73,10 @@ var PATH_PATTERNS = [
|
|
|
67
73
|
/\.%2e[\\/]/gi,
|
|
68
74
|
/** Fully URL-encoded: %2e%2e%2f */
|
|
69
75
|
/%2e%2e%2f/gi,
|
|
76
|
+
/** Double URL-encoded forward slash: %252f */
|
|
77
|
+
/%252f/gi,
|
|
78
|
+
/** Dotdotslash bypass: ....// or ....\\ */
|
|
79
|
+
/\.{2,}[/\\]{2,}/g,
|
|
70
80
|
/** Null byte injection in paths */
|
|
71
81
|
/\0/g
|
|
72
82
|
];
|
|
@@ -82,7 +92,9 @@ var COMMAND_PATTERNS = [
|
|
|
82
92
|
*/
|
|
83
93
|
/[;&|`]/g,
|
|
84
94
|
/** Command substitution: $( ... ) — matched as a pair to reduce false positives */
|
|
85
|
-
/\$\(/g
|
|
95
|
+
/\$\(/g,
|
|
96
|
+
/** URL-encoded newline/carriage-return injection (%0a, %0d) */
|
|
97
|
+
/%0[ad]/gi
|
|
86
98
|
];
|
|
87
99
|
var VALIDATION = {
|
|
88
100
|
/**
|
|
@@ -694,6 +706,489 @@ function isDangerousExtension(filename) {
|
|
|
694
706
|
return ext !== "" && DANGEROUS_EXTENSIONS.has(ext);
|
|
695
707
|
}
|
|
696
708
|
|
|
697
|
-
|
|
709
|
+
// src/validation/url.ts
|
|
710
|
+
function validateUrl(url, options = {}) {
|
|
711
|
+
const {
|
|
712
|
+
allowedProtocols = ["http:", "https:"],
|
|
713
|
+
blockedHosts = [],
|
|
714
|
+
allowedHosts = [],
|
|
715
|
+
allowLocalhost = false,
|
|
716
|
+
allowPrivate = false
|
|
717
|
+
} = options;
|
|
718
|
+
if (typeof url !== "string" || url.trim() === "") {
|
|
719
|
+
return { safe: false, reason: "invalid URL: empty or not a string" };
|
|
720
|
+
}
|
|
721
|
+
let parsed;
|
|
722
|
+
try {
|
|
723
|
+
parsed = new URL(url);
|
|
724
|
+
} catch {
|
|
725
|
+
return { safe: false, reason: "invalid URL: failed to parse" };
|
|
726
|
+
}
|
|
727
|
+
if (!allowedProtocols.includes(parsed.protocol)) {
|
|
728
|
+
return { safe: false, reason: `disallowed protocol: ${parsed.protocol}` };
|
|
729
|
+
}
|
|
730
|
+
if (parsed.username || parsed.password) {
|
|
731
|
+
return { safe: false, reason: "URL contains credentials" };
|
|
732
|
+
}
|
|
733
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
734
|
+
if (allowedHosts.some((h) => hostname === h.toLowerCase())) {
|
|
735
|
+
return { safe: true };
|
|
736
|
+
}
|
|
737
|
+
if (blockedHosts.some((h) => hostname === h.toLowerCase())) {
|
|
738
|
+
return { safe: false, reason: `blocked host: ${hostname}` };
|
|
739
|
+
}
|
|
740
|
+
if (!allowLocalhost) {
|
|
741
|
+
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "::1" || hostname === "0.0.0.0" || hostname.endsWith(".localhost")) {
|
|
742
|
+
return { safe: false, reason: "loopback address" };
|
|
743
|
+
}
|
|
744
|
+
if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname)) {
|
|
745
|
+
return { safe: false, reason: "loopback address" };
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (!allowLocalhost || !allowPrivate) {
|
|
749
|
+
const decimalCheck = checkDecimalIp(hostname, allowLocalhost, allowPrivate);
|
|
750
|
+
if (decimalCheck) {
|
|
751
|
+
return { safe: false, reason: decimalCheck };
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
if (!allowLocalhost || !allowPrivate) {
|
|
755
|
+
const octalCheck = checkOctalIp(hostname, allowLocalhost, allowPrivate);
|
|
756
|
+
if (octalCheck) {
|
|
757
|
+
return { safe: false, reason: octalCheck };
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
if (!allowPrivate) {
|
|
761
|
+
const privateCheck = checkPrivateIp(hostname);
|
|
762
|
+
if (privateCheck) {
|
|
763
|
+
return { safe: false, reason: privateCheck };
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return { safe: true };
|
|
767
|
+
}
|
|
768
|
+
function isUrlSafe(url, options = {}) {
|
|
769
|
+
return validateUrl(url, options).safe;
|
|
770
|
+
}
|
|
771
|
+
function checkPrivateIp(hostname) {
|
|
772
|
+
if (/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname)) {
|
|
773
|
+
return "private address (10.0.0.0/8)";
|
|
774
|
+
}
|
|
775
|
+
const match172 = hostname.match(/^172\.(\d{1,3})\.\d{1,3}\.\d{1,3}$/);
|
|
776
|
+
if (match172) {
|
|
777
|
+
const second = parseInt(match172[1], 10);
|
|
778
|
+
if (second >= 16 && second <= 31) {
|
|
779
|
+
return "private address (172.16.0.0/12)";
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
if (/^192\.168\.\d{1,3}\.\d{1,3}$/.test(hostname)) {
|
|
783
|
+
return "private address (192.168.0.0/16)";
|
|
784
|
+
}
|
|
785
|
+
if (/^169\.254\.\d{1,3}\.\d{1,3}$/.test(hostname)) {
|
|
786
|
+
return "link-local address (169.254.0.0/16)";
|
|
787
|
+
}
|
|
788
|
+
if (/^0\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname)) {
|
|
789
|
+
return "current network address (0.0.0.0/8)";
|
|
790
|
+
}
|
|
791
|
+
if (hostname === "metadata.google.internal" || hostname === "metadata.internal" || hostname === "metadata.azure.internal") {
|
|
792
|
+
return "cloud metadata endpoint";
|
|
793
|
+
}
|
|
794
|
+
const ipv6 = hostname.replace(/^\[|\]$/g, "");
|
|
795
|
+
if (ipv6 === "::1" || ipv6 === "::" || ipv6.startsWith("fc") || ipv6.startsWith("fd") || ipv6.startsWith("fe80")) {
|
|
796
|
+
return "private IPv6 address";
|
|
797
|
+
}
|
|
798
|
+
const mappedDotted = ipv6.match(/^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i);
|
|
799
|
+
if (mappedDotted) {
|
|
800
|
+
const mappedIp = mappedDotted[1];
|
|
801
|
+
if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(mappedIp)) {
|
|
802
|
+
return "IPv6-mapped loopback address";
|
|
803
|
+
}
|
|
804
|
+
const mappedCheck = checkPrivateIp(mappedIp);
|
|
805
|
+
if (mappedCheck) {
|
|
806
|
+
return `IPv6-mapped ${mappedCheck}`;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
const mappedHex = ipv6.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/i);
|
|
810
|
+
if (mappedHex) {
|
|
811
|
+
const hi = parseInt(mappedHex[1], 16);
|
|
812
|
+
const lo = parseInt(mappedHex[2], 16);
|
|
813
|
+
const a = hi >> 8 & 255;
|
|
814
|
+
const b = hi & 255;
|
|
815
|
+
const c = lo >> 8 & 255;
|
|
816
|
+
const d = lo & 255;
|
|
817
|
+
const dotted = `${a}.${b}.${c}.${d}`;
|
|
818
|
+
if (a === 127) {
|
|
819
|
+
return "IPv6-mapped loopback address";
|
|
820
|
+
}
|
|
821
|
+
const hexCheck = checkPrivateIp(dotted);
|
|
822
|
+
if (hexCheck) {
|
|
823
|
+
return `IPv6-mapped ${hexCheck}`;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return null;
|
|
827
|
+
}
|
|
828
|
+
function checkDecimalIp(hostname, allowLocalhost, allowPrivate) {
|
|
829
|
+
if (!/^\d+$/.test(hostname)) return null;
|
|
830
|
+
const num = parseInt(hostname, 10);
|
|
831
|
+
if (isNaN(num) || num < 0 || num > 4294967295) return null;
|
|
832
|
+
const a = num >>> 24 & 255;
|
|
833
|
+
const b = num >>> 16 & 255;
|
|
834
|
+
const c = num >>> 8 & 255;
|
|
835
|
+
const d = num & 255;
|
|
836
|
+
const dotted = `${a}.${b}.${c}.${d}`;
|
|
837
|
+
if (!allowLocalhost && a === 127) {
|
|
838
|
+
return `loopback address (decimal IP: ${dotted})`;
|
|
839
|
+
}
|
|
840
|
+
if (!allowPrivate) {
|
|
841
|
+
const privateCheck = checkPrivateIp(dotted);
|
|
842
|
+
if (privateCheck) {
|
|
843
|
+
return `${privateCheck} (decimal IP: ${dotted})`;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
function checkOctalIp(hostname, allowLocalhost, allowPrivate) {
|
|
849
|
+
const parts = hostname.split(".");
|
|
850
|
+
if (parts.length !== 4) return null;
|
|
851
|
+
const hasAlternateNotation = parts.some((p) => /^0[0-7]+$/.test(p) || /^0x[0-9a-fA-F]+$/i.test(p));
|
|
852
|
+
if (!hasAlternateNotation) return null;
|
|
853
|
+
const octets = [];
|
|
854
|
+
for (const part of parts) {
|
|
855
|
+
let val;
|
|
856
|
+
if (/^0x[0-9a-fA-F]+$/i.test(part)) {
|
|
857
|
+
val = parseInt(part, 16);
|
|
858
|
+
} else if (/^0[0-7]*$/.test(part)) {
|
|
859
|
+
val = parseInt(part, 8);
|
|
860
|
+
} else if (/^\d+$/.test(part)) {
|
|
861
|
+
val = parseInt(part, 10);
|
|
862
|
+
} else {
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
865
|
+
if (val < 0 || val > 255) return null;
|
|
866
|
+
octets.push(val);
|
|
867
|
+
}
|
|
868
|
+
const dotted = octets.join(".");
|
|
869
|
+
if (!allowLocalhost && octets[0] === 127) {
|
|
870
|
+
return `loopback address (octal IP: ${dotted})`;
|
|
871
|
+
}
|
|
872
|
+
if (!allowPrivate) {
|
|
873
|
+
const privateCheck = checkPrivateIp(dotted);
|
|
874
|
+
if (privateCheck) {
|
|
875
|
+
return `${privateCheck} (octal IP: ${dotted})`;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return null;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// src/validation/redirect.ts
|
|
882
|
+
var DANGEROUS_PROTOCOLS = /^(javascript|data|vbscript|blob):/i;
|
|
883
|
+
var CONTROL_CHARS = /[\t\n\r]/g;
|
|
884
|
+
function validateRedirect(url, options = {}) {
|
|
885
|
+
const {
|
|
886
|
+
allowedHosts = [],
|
|
887
|
+
allowProtocolRelative = false,
|
|
888
|
+
allowedProtocols = ["http:", "https:"]
|
|
889
|
+
} = options;
|
|
890
|
+
if (typeof url !== "string" || url.trim() === "") {
|
|
891
|
+
return { safe: false, reason: "invalid redirect: empty or not a string" };
|
|
892
|
+
}
|
|
893
|
+
const cleaned = url.replace(CONTROL_CHARS, "");
|
|
894
|
+
if (DANGEROUS_PROTOCOLS.test(cleaned)) {
|
|
895
|
+
const proto = cleaned.match(DANGEROUS_PROTOCOLS);
|
|
896
|
+
return { safe: false, reason: `dangerous protocol: ${proto[0]}` };
|
|
897
|
+
}
|
|
898
|
+
if (cleaned.startsWith("\\")) {
|
|
899
|
+
return { safe: false, reason: "backslash-prefixed URL (browser treats as protocol-relative)" };
|
|
900
|
+
}
|
|
901
|
+
if (cleaned.startsWith("//")) {
|
|
902
|
+
if (!allowProtocolRelative) {
|
|
903
|
+
const host2 = extractHost(cleaned);
|
|
904
|
+
if (host2 && allowedHosts.some((h) => host2 === h.toLowerCase())) {
|
|
905
|
+
return { safe: true };
|
|
906
|
+
}
|
|
907
|
+
return { safe: false, reason: "protocol-relative URL not in allowed hosts" };
|
|
908
|
+
}
|
|
909
|
+
const host = extractHost(cleaned);
|
|
910
|
+
if (host && allowedHosts.length > 0 && !allowedHosts.some((h) => host === h.toLowerCase())) {
|
|
911
|
+
return { safe: false, reason: "protocol-relative URL not in allowed hosts" };
|
|
912
|
+
}
|
|
913
|
+
return { safe: true };
|
|
914
|
+
}
|
|
915
|
+
let parsed;
|
|
916
|
+
try {
|
|
917
|
+
parsed = new URL(cleaned);
|
|
918
|
+
} catch {
|
|
919
|
+
return { safe: true };
|
|
920
|
+
}
|
|
921
|
+
if (!allowedProtocols.includes(parsed.protocol)) {
|
|
922
|
+
return { safe: false, reason: `disallowed protocol: ${parsed.protocol}` };
|
|
923
|
+
}
|
|
924
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
925
|
+
if (allowedHosts.length === 0) {
|
|
926
|
+
return { safe: false, reason: "absolute URL not in allowed hosts" };
|
|
927
|
+
}
|
|
928
|
+
if (!allowedHosts.some((h) => hostname === h.toLowerCase())) {
|
|
929
|
+
return { safe: false, reason: `host not allowed: ${hostname}` };
|
|
930
|
+
}
|
|
931
|
+
return { safe: true };
|
|
932
|
+
}
|
|
933
|
+
function isRedirectSafe(url, options = {}) {
|
|
934
|
+
return validateRedirect(url, options).safe;
|
|
935
|
+
}
|
|
936
|
+
function extractHost(url) {
|
|
937
|
+
const match = url.match(/^\/\/([^/:?#]+)/);
|
|
938
|
+
return match ? match[1].toLowerCase() : null;
|
|
939
|
+
}
|
|
940
|
+
var MAX_EMAIL_LENGTH = 254;
|
|
941
|
+
var MAX_LOCAL_LENGTH = 64;
|
|
942
|
+
var MAX_DOMAIN_LENGTH = 255;
|
|
943
|
+
var EMAIL_SYNTAX = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\.[a-zA-Z]{2,}$/;
|
|
944
|
+
var FREE_PROVIDERS = /* @__PURE__ */ new Set([
|
|
945
|
+
"gmail.com",
|
|
946
|
+
"yahoo.com",
|
|
947
|
+
"hotmail.com",
|
|
948
|
+
"outlook.com",
|
|
949
|
+
"aol.com",
|
|
950
|
+
"protonmail.com",
|
|
951
|
+
"proton.me",
|
|
952
|
+
"icloud.com",
|
|
953
|
+
"mail.com",
|
|
954
|
+
"zoho.com",
|
|
955
|
+
"yandex.com",
|
|
956
|
+
"gmx.com",
|
|
957
|
+
"gmx.net",
|
|
958
|
+
"live.com",
|
|
959
|
+
"msn.com",
|
|
960
|
+
"me.com",
|
|
961
|
+
"mac.com",
|
|
962
|
+
"fastmail.com",
|
|
963
|
+
"tutanota.com",
|
|
964
|
+
"hey.com"
|
|
965
|
+
]);
|
|
966
|
+
var DISPOSABLE_DOMAINS = /* @__PURE__ */ new Set([
|
|
967
|
+
// Popular disposable services
|
|
968
|
+
"guerrillamail.com",
|
|
969
|
+
"guerrillamail.net",
|
|
970
|
+
"guerrillamail.org",
|
|
971
|
+
"tempmail.com",
|
|
972
|
+
"temp-mail.org",
|
|
973
|
+
"temp-mail.io",
|
|
974
|
+
"throwaway.email",
|
|
975
|
+
"throwaway.com",
|
|
976
|
+
"mailinator.com",
|
|
977
|
+
"mailinator.net",
|
|
978
|
+
"yopmail.com",
|
|
979
|
+
"yopmail.fr",
|
|
980
|
+
"yopmail.net",
|
|
981
|
+
"sharklasers.com",
|
|
982
|
+
"grr.la",
|
|
983
|
+
"guerrillamail.info",
|
|
984
|
+
"guerrillamail.biz",
|
|
985
|
+
"guerrillamail.de",
|
|
986
|
+
"trashmail.com",
|
|
987
|
+
"trashmail.me",
|
|
988
|
+
"trashmail.net",
|
|
989
|
+
"dispostable.com",
|
|
990
|
+
"maildrop.cc",
|
|
991
|
+
"mailnesia.com",
|
|
992
|
+
"tempail.com",
|
|
993
|
+
"mohmal.com",
|
|
994
|
+
"getnada.com",
|
|
995
|
+
"emailondeck.com",
|
|
996
|
+
"discard.email",
|
|
997
|
+
"fakeinbox.com",
|
|
998
|
+
"mailcatch.com",
|
|
999
|
+
"mintemail.com",
|
|
1000
|
+
"tempr.email",
|
|
1001
|
+
"tempinbox.com",
|
|
1002
|
+
"burnermail.io",
|
|
1003
|
+
"mailsac.com",
|
|
1004
|
+
"harakirimail.com",
|
|
1005
|
+
"tempmailo.com",
|
|
1006
|
+
"emailfake.com",
|
|
1007
|
+
"crazymailing.com",
|
|
1008
|
+
"armyspy.com",
|
|
1009
|
+
"dayrep.com",
|
|
1010
|
+
"einrot.com",
|
|
1011
|
+
"fleckens.hu",
|
|
1012
|
+
"gustr.com",
|
|
1013
|
+
"jourrapide.com",
|
|
1014
|
+
"rhyta.com",
|
|
1015
|
+
"superrito.com",
|
|
1016
|
+
"teleworm.us",
|
|
1017
|
+
"10minutemail.com",
|
|
1018
|
+
"10minutemail.net",
|
|
1019
|
+
"minutemail.com",
|
|
1020
|
+
"tempsky.com",
|
|
1021
|
+
"spamgourmet.com",
|
|
1022
|
+
"mytrashmail.com",
|
|
1023
|
+
"mailexpire.com",
|
|
1024
|
+
"safetymail.info",
|
|
1025
|
+
"filzmail.com",
|
|
1026
|
+
"trashymail.com",
|
|
1027
|
+
"sharkmail.com",
|
|
1028
|
+
"jetable.org",
|
|
1029
|
+
"nospam.ze.tc",
|
|
1030
|
+
"trash-me.com",
|
|
1031
|
+
"dodgit.com",
|
|
1032
|
+
"mailmoat.com",
|
|
1033
|
+
"spamfree24.org",
|
|
1034
|
+
"incognitomail.org",
|
|
1035
|
+
"tempomail.fr",
|
|
1036
|
+
"ephemail.net",
|
|
1037
|
+
"hidemail.de",
|
|
1038
|
+
"spaml.de",
|
|
1039
|
+
"uggsrock.com",
|
|
1040
|
+
"binkmail.com",
|
|
1041
|
+
"suremail.info",
|
|
1042
|
+
"bugmenot.com"
|
|
1043
|
+
]);
|
|
1044
|
+
var DOMAIN_TYPOS = {
|
|
1045
|
+
"gmial.com": "gmail.com",
|
|
1046
|
+
"gmaill.com": "gmail.com",
|
|
1047
|
+
"gmai.com": "gmail.com",
|
|
1048
|
+
"gamil.com": "gmail.com",
|
|
1049
|
+
"gnail.com": "gmail.com",
|
|
1050
|
+
"gmal.com": "gmail.com",
|
|
1051
|
+
"gmil.com": "gmail.com",
|
|
1052
|
+
"gmail.co": "gmail.com",
|
|
1053
|
+
"gmail.cm": "gmail.com",
|
|
1054
|
+
"gmail.om": "gmail.com",
|
|
1055
|
+
"gmail.con": "gmail.com",
|
|
1056
|
+
"gmail.cim": "gmail.com",
|
|
1057
|
+
"gmail.comm": "gmail.com",
|
|
1058
|
+
"yahooo.com": "yahoo.com",
|
|
1059
|
+
"yaho.com": "yahoo.com",
|
|
1060
|
+
"yahoo.co": "yahoo.com",
|
|
1061
|
+
"yahoo.cm": "yahoo.com",
|
|
1062
|
+
"yahoo.con": "yahoo.com",
|
|
1063
|
+
"yahho.com": "yahoo.com",
|
|
1064
|
+
"hotmial.com": "hotmail.com",
|
|
1065
|
+
"hotmal.com": "hotmail.com",
|
|
1066
|
+
"hotmai.com": "hotmail.com",
|
|
1067
|
+
"hotmil.com": "hotmail.com",
|
|
1068
|
+
"hotmail.co": "hotmail.com",
|
|
1069
|
+
"hotmail.cm": "hotmail.com",
|
|
1070
|
+
"hotmail.con": "hotmail.com",
|
|
1071
|
+
"outlok.com": "outlook.com",
|
|
1072
|
+
"outloo.com": "outlook.com",
|
|
1073
|
+
"outlook.co": "outlook.com",
|
|
1074
|
+
"outlook.cm": "outlook.com",
|
|
1075
|
+
"protonmal.com": "protonmail.com",
|
|
1076
|
+
"protonmail.co": "protonmail.com",
|
|
1077
|
+
"icloud.co": "icloud.com",
|
|
1078
|
+
"icloud.cm": "icloud.com",
|
|
1079
|
+
"icoud.com": "icloud.com"
|
|
1080
|
+
};
|
|
1081
|
+
function invalidResult(reason, email) {
|
|
1082
|
+
return {
|
|
1083
|
+
valid: false,
|
|
1084
|
+
reason,
|
|
1085
|
+
suggestion: null,
|
|
1086
|
+
isFree: false,
|
|
1087
|
+
isDisposable: false,
|
|
1088
|
+
normalized: email
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
function validateEmail(email, options = {}) {
|
|
1092
|
+
const {
|
|
1093
|
+
checkDisposable = true,
|
|
1094
|
+
suggestTypoFix = true,
|
|
1095
|
+
blockedDomains = [],
|
|
1096
|
+
allowedDomains = []
|
|
1097
|
+
} = options;
|
|
1098
|
+
const normalized = email.trim().toLowerCase();
|
|
1099
|
+
if (!normalized || normalized.length > MAX_EMAIL_LENGTH) {
|
|
1100
|
+
return invalidResult("invalid_syntax", normalized);
|
|
1101
|
+
}
|
|
1102
|
+
const atIndex = normalized.lastIndexOf("@");
|
|
1103
|
+
if (atIndex === -1) {
|
|
1104
|
+
return invalidResult("invalid_syntax", normalized);
|
|
1105
|
+
}
|
|
1106
|
+
const localPart = normalized.slice(0, atIndex);
|
|
1107
|
+
const domain = normalized.slice(atIndex + 1);
|
|
1108
|
+
if (localPart.length === 0 || localPart.length > MAX_LOCAL_LENGTH) {
|
|
1109
|
+
return invalidResult("invalid_syntax", normalized);
|
|
1110
|
+
}
|
|
1111
|
+
if (domain.length === 0 || domain.length > MAX_DOMAIN_LENGTH) {
|
|
1112
|
+
return invalidResult("invalid_syntax", normalized);
|
|
1113
|
+
}
|
|
1114
|
+
if (localPart.includes("..")) {
|
|
1115
|
+
return invalidResult("invalid_syntax", normalized);
|
|
1116
|
+
}
|
|
1117
|
+
if (localPart.startsWith(".") || localPart.endsWith(".")) {
|
|
1118
|
+
return invalidResult("invalid_syntax", normalized);
|
|
1119
|
+
}
|
|
1120
|
+
if (!EMAIL_SYNTAX.test(normalized)) {
|
|
1121
|
+
return invalidResult("invalid_syntax", normalized);
|
|
1122
|
+
}
|
|
1123
|
+
const allowedSet = new Set(allowedDomains.map((d) => d.toLowerCase()));
|
|
1124
|
+
if (allowedSet.has(domain)) {
|
|
1125
|
+
return {
|
|
1126
|
+
valid: true,
|
|
1127
|
+
reason: "valid",
|
|
1128
|
+
suggestion: null,
|
|
1129
|
+
isFree: FREE_PROVIDERS.has(domain),
|
|
1130
|
+
isDisposable: false,
|
|
1131
|
+
normalized
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
const blockedSet = new Set(blockedDomains.map((d) => d.toLowerCase()));
|
|
1135
|
+
if (blockedSet.has(domain)) {
|
|
1136
|
+
return invalidResult("blocked", normalized);
|
|
1137
|
+
}
|
|
1138
|
+
const isDisposable = DISPOSABLE_DOMAINS.has(domain);
|
|
1139
|
+
if (checkDisposable && isDisposable) {
|
|
1140
|
+
return {
|
|
1141
|
+
valid: false,
|
|
1142
|
+
reason: "disposable",
|
|
1143
|
+
suggestion: null,
|
|
1144
|
+
isFree: false,
|
|
1145
|
+
isDisposable: true,
|
|
1146
|
+
normalized
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
const isFree = FREE_PROVIDERS.has(domain);
|
|
1150
|
+
if (suggestTypoFix && DOMAIN_TYPOS[domain]) {
|
|
1151
|
+
const corrected = `${localPart}@${DOMAIN_TYPOS[domain]}`;
|
|
1152
|
+
return {
|
|
1153
|
+
valid: true,
|
|
1154
|
+
reason: "typo",
|
|
1155
|
+
suggestion: corrected,
|
|
1156
|
+
isFree: FREE_PROVIDERS.has(DOMAIN_TYPOS[domain]),
|
|
1157
|
+
isDisposable: false,
|
|
1158
|
+
normalized
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
return {
|
|
1162
|
+
valid: true,
|
|
1163
|
+
reason: "valid",
|
|
1164
|
+
suggestion: null,
|
|
1165
|
+
isFree,
|
|
1166
|
+
isDisposable,
|
|
1167
|
+
normalized
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
async function verifyEmailMx(email) {
|
|
1171
|
+
if (!isValidEmailSyntax(email)) return false;
|
|
1172
|
+
const atIndex = email.lastIndexOf("@");
|
|
1173
|
+
const domain = email.slice(atIndex + 1).trim().toLowerCase();
|
|
1174
|
+
if (!domain) return false;
|
|
1175
|
+
try {
|
|
1176
|
+
const records = await promises.resolveMx(domain);
|
|
1177
|
+
return records.length > 0;
|
|
1178
|
+
} catch {
|
|
1179
|
+
return false;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
function isValidEmailSyntax(email) {
|
|
1183
|
+
const normalized = email.trim().toLowerCase();
|
|
1184
|
+
if (!normalized || normalized.length > MAX_EMAIL_LENGTH) return false;
|
|
1185
|
+
const atIndex = normalized.lastIndexOf("@");
|
|
1186
|
+
if (atIndex === -1) return false;
|
|
1187
|
+
const localPart = normalized.slice(0, atIndex);
|
|
1188
|
+
if (localPart.includes("..") || localPart.startsWith(".") || localPart.endsWith(".")) return false;
|
|
1189
|
+
return EMAIL_SYNTAX.test(normalized);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
export { createValidator, isDangerousExtension, isRedirectSafe, isUrlSafe, isValidEmailSyntax, sanitizeFilename, validate, validateEmail, validateFile, validateRedirect, validateUrl, verifyEmailMx };
|
|
698
1193
|
//# sourceMappingURL=index.mjs.map
|
|
699
1194
|
//# sourceMappingURL=index.mjs.map
|