All files / src/utils fileNameSanitizer.js

100% Statements 109/109
100% Branches 32/32
100% Functions 3/3
100% Lines 109/109

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 1101x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 6x 6x 3x 3x 3x 3x 3x 3x 3x 3x 3x 6x 1x 1x 3x 6x 6x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 1x 1x 1x 1x 1x 1x 1x 1x 17x 17x 15x 15x 15x 17x 17x 17x 17x 17x 17x 17x 17x 17x 17x 17x 9x 9x 6x 6x 17x 2x 2x 2x 1x 1x 2x 5x 5x 5x 17x 3x 3x 3x 3x 17x 1x 1x 17x 1x 1x 1x 1x 1x 1x  
/**
 * Utilidades para sanitizar nombres de archivos
 * Asegura que los nombres sean seguros para el sistema de archivos
 */
 
/**
 * Sanitiza un texto para usarlo como nombre de archivo
 * @param {string} text - Texto a sanitizar
 * @param {number} maxLength - Longitud máxima (default: 50)
 * @returns {string} Texto sanitizado
 */
function sanitizeFileName(text, maxLength = 50) {
  if (!text) return "untitled";
 
  // Optimizado: Una pasada de regex combinada y trim eficiente
  let sanitized = String(text)
    .replace(/[<>:"\/\\|?*\s]+/g, "-") // Todos los caracteres prohibidos y espacios en un solo replace
    .replace(/[^\w-]/g, "") // Remover caracteres no alfanuméricos (excepto - y _)
    .replace(/-+/g, "-") // Múltiples guiones a uno solo
    .replace(/^-+|-+$/g, ""); // Guiones al inicio y final
 
  // Truncar a longitud máxima
  if (sanitized.length > maxLength) {
    sanitized = sanitized.substring(0, maxLength).replace(/-+$/, "");
  }
 
  return sanitized || "untitled";
}
 
/**
 * Genera un nombre de archivo para un work item
 * @param {number} workItemId - ID del work item
 * @param {string} title - Título del work item
 * @param {string} workItemType - Tipo de work item (opcional)
 * @returns {string} Nombre de archivo con formato: {WorkItemType} {ID} {título}.pdf
 */
function generateWorkItemFileName(
  workItemId,
  title,
  workItemType = "WorkItem",
) {
  // Limpiar título pero mantener espacios
  const cleanTitle = title
    .replace(/[<>:"\/\\|?*]/g, "") // Remover caracteres prohibidos
    .replace(/\s+/g, " ") // Normalizar espacios múltiples a uno solo
    .trim();
 
  // Truncar título si es muy largo (dejar espacio para tipo e ID)
  const maxTitleLength = 80;
  const truncatedTitle =
    cleanTitle.length > maxTitleLength
      ? cleanTitle.substring(0, maxTitleLength).trim()
      : cleanTitle;
 
  return `${workItemType} ${workItemId} ${truncatedTitle}.pdf`;
}
 
/**
 * Valida que un nombre de archivo sea seguro
 * Validación mejorada con whitelist de subdirectorios
 * @param {string} filename - Nombre de archivo a validar
 * @param {Array<string>} allowedSubdirs - Subdirectorios permitidos (opcional)
 * @returns {boolean} True si es válido
 */
function isValidFileName(filename, allowedSubdirs = []) {
  if (!filename || typeof filename !== "string") return false;
 
  // CRÍTICO: Verificar path traversal completo
  if (
    filename.includes("..") || // ../ o ..\
    filename.includes("./") || // ./
    filename.includes(".\\") || // .\
    filename.includes(":\\") || // :\ (Windows)
    filename.startsWith("/") || // Path absoluto Unix/Mac
    filename.startsWith("\\") || // UNC path Windows
    /^[a-zA-Z]:/.test(filename) || // Drive letter Windows (C:, D:, etc)
    filename === "." || // Directorio actual
    filename === ".." || // Directorio padre
    filename.includes("\0") // Null byte injection
  ) {
    return false;
  }
 
  // Validar subdirectorios contra whitelist si se provee
  if (allowedSubdirs.length > 0 && filename.includes("/")) {
    const parts = filename.split("/");
    const firstDir = parts[0];
    if (!allowedSubdirs.includes(firstDir)) {
      return false;
    }
  }
 
  // Verificar caracteres prohibidos
  const invalidChars = /[<>:"\/\\|?*]/;
  if (invalidChars.test(filename)) return false;
 
  // Verificar nombres reservados de Windows
  const reservedNames = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])$/i;
  const nameWithoutExt = filename.replace(/\.[^.]+$/, "");
  if (reservedNames.test(nameWithoutExt)) return false;
 
  return true;
}
 
module.exports = {
  sanitizeFileName,
  generateWorkItemFileName,
  isValidFileName,
};