/ src / utils / sanitizer.js
sanitizer.js
  1  import sanitizeHtml from "sanitize-html";
  2  import config from "@/config.js";
  3  
  4  const SanitizerUtils = {
  5    sanitizeString: (str, maxLength = null) => {
  6      const limit = maxLength ?? config.limits.sanitize.maxStringLength;
  7  
  8      if (!str || typeof str !== "string") {
  9        return str;
 10      } else if (str.length > limit) {
 11        return "";
 12      }
 13  
 14      const normalized = str.normalize("NFC");
 15      const truncated = normalized.slice(0, limit);
 16      const sanitized = sanitizeHtml(truncated, config.sanitizer);
 17  
 18      if (sanitized.length === 0 && str.length > 0) {
 19        return "[Invalid content]";
 20      } else {
 21        return sanitized;
 22      }
 23    },
 24  
 25    sanitizeObject: (obj, depth = 0, maxDepth = null) => {
 26      const maxDepthLimit = maxDepth ?? config.limits.sanitize.maxDepth;
 27  
 28      if (depth > maxDepthLimit) {
 29        return null;
 30      } else if (typeof obj === "string") {
 31        return SanitizerUtils.sanitizeString(obj, config.limits.sanitize.maxStringLength);
 32      } else if (Array.isArray(obj)) {
 33        const limitedArray = obj.slice(0, config.limits.sanitize.maxArrayLength);
 34        return limitedArray.map(item => SanitizerUtils.sanitizeObject(item, depth + 1, maxDepthLimit));
 35      } else if (obj && typeof obj === "object") {
 36        const sanitized = Object.create(null);
 37        const keys = Object.keys(obj).slice(0, config.limits.sanitize.maxObjectKeys);
 38        for (const key of keys) {
 39          if (key === "__proto__" || key === "constructor" || key === "prototype") {
 40            continue;
 41          }
 42          const sanitizedKey = SanitizerUtils.sanitizeString(key, 100);
 43          if (sanitizedKey) {
 44            sanitized[sanitizedKey] = SanitizerUtils.sanitizeObject(obj[key], depth + 1, maxDepthLimit);
 45          }
 46        }
 47        return Object.freeze(sanitized);
 48      } else {
 49        return obj;
 50      }
 51    },
 52  };
 53  
 54  const JsonUtils = {
 55    safeParseJSON: (jsonString, maxSize = null) => {
 56      const limit = maxSize ?? config.limits.sanitize.maxJsonSize;
 57      const maxDepth = config.limits.sanitize.maxJsonDepth;
 58  
 59      if (typeof jsonString !== "string") {
 60        return null;
 61      } else if (jsonString.length > limit) {
 62        return null;
 63      }
 64  
 65      try {
 66        const parsed = JSON.parse(jsonString);
 67  
 68        const checkDepth = (obj, currentDepth = 0) => {
 69          if (currentDepth > maxDepth) {
 70            return false;
 71          } else if (Array.isArray(obj)) {
 72            return obj.every(item => checkDepth(item, currentDepth + 1));
 73          } else if (obj && typeof obj === "object") {
 74            return Object.values(obj).every(value => checkDepth(value, currentDepth + 1));
 75          } else {
 76            return true;
 77          }
 78        };
 79  
 80        if (!checkDepth(parsed)) {
 81          return null;
 82        } else {
 83          return SanitizerUtils.sanitizeObject(parsed);
 84        }
 85      } catch {
 86        return null;
 87      }
 88    },
 89  
 90    getCircularReplacer: () => {
 91      const seen = new WeakSet();
 92      return (key, value) => {
 93        if (typeof value === "object" && value !== null) {
 94          if (seen.has(value)) {
 95            return "[Circular]";
 96          } else {
 97            seen.add(value);
 98          }
 99        }
100        return value;
101      };
102    },
103  
104    safeStringify: (obj) => {
105      try {
106        const sanitized = SanitizerUtils.sanitizeObject(obj);
107        return JSON.stringify(sanitized, JsonUtils.getCircularReplacer());
108      } catch {
109        return "[Object could not be stringified]";
110      }
111    },
112  };
113  
114  export { SanitizerUtils, JsonUtils };
115  export const sanitizeString = SanitizerUtils.sanitizeString;
116  export const sanitizeObject = SanitizerUtils.sanitizeObject;
117  export const safeParseJSON = JsonUtils.safeParseJSON;
118  export const getCircularReplacer = JsonUtils.getCircularReplacer;
119  export const safeStringify = JsonUtils.safeStringify;