update requirements, fix some bugs

This commit is contained in:
overcuriousity
2025-09-17 23:55:41 +02:00
parent 98e1b2280b
commit aae459446c
11 changed files with 660 additions and 202 deletions

View File

@@ -58,7 +58,10 @@ class DNSReconApp {
startScan: document.getElementById('start-scan'),
addToGraph: document.getElementById('add-to-graph'),
stopScan: document.getElementById('stop-scan'),
exportResults: document.getElementById('export-results'),
exportOptions: document.getElementById('export-options'),
exportModal: document.getElementById('export-modal'),
exportModalClose: document.getElementById('export-modal-close'),
exportGraphJson: document.getElementById('export-graph-json'),
configureSettings: document.getElementById('configure-settings'),
// Status elements
@@ -146,11 +149,24 @@ class DNSReconApp {
this.stopScan();
});
this.elements.exportResults.addEventListener('click', (e) => {
this.elements.exportOptions.addEventListener('click', (e) => {
e.preventDefault();
this.exportResults();
this.showExportModal();
});
if (this.elements.exportModalClose) {
this.elements.exportModalClose.addEventListener('click', () => this.hideExportModal());
}
if (this.elements.exportModal) {
this.elements.exportModal.addEventListener('click', (e) => {
if (e.target === this.elements.exportModal) this.hideExportModal();
});
}
if (this.elements.exportGraphJson) {
this.elements.exportGraphJson.addEventListener('click', () => this.exportGraphJson());
}
this.elements.configureSettings.addEventListener('click', () => this.showSettingsModal());
// Enter key support for target domain input
@@ -219,6 +235,7 @@ class DNSReconApp {
if (e.key === 'Escape') {
this.hideModal();
this.hideSettingsModal();
this.hideExportModal(); // Add this line
}
});
@@ -376,26 +393,96 @@ class DNSReconApp {
}
/**
* Export scan results
* Show Export modal
*/
async exportResults() {
showExportModal() {
if (this.elements.exportModal) {
this.elements.exportModal.style.display = 'block';
}
}
/**
* Hide Export modal
*/
hideExportModal() {
if (this.elements.exportModal) {
this.elements.exportModal.style.display = 'none';
}
}
/**
* Export graph data as JSON with proper error handling
*/
async exportGraphJson() {
try {
console.log('Exporting results...');
console.log('Exporting graph data as JSON...');
// Create a temporary link to trigger download
// Show loading state
if (this.elements.exportGraphJson) {
const originalContent = this.elements.exportGraphJson.innerHTML;
this.elements.exportGraphJson.innerHTML = '<span class="btn-icon">[...]</span><span>Exporting...</span>';
this.elements.exportGraphJson.disabled = true;
// Store original content for restoration
this.elements.exportGraphJson._originalContent = originalContent;
}
// Make API call to get export data
const response = await fetch('/api/export', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || `HTTP ${response.status}: ${response.statusText}`);
}
// Check if response is JSON or file download
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json') && !response.headers.get('content-disposition')) {
// This is an error response in JSON format
const errorData = await response.json();
throw new Error(errorData.error || 'Export failed');
}
// Get the filename from headers or create one
const contentDisposition = response.headers.get('content-disposition');
let filename = 'dnsrecon_export.json';
if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
if (filenameMatch) {
filename = filenameMatch[1].replace(/['"]/g, '');
}
}
// Create blob and download
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = '/api/export';
link.download = ''; // Let server determine filename
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
this.showSuccess('Results export initiated');
console.log('Results export initiated');
this.showSuccess('Graph data exported successfully');
this.hideExportModal();
} catch (error) {
console.error('Failed to export results:', error);
this.showError(`Failed to export results: ${error.message}`);
console.error('Failed to export graph data:', error);
this.showError(`Export failed: ${error.message}`);
} finally {
// Restore button state
if (this.elements.exportGraphJson) {
const originalContent = this.elements.exportGraphJson._originalContent ||
'<span class="btn-icon">[JSON]</span><span>Export Graph Data</span>';
this.elements.exportGraphJson.innerHTML = originalContent;
this.elements.exportGraphJson.disabled = false;
}
}
}
@@ -2116,14 +2203,7 @@ class DNSReconApp {
}
}
/**
* Validate target (domain or IP)
* @param {string} target - Target to validate
* @returns {boolean} True if valid
*/
isValidTarget(target) {
return this.isValidDomain(target) || this.isValidIp(target);
}
/**
* Validate domain name
@@ -2143,20 +2223,149 @@ class DNSReconApp {
}
/**
* Validate IP address
* Validate target (domain or IP) - UPDATED for IPv6 support
* @param {string} target - Target to validate
* @returns {boolean} True if valid
*/
isValidTarget(target) {
return this.isValidDomain(target) || this.isValidIp(target);
}
/**
* Validate IP address (IPv4 or IPv6)
* @param {string} ip - IP to validate
* @returns {boolean} True if valid
*/
isValidIp(ip) {
console.log(`Validating IP: "${ip}"`);
const parts = ip.split('.');
if (parts.length !== 4) {
if (!ip || typeof ip !== 'string') {
return false;
}
return parts.every(part => {
const num = parseInt(part, 10);
return !isNaN(num) && num >= 0 && num <= 255 && String(num) === part;
});
ip = ip.trim();
// IPv4 validation
if (this.isValidIPv4(ip)) {
return true;
}
// IPv6 validation
if (this.isValidIPv6(ip)) {
return true;
}
return false;
}
/**
* Validate IPv4 address
* @param {string} ip - IP to validate
* @returns {boolean} True if valid IPv4
*/
isValidIPv4(ip) {
const ipv4Pattern = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
const match = ip.match(ipv4Pattern);
if (!match) {
return false;
}
// Check each octet is between 0-255
for (let i = 1; i <= 4; i++) {
const octet = parseInt(match[i], 10);
if (octet < 0 || octet > 255) {
return false;
}
// Check for leading zeros (except for '0' itself)
if (match[i].length > 1 && match[i][0] === '0') {
return false;
}
}
return true;
}
/**
* Validate IPv6 address
* @param {string} ip - IP to validate
* @returns {boolean} True if valid IPv6
*/
isValidIPv6(ip) {
// Handle IPv6 with embedded IPv4 (e.g., ::ffff:192.168.1.1)
if (ip.includes('.')) {
const lastColon = ip.lastIndexOf(':');
if (lastColon !== -1) {
const ipv6Part = ip.substring(0, lastColon + 1);
const ipv4Part = ip.substring(lastColon + 1);
if (this.isValidIPv4(ipv4Part)) {
// Validate the IPv6 part (should end with ::)
return this.isValidIPv6Pure(ipv6Part + '0:0');
}
}
}
return this.isValidIPv6Pure(ip);
}
/**
* Validate pure IPv6 address (no embedded IPv4)
* @param {string} ip - IPv6 address to validate
* @returns {boolean} True if valid IPv6
*/
isValidIPv6Pure(ip) {
// Basic format check
if (!ip || ip.length < 2 || ip.length > 39) {
return false;
}
// Check for invalid characters
if (!/^[0-9a-fA-F:]+$/.test(ip)) {
return false;
}
// Handle double colon (::) for zero compression
const doubleColonCount = (ip.match(/::/g) || []).length;
if (doubleColonCount > 1) {
return false; // Only one :: allowed
}
let parts;
if (doubleColonCount === 1) {
// Expand the :: notation
const [before, after] = ip.split('::');
const beforeParts = before ? before.split(':') : [];
const afterParts = after ? after.split(':') : [];
// Calculate how many zero groups the :: represents
const totalParts = beforeParts.length + afterParts.length;
const zeroGroups = 8 - totalParts;
if (zeroGroups < 1) {
return false; // :: must represent at least one zero group
}
// Build the full address
parts = beforeParts.concat(Array(zeroGroups).fill('0')).concat(afterParts);
} else {
// No :: notation, split normally
parts = ip.split(':');
}
// IPv6 should have exactly 8 groups
if (parts.length !== 8) {
return false;
}
// Validate each group (1-4 hex digits)
for (const part of parts) {
if (!part || part.length > 4 || !/^[0-9a-fA-F]+$/.test(part)) {
return false;
}
}
return true;
}
/**