@arcis/node 1.0.0 → 1.1.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.
@@ -1,3 +1,3 @@
1
- export { c as createValidator, i as isDangerousExtension, s as sanitizeFilename, v as validate, b as validateFile } from '../index-nAgXexwD.mjs';
1
+ export { g as createValidator, i as isDangerousExtension, h as isRedirectSafe, j as isUrlSafe, k as isValidEmailSyntax, s as sanitizeFilename, v as validate, l as validateEmail, m as validateFile, n as validateRedirect, o as validateUrl, p as verifyEmailMx } from '../index-CslcoZUN.mjs';
2
2
  import 'express';
3
3
  import '../types-BOdL3ZWo.mjs';
@@ -1,3 +1,3 @@
1
- export { c as createValidator, i as isDangerousExtension, s as sanitizeFilename, v as validate, b as validateFile } from '../index-BgHPM7LC.js';
1
+ export { g as createValidator, i as isDangerousExtension, h as isRedirectSafe, j as isUrlSafe, k as isValidEmailSyntax, s as sanitizeFilename, v as validate, l as validateEmail, m as validateFile, n as validateRedirect, o as validateUrl, p as verifyEmailMx } from '../index-iCOw8Fcg.js';
2
2
  import 'express';
3
3
  import '../types-BOdL3ZWo.js';
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ var dns = require('dns');
4
+
3
5
  // src/core/constants.ts
4
6
  var INPUT = {
5
7
  /** Default maximum input size (1MB) */
@@ -696,10 +698,408 @@ function isDangerousExtension(filename) {
696
698
  return ext !== "" && DANGEROUS_EXTENSIONS.has(ext);
697
699
  }
698
700
 
701
+ // src/validation/url.ts
702
+ function validateUrl(url, options = {}) {
703
+ const {
704
+ allowedProtocols = ["http:", "https:"],
705
+ blockedHosts = [],
706
+ allowedHosts = [],
707
+ allowLocalhost = false,
708
+ allowPrivate = false
709
+ } = options;
710
+ if (typeof url !== "string" || url.trim() === "") {
711
+ return { safe: false, reason: "invalid URL: empty or not a string" };
712
+ }
713
+ let parsed;
714
+ try {
715
+ parsed = new URL(url);
716
+ } catch {
717
+ return { safe: false, reason: "invalid URL: failed to parse" };
718
+ }
719
+ if (!allowedProtocols.includes(parsed.protocol)) {
720
+ return { safe: false, reason: `disallowed protocol: ${parsed.protocol}` };
721
+ }
722
+ if (parsed.username || parsed.password) {
723
+ return { safe: false, reason: "URL contains credentials" };
724
+ }
725
+ const hostname = parsed.hostname.toLowerCase();
726
+ if (allowedHosts.some((h) => hostname === h.toLowerCase())) {
727
+ return { safe: true };
728
+ }
729
+ if (blockedHosts.some((h) => hostname === h.toLowerCase())) {
730
+ return { safe: false, reason: `blocked host: ${hostname}` };
731
+ }
732
+ if (!allowLocalhost) {
733
+ if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "::1" || hostname === "0.0.0.0" || hostname.endsWith(".localhost")) {
734
+ return { safe: false, reason: "loopback address" };
735
+ }
736
+ if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname)) {
737
+ return { safe: false, reason: "loopback address" };
738
+ }
739
+ }
740
+ if (!allowPrivate) {
741
+ const privateCheck = checkPrivateIp(hostname);
742
+ if (privateCheck) {
743
+ return { safe: false, reason: privateCheck };
744
+ }
745
+ }
746
+ return { safe: true };
747
+ }
748
+ function isUrlSafe(url, options = {}) {
749
+ return validateUrl(url, options).safe;
750
+ }
751
+ function checkPrivateIp(hostname) {
752
+ if (/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname)) {
753
+ return "private address (10.0.0.0/8)";
754
+ }
755
+ const match172 = hostname.match(/^172\.(\d{1,3})\.\d{1,3}\.\d{1,3}$/);
756
+ if (match172) {
757
+ const second = parseInt(match172[1], 10);
758
+ if (second >= 16 && second <= 31) {
759
+ return "private address (172.16.0.0/12)";
760
+ }
761
+ }
762
+ if (/^192\.168\.\d{1,3}\.\d{1,3}$/.test(hostname)) {
763
+ return "private address (192.168.0.0/16)";
764
+ }
765
+ if (/^169\.254\.\d{1,3}\.\d{1,3}$/.test(hostname)) {
766
+ return "link-local address (169.254.0.0/16)";
767
+ }
768
+ if (/^0\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname)) {
769
+ return "current network address (0.0.0.0/8)";
770
+ }
771
+ if (hostname === "metadata.google.internal" || hostname === "metadata.internal") {
772
+ return "cloud metadata endpoint";
773
+ }
774
+ const ipv6 = hostname.replace(/^\[|\]$/g, "");
775
+ if (ipv6 === "::1" || ipv6 === "::" || ipv6.startsWith("fc") || ipv6.startsWith("fd") || ipv6.startsWith("fe80")) {
776
+ return "private IPv6 address";
777
+ }
778
+ return null;
779
+ }
780
+
781
+ // src/validation/redirect.ts
782
+ var DANGEROUS_PROTOCOLS = /^(javascript|data|vbscript|blob):/i;
783
+ var CONTROL_CHARS = /[\t\n\r]/g;
784
+ function validateRedirect(url, options = {}) {
785
+ const {
786
+ allowedHosts = [],
787
+ allowProtocolRelative = false,
788
+ allowedProtocols = ["http:", "https:"]
789
+ } = options;
790
+ if (typeof url !== "string" || url.trim() === "") {
791
+ return { safe: false, reason: "invalid redirect: empty or not a string" };
792
+ }
793
+ const cleaned = url.replace(CONTROL_CHARS, "");
794
+ if (DANGEROUS_PROTOCOLS.test(cleaned)) {
795
+ const proto = cleaned.match(DANGEROUS_PROTOCOLS);
796
+ return { safe: false, reason: `dangerous protocol: ${proto[0]}` };
797
+ }
798
+ if (cleaned.startsWith("\\")) {
799
+ return { safe: false, reason: "backslash-prefixed URL (browser treats as protocol-relative)" };
800
+ }
801
+ if (cleaned.startsWith("//")) {
802
+ if (!allowProtocolRelative) {
803
+ const host2 = extractHost(cleaned);
804
+ if (host2 && allowedHosts.some((h) => host2 === h.toLowerCase())) {
805
+ return { safe: true };
806
+ }
807
+ return { safe: false, reason: "protocol-relative URL not in allowed hosts" };
808
+ }
809
+ const host = extractHost(cleaned);
810
+ if (host && allowedHosts.length > 0 && !allowedHosts.some((h) => host === h.toLowerCase())) {
811
+ return { safe: false, reason: "protocol-relative URL not in allowed hosts" };
812
+ }
813
+ return { safe: true };
814
+ }
815
+ let parsed;
816
+ try {
817
+ parsed = new URL(cleaned);
818
+ } catch {
819
+ return { safe: true };
820
+ }
821
+ if (!allowedProtocols.includes(parsed.protocol)) {
822
+ return { safe: false, reason: `disallowed protocol: ${parsed.protocol}` };
823
+ }
824
+ const hostname = parsed.hostname.toLowerCase();
825
+ if (allowedHosts.length === 0) {
826
+ return { safe: false, reason: "absolute URL not in allowed hosts" };
827
+ }
828
+ if (!allowedHosts.some((h) => hostname === h.toLowerCase())) {
829
+ return { safe: false, reason: `host not allowed: ${hostname}` };
830
+ }
831
+ return { safe: true };
832
+ }
833
+ function isRedirectSafe(url, options = {}) {
834
+ return validateRedirect(url, options).safe;
835
+ }
836
+ function extractHost(url) {
837
+ const match = url.match(/^\/\/([^/:?#]+)/);
838
+ return match ? match[1].toLowerCase() : null;
839
+ }
840
+ var MAX_EMAIL_LENGTH = 254;
841
+ var MAX_LOCAL_LENGTH = 64;
842
+ var MAX_DOMAIN_LENGTH = 255;
843
+ 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,}$/;
844
+ var FREE_PROVIDERS = /* @__PURE__ */ new Set([
845
+ "gmail.com",
846
+ "yahoo.com",
847
+ "hotmail.com",
848
+ "outlook.com",
849
+ "aol.com",
850
+ "protonmail.com",
851
+ "proton.me",
852
+ "icloud.com",
853
+ "mail.com",
854
+ "zoho.com",
855
+ "yandex.com",
856
+ "gmx.com",
857
+ "gmx.net",
858
+ "live.com",
859
+ "msn.com",
860
+ "me.com",
861
+ "mac.com",
862
+ "fastmail.com",
863
+ "tutanota.com",
864
+ "hey.com"
865
+ ]);
866
+ var DISPOSABLE_DOMAINS = /* @__PURE__ */ new Set([
867
+ // Popular disposable services
868
+ "guerrillamail.com",
869
+ "guerrillamail.net",
870
+ "guerrillamail.org",
871
+ "tempmail.com",
872
+ "temp-mail.org",
873
+ "temp-mail.io",
874
+ "throwaway.email",
875
+ "throwaway.com",
876
+ "mailinator.com",
877
+ "mailinator.net",
878
+ "yopmail.com",
879
+ "yopmail.fr",
880
+ "yopmail.net",
881
+ "sharklasers.com",
882
+ "grr.la",
883
+ "guerrillamail.info",
884
+ "guerrillamail.biz",
885
+ "guerrillamail.de",
886
+ "trashmail.com",
887
+ "trashmail.me",
888
+ "trashmail.net",
889
+ "dispostable.com",
890
+ "maildrop.cc",
891
+ "mailnesia.com",
892
+ "tempail.com",
893
+ "mohmal.com",
894
+ "getnada.com",
895
+ "emailondeck.com",
896
+ "discard.email",
897
+ "fakeinbox.com",
898
+ "mailcatch.com",
899
+ "mintemail.com",
900
+ "tempr.email",
901
+ "tempinbox.com",
902
+ "burnermail.io",
903
+ "mailsac.com",
904
+ "harakirimail.com",
905
+ "tempmailo.com",
906
+ "emailfake.com",
907
+ "crazymailing.com",
908
+ "armyspy.com",
909
+ "dayrep.com",
910
+ "einrot.com",
911
+ "fleckens.hu",
912
+ "gustr.com",
913
+ "jourrapide.com",
914
+ "rhyta.com",
915
+ "superrito.com",
916
+ "teleworm.us",
917
+ "10minutemail.com",
918
+ "10minutemail.net",
919
+ "minutemail.com",
920
+ "tempsky.com",
921
+ "spamgourmet.com",
922
+ "mytrashmail.com",
923
+ "mailexpire.com",
924
+ "safetymail.info",
925
+ "filzmail.com",
926
+ "trashymail.com",
927
+ "sharkmail.com",
928
+ "jetable.org",
929
+ "nospam.ze.tc",
930
+ "trash-me.com",
931
+ "dodgit.com",
932
+ "mailmoat.com",
933
+ "spamfree24.org",
934
+ "incognitomail.org",
935
+ "tempomail.fr",
936
+ "ephemail.net",
937
+ "hidemail.de",
938
+ "spaml.de",
939
+ "uggsrock.com",
940
+ "binkmail.com",
941
+ "suremail.info",
942
+ "bugmenot.com"
943
+ ]);
944
+ var DOMAIN_TYPOS = {
945
+ "gmial.com": "gmail.com",
946
+ "gmaill.com": "gmail.com",
947
+ "gmai.com": "gmail.com",
948
+ "gamil.com": "gmail.com",
949
+ "gnail.com": "gmail.com",
950
+ "gmal.com": "gmail.com",
951
+ "gmil.com": "gmail.com",
952
+ "gmail.co": "gmail.com",
953
+ "gmail.cm": "gmail.com",
954
+ "gmail.om": "gmail.com",
955
+ "gmail.con": "gmail.com",
956
+ "gmail.cim": "gmail.com",
957
+ "gmail.comm": "gmail.com",
958
+ "yahooo.com": "yahoo.com",
959
+ "yaho.com": "yahoo.com",
960
+ "yahoo.co": "yahoo.com",
961
+ "yahoo.cm": "yahoo.com",
962
+ "yahoo.con": "yahoo.com",
963
+ "yahho.com": "yahoo.com",
964
+ "hotmial.com": "hotmail.com",
965
+ "hotmal.com": "hotmail.com",
966
+ "hotmai.com": "hotmail.com",
967
+ "hotmil.com": "hotmail.com",
968
+ "hotmail.co": "hotmail.com",
969
+ "hotmail.cm": "hotmail.com",
970
+ "hotmail.con": "hotmail.com",
971
+ "outlok.com": "outlook.com",
972
+ "outloo.com": "outlook.com",
973
+ "outlook.co": "outlook.com",
974
+ "outlook.cm": "outlook.com",
975
+ "protonmal.com": "protonmail.com",
976
+ "protonmail.co": "protonmail.com",
977
+ "icloud.co": "icloud.com",
978
+ "icloud.cm": "icloud.com",
979
+ "icoud.com": "icloud.com"
980
+ };
981
+ function invalidResult(reason, email) {
982
+ return {
983
+ valid: false,
984
+ reason,
985
+ suggestion: null,
986
+ isFree: false,
987
+ isDisposable: false,
988
+ normalized: email
989
+ };
990
+ }
991
+ function validateEmail(email, options = {}) {
992
+ const {
993
+ checkDisposable = true,
994
+ suggestTypoFix = true,
995
+ blockedDomains = [],
996
+ allowedDomains = []
997
+ } = options;
998
+ const normalized = email.trim().toLowerCase();
999
+ if (!normalized || normalized.length > MAX_EMAIL_LENGTH) {
1000
+ return invalidResult("invalid_syntax", normalized);
1001
+ }
1002
+ const atIndex = normalized.lastIndexOf("@");
1003
+ if (atIndex === -1) {
1004
+ return invalidResult("invalid_syntax", normalized);
1005
+ }
1006
+ const localPart = normalized.slice(0, atIndex);
1007
+ const domain = normalized.slice(atIndex + 1);
1008
+ if (localPart.length === 0 || localPart.length > MAX_LOCAL_LENGTH) {
1009
+ return invalidResult("invalid_syntax", normalized);
1010
+ }
1011
+ if (domain.length === 0 || domain.length > MAX_DOMAIN_LENGTH) {
1012
+ return invalidResult("invalid_syntax", normalized);
1013
+ }
1014
+ if (localPart.includes("..")) {
1015
+ return invalidResult("invalid_syntax", normalized);
1016
+ }
1017
+ if (localPart.startsWith(".") || localPart.endsWith(".")) {
1018
+ return invalidResult("invalid_syntax", normalized);
1019
+ }
1020
+ if (!EMAIL_SYNTAX.test(normalized)) {
1021
+ return invalidResult("invalid_syntax", normalized);
1022
+ }
1023
+ const allowedSet = new Set(allowedDomains.map((d) => d.toLowerCase()));
1024
+ if (allowedSet.has(domain)) {
1025
+ return {
1026
+ valid: true,
1027
+ reason: "valid",
1028
+ suggestion: null,
1029
+ isFree: FREE_PROVIDERS.has(domain),
1030
+ isDisposable: false,
1031
+ normalized
1032
+ };
1033
+ }
1034
+ const blockedSet = new Set(blockedDomains.map((d) => d.toLowerCase()));
1035
+ if (blockedSet.has(domain)) {
1036
+ return invalidResult("blocked", normalized);
1037
+ }
1038
+ const isDisposable = DISPOSABLE_DOMAINS.has(domain);
1039
+ if (checkDisposable && isDisposable) {
1040
+ return {
1041
+ valid: false,
1042
+ reason: "disposable",
1043
+ suggestion: null,
1044
+ isFree: false,
1045
+ isDisposable: true,
1046
+ normalized
1047
+ };
1048
+ }
1049
+ const isFree = FREE_PROVIDERS.has(domain);
1050
+ if (suggestTypoFix && DOMAIN_TYPOS[domain]) {
1051
+ const corrected = `${localPart}@${DOMAIN_TYPOS[domain]}`;
1052
+ return {
1053
+ valid: true,
1054
+ reason: "typo",
1055
+ suggestion: corrected,
1056
+ isFree: FREE_PROVIDERS.has(DOMAIN_TYPOS[domain]),
1057
+ isDisposable: false,
1058
+ normalized
1059
+ };
1060
+ }
1061
+ return {
1062
+ valid: true,
1063
+ reason: "valid",
1064
+ suggestion: null,
1065
+ isFree,
1066
+ isDisposable,
1067
+ normalized
1068
+ };
1069
+ }
1070
+ async function verifyEmailMx(email) {
1071
+ if (!isValidEmailSyntax(email)) return false;
1072
+ const atIndex = email.lastIndexOf("@");
1073
+ const domain = email.slice(atIndex + 1).trim().toLowerCase();
1074
+ if (!domain) return false;
1075
+ try {
1076
+ const records = await dns.promises.resolveMx(domain);
1077
+ return records.length > 0;
1078
+ } catch {
1079
+ return false;
1080
+ }
1081
+ }
1082
+ function isValidEmailSyntax(email) {
1083
+ const normalized = email.trim().toLowerCase();
1084
+ if (!normalized || normalized.length > MAX_EMAIL_LENGTH) return false;
1085
+ const atIndex = normalized.lastIndexOf("@");
1086
+ if (atIndex === -1) return false;
1087
+ const localPart = normalized.slice(0, atIndex);
1088
+ if (localPart.includes("..") || localPart.startsWith(".") || localPart.endsWith(".")) return false;
1089
+ return EMAIL_SYNTAX.test(normalized);
1090
+ }
1091
+
699
1092
  exports.createValidator = createValidator;
700
1093
  exports.isDangerousExtension = isDangerousExtension;
1094
+ exports.isRedirectSafe = isRedirectSafe;
1095
+ exports.isUrlSafe = isUrlSafe;
1096
+ exports.isValidEmailSyntax = isValidEmailSyntax;
701
1097
  exports.sanitizeFilename = sanitizeFilename;
702
1098
  exports.validate = validate;
1099
+ exports.validateEmail = validateEmail;
703
1100
  exports.validateFile = validateFile;
1101
+ exports.validateRedirect = validateRedirect;
1102
+ exports.validateUrl = validateUrl;
1103
+ exports.verifyEmailMx = verifyEmailMx;
704
1104
  //# sourceMappingURL=index.js.map
705
1105
  //# sourceMappingURL=index.js.map