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 110 | 1x 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,
};
|