update requirements, fix some bugs
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user