import axios from 'axios'; import { v4 as uuidv4 } from 'uuid'; import { createHash } from 'crypto'; // Tentativa de importar o UAParser com fallback para evitar quebra caso não esteja instalado let UAParser; try { UAParser = require('ua-parser-js'); } catch (error) { console.warn('Aviso: UAParser não está disponível. Usando método alternativo para analisar User-Agent.'); // Implementação simples de fallback para quando a biblioteca não está disponível UAParser = class SimpleBrowserDetect { constructor(userAgent) { this.userAgent = userAgent; } getResult() { const isMobile = /mobile|android|iphone|ipod|ipad/i.test(this.userAgent); const isWindows = /windows/i.test(this.userAgent); const isMac = /macintosh|mac os/i.test(this.userAgent); const isLinux = /linux/i.test(this.userAgent); const chromeMatch = this.userAgent.match(/chrome\/(\d+)/i); const firefoxMatch = this.userAgent.match(/firefox\/(\d+)/i); const safariMatch = this.userAgent.match(/safari\/(\d+)/i); const edgeMatch = this.userAgent.match(/edge\/(\d+)|edg\/(\d+)/i); return { browser: { name: edgeMatch ? 'Edge' : chromeMatch ? 'Chrome' : firefoxMatch ? 'Firefox' : safariMatch ? 'Safari' : 'Desconhecido', version: (edgeMatch || chromeMatch || firefoxMatch || safariMatch || ['', '0'])[1], major: (edgeMatch || chromeMatch || firefoxMatch || safariMatch || ['', '0'])[1], }, engine: { name: 'Desconhecido', version: '', }, os: { name: isWindows ? 'Windows' : isMac ? 'Mac OS' : isLinux ? 'Linux' : 'Desconhecido', version: '', }, device: { vendor: '', model: '', type: isMobile ? 'mobile' : 'desktop', }, cpu: { architecture: '', } }; } }; } /** * Classe para coletar e validar metadados de arquivos conforme RFC 3227 * (Diretrizes para Coleta e Arquivamento de Evidências) */ export class MetadataCollector { /** * Coleta metadados avançados a partir dos dados fornecidos */ async collectMetadata(data: { userAgent: string; ipAddress: string; timestamp: Date; fileName: string; fileModified?: Date; fileSize: number; viewportWidth?: string; viewportHeight?: string; language?: string; referrer?: string; latitude?: number; longitude?: number; }) { try { // Gerar identificador único para a coleta const collectionId = uuidv4(); const collectionTimestamp = new Date(); // Parser para informações do navegador const parser = new UAParser(data.userAgent); const browserInfo = parser.getResult(); // Obter geolocalização a partir do IP se não fornecida diretamente let geoInfo = null; if (!data.latitude && !data.longitude) { try { geoInfo = await this.getGeolocationFromIP(data.ipAddress); } catch (error) { console.warn('Falha ao obter geolocalização:', error); } } // Construir o objeto de metadados return { // Identificação da coleta collectionId, collectionTimestamp: collectionTimestamp.toISOString(), // Dados do usuário userInfo: { ipAddress: data.ipAddress, userAgent: data.userAgent, language: data.language || 'não informado', browser: { name: browserInfo.browser.name || 'desconhecido', version: browserInfo.browser.version || 'desconhecido', major: browserInfo.browser.major || 'desconhecido', }, engine: { name: browserInfo.engine.name || 'desconhecido', version: browserInfo.engine.version || 'desconhecido', }, os: { name: browserInfo.os.name || 'desconhecido', version: browserInfo.os.version || 'desconhecido', }, device: { vendor: browserInfo.device.vendor || 'desconhecido', model: browserInfo.device.model || 'desconhecido', type: browserInfo.device.type || 'desconhecido', }, cpu: { architecture: browserInfo.cpu.architecture || 'desconhecido', }, viewportSize: data.viewportWidth && data.viewportHeight ? `${data.viewportWidth}x${data.viewportHeight}` : 'não informado', referrer: data.referrer || 'não informado', }, // Dados do arquivo fileInfo: { name: data.fileName, size: data.fileSize, sizeFormatted: this.formatFileSize(data.fileSize), modifiedDate: data.fileModified ? data.fileModified.toISOString() : 'não informado', }, // Dados de localização locationInfo: geoInfo || { latitude: data.latitude || null, longitude: data.longitude || null, city: 'não disponível', region: 'não disponível', country: 'não disponível', timezone: 'não disponível' }, // Timestamp oficial do sistema timestamp: data.timestamp.toISOString(), // Hash da coleta para garantir integridade integrityHash: this.generateIntegrityHash( data.ipAddress, data.userAgent, data.fileName, data.fileSize.toString(), collectionTimestamp.toISOString() ) }; } catch (error) { console.error('Erro ao coletar metadados:', error); // Retornar metadados simplificados se algo der errado return { timestamp: new Date().toISOString(), userAgent: data.userAgent, ipAddress: data.ipAddress, fileName: data.fileName, fileSize: data.fileSize, error: 'Falha na coleta completa de metadados' }; } } /** * Formata o tamanho do arquivo para exibição */ private formatFileSize(bytes: number): string { if (bytes < 1024) return bytes + ' bytes'; else if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB'; else if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(2) + ' MB'; else return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB'; } /** * Gera um hash de integridade para os metadados coletados */ private generateIntegrityHash(...inputs: string[]): string { try { return createHash('sha256') .update(inputs.join('|')) .digest('hex'); } catch (error) { console.error('Erro ao gerar hash de integridade:', error); return 'hash-error-' + Date.now(); } } /** * Obtém informações de geolocalização a partir do endereço IP */ private async getGeolocationFromIP(ipAddress: string) { try { // Apenas para endereços IP públicos, ignorar IPs locais if (ipAddress === '127.0.0.1' || ipAddress === '::1' || ipAddress.startsWith('192.168.') || ipAddress.startsWith('10.')) { return null; } // Usar API pública de geolocalização const response = await axios.get(`https://ipinfo.io/${ipAddress}/json`); const data = response.data; // Extrair coordenadas (formato: "lat,long") let latitude = null; let longitude = null; if (data.loc) { const [lat, lng] = data.loc.split(','); latitude = parseFloat(lat); longitude = parseFloat(lng); } return { latitude, longitude, city: data.city || 'não disponível', region: data.region || 'não disponível', country: data.country || 'não disponível', timezone: data.timezone || 'não disponível', provider: 'ipinfo.io' }; } catch (error) { console.error('Erro ao obter geolocalização:', error); return null; } } }