update layout, pdf export for headeranalyzer needs fixing layout

This commit is contained in:
nahakubuilde
2025-07-17 08:33:04 +01:00
parent 518cc2e275
commit 4e4e4f735e
13 changed files with 2377 additions and 1512 deletions

29
web/base.html Normal file
View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{block "title" .}}HeaderAnalyzer{{end}}</title>
<link rel="stylesheet" href="/static/style.css">
<link rel="icon" href="/favicon.ico" type="image/x-icon">
{{block "head" .}}{{end}}
</head>
<body>
<nav>
<a href="/" {{if eq .CurrentPage "home"}}class="active"{{end}}>Analyze New Header</a>
<a href="/dns" {{if eq .CurrentPage "dns"}}class="active"{{end}}>DNS Tools</a>
<a href="/password" {{if eq .CurrentPage "password"}}class="active"{{end}}>Password Generator</a>
</nav>
<main>
{{block "content" .}}
<div class="container">
<h1>HeaderAnalyzer</h1>
<p>Welcome to HeaderAnalyzer - your tool for email header analysis, DNS tools, and password generation.</p>
</div>
{{end}}
</main>
{{block "scripts" .}}{{end}}
</body>
</html>

View File

@@ -1,26 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<title>DNS Tools</title>
<link rel="stylesheet" href="/static/style.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
.dns-tools-container { max-width: 900px; margin: 0 auto; }
.dns-query-form { display: flex; gap: 10px; margin-bottom: 10px; }
.dns-query-form input, .dns-query-form select { padding: 7px; border-radius: 4px; border: 1px solid #444; background: #232323; color: #e0e0e0; }
.dns-query-form button { padding: 7px 16px; }
.dns-results { margin-top: 10px; }
.dns-result-block { background: #232323; border-radius: 6px; margin-bottom: 12px; padding: 12px; box-shadow: 0 2px 4px rgba(0,0,0,0.12); }
.dns-result-block pre { white-space: pre-wrap; word-break: break-word; font-size: 1em; }
.save-btns { margin-bottom: 10px; }
</style>
</head>
<body>
<nav>
<a href="/">Analyze New Header</a>
<a href="/dns">DNS Tools</a>
<a href="/password">Password Generator</a>
</nav>
{{template "base.html" .}}
{{define "title"}}DNS Tools - HeaderAnalyzer{{end}}
{{define "head"}}
<style>
.dns-tools-container { max-width: 900px; margin: 0 auto; }
.dns-query-form { display: flex; gap: 10px; margin-bottom: 10px; }
.dns-query-form input, .dns-query-form select { padding: 7px; border-radius: 4px; border: 1px solid #444; background: #232323; color: #e0e0e0; }
.dns-query-form button { padding: 7px 16px; }
.dns-results { margin-top: 10px; }
.dns-result-block { background: #232323; border-radius: 6px; margin-bottom: 12px; padding: 12px; box-shadow: 0 2px 4px rgba(0,0,0,0.12); }
.dns-result-block pre { white-space: pre-wrap; word-break: break-word; font-size: 1em; }
.save-btns { margin-bottom: 10px; }
</style>
{{end}}
{{define "content"}}
<div class="dns-tools-container">
<h1>DNS Tools</h1>
<form class="dns-query-form" id="dnsForm" onsubmit="return false;">
@@ -48,45 +43,102 @@
</div>
<div class="dns-results" id="dnsResults"></div>
</div>
{{end}}
{{define "scripts"}}
<script>
let results = [];
function renderResults() {
const container = document.getElementById('dnsResults');
container.innerHTML = '';
results.forEach(r => {
const block = document.createElement('div');
block.className = 'dns-result-block';
block.innerHTML = `<b>${r.type} for ${r.query}</b><br><pre>${r.result}</pre>`;
container.appendChild(block);
});
}
document.getElementById('dnsForm').addEventListener('submit', async function() {
document.getElementById('dnsForm').addEventListener('submit', function() {
const query = document.getElementById('dnsInput').value.trim();
const type = document.getElementById('dnsType').value;
const server = document.getElementById('dnsServer').value.trim();
if (!query) return;
let url = `/api/dns?query=${encodeURIComponent(query)}&type=${encodeURIComponent(type)}`;
const dnsServer = document.getElementById('dnsServer').value.trim();
if (dnsServer) url += `&server=${encodeURIComponent(dnsServer)}`;
let res = await fetch(url);
let data = await res.text();
results.unshift({query, type, result: data});
renderResults();
if (server) {
url += `&server=${encodeURIComponent(server)}`;
}
// Add selector field for DKIM queries
if (type === 'DKIM' && !query.includes(':')) {
const selector = prompt('Enter DKIM selector (e.g., "selector1", "default"):');
if (selector) {
url = `/api/dns?query=${encodeURIComponent(query + ':' + selector)}&type=${encodeURIComponent(type)}`;
if (server) {
url += `&server=${encodeURIComponent(server)}`;
}
}
}
fetch(url)
.then(response => response.text())
.then(data => {
const timestamp = new Date().toLocaleString();
const result = {
timestamp: timestamp,
query: query,
type: type,
server: server || 'Default',
result: data
};
results.push(result);
const resultDiv = document.createElement('div');
resultDiv.className = 'dns-result-block';
resultDiv.innerHTML = `
<h3>${type} query for ${query}</h3>
<p><small>Time: ${timestamp} | Server: ${server || 'Default'}</small></p>
<pre>${data}</pre>
`;
document.getElementById('dnsResults').appendChild(resultDiv);
})
.catch(error => {
console.error('Error:', error);
const resultDiv = document.createElement('div');
resultDiv.className = 'dns-result-block';
resultDiv.innerHTML = `
<h3>Error querying ${query}</h3>
<pre>Error: ${error.message}</pre>
`;
document.getElementById('dnsResults').appendChild(resultDiv);
});
});
function saveResults(format) {
let content = '';
if (format === 'csv') {
content = 'Type,Query,Result\n' + results.map(r => `${r.type},${r.query},"${r.result.replace(/"/g, '""')}"`).join('\n');
} else {
content = results.map(r => `${r.type} for ${r.query}\n${r.result}\n`).join('\n');
if (results.length === 0) {
alert('No results to save');
return;
}
const blob = new Blob([content], {type: 'text/plain'});
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `dnswhois_results.${format}`;
link.click();
let content = '';
let filename = '';
if (format === 'csv') {
content = 'Timestamp,Query,Type,Server,Result\n';
results.forEach(r => {
const escapedResult = '"' + r.result.replace(/"/g, '""') + '"';
content += `"${r.timestamp}","${r.query}","${r.type}","${r.server}",${escapedResult}\n`;
});
filename = 'dns-results.csv';
} else if (format === 'txt') {
results.forEach(r => {
content += `=== ${r.type} query for ${r.query} ===\n`;
content += `Time: ${r.timestamp}\n`;
content += `Server: ${r.server}\n`;
content += `Result:\n${r.result}\n\n`;
});
filename = 'dns-results.txt';
}
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
</script>
</body>
</html>
{{end}}

710
web/headeranalyzer.html Normal file
View File

@@ -0,0 +1,710 @@
{{template "base.html" .}}
{{define "title"}}Email Header Analyzer{{end}}
{{define "head"}}
<script src="https://unpkg.com/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
<script src="https://unpkg.com/jspdf@2.5.1/dist/jspdf.umd.min.js"></script>
<script src="https://unpkg.com/html2pdf.js@0.10.1/dist/html2pdf.bundle.min.js"></script>
{{end}}
{{define "content"}}
<div class="container">
<h1>Email Header Analyzer</h1>
{{if not .From}}
<form method="POST">
<textarea name="headers" placeholder="Paste email headers here..."></textarea>
<br>
<button type="submit">Analyze Headers</button>
</form>
{{end}}
{{if .From}}
<div id="report" class="container">
<div class="section" style="display: flex; align-items: flex-start; justify-content: space-between; gap: 30px;">
<div style="flex: 1 1 0; min-width: 0;">
<h2>Sender Identification</h2>
<div class="grid">
<div>
<p><b>Envelope Sender (Return-Path):</b> {{.EnvelopeSender}}</p>
<p><b>From Domain:</b> {{.FromDomain}}</p>
<p><b>Sending Server:</b> {{.SendingServer}}</p>
</div>
<div class="score-indicators">
<span class="status {{if .SPFPass}}good{{else}}error{{end}}" title="SPF">SPF {{if .SPFPass}}✓{{else}}✗{{end}}</span>
<span class="status {{if .DMARCPass}}good{{else}}error{{end}}" title="DMARC">DMARC {{if .DMARCPass}}✓{{else}}✗{{end}}</span>
<span class="status {{if .DKIMPass}}good{{else}}error{{end}}" title="DKIM">DKIM {{if .DKIMPass}}✓{{else}}✗{{end}}</span>
<span class="status {{if .Encrypted}}good{{else}}error{{end}}" title="Encrypted">Encrypted {{if .Encrypted}}✓{{else}}✗{{end}}</span>
{{if .Blacklists}}
<span class="status error" title="{{range .Blacklists}}{{.}}, {{end}}">Blacklisted {{len .Blacklists}} times</span>
{{else}}
<span class="status good">Not listed on major blacklists</span>
{{end}}
</div>
</div>
{{if .SenderRep}}
<div>
<b><span>Sender Reputation: </span></b><div class="status {{if contains .SenderRep "EXCELLENT"}}good{{else if contains .SenderRep "GOOD"}}good{{else if contains .SenderRep "FAIR"}}warning{{else}}error{{end}}">
{{.SenderRep}}
</div>
</div>
{{end}}
<div class="explanation">
<small>
<b>Envelope Sender</b> is the real sender used for delivery (can differ from From).<br>
<b>From Domain</b> is the domain shown to the recipient.<br>
<b>Sending Server</b> is the host or IP that actually sent the message (from first Received header).<br>
If these differ, the message may be sent on behalf of another user or via a third-party service.
</small>
</div>
</div>
</div>
<details id="all-headers" class="section" style="margin-top:10px;">
<summary><b style="font-size: 1.5em;">All Email Headers Table</b></summary>
<div style="margin-bottom:10px;">
<input type="text" id="headerSearch" placeholder="Search headers..." style="width: 100%; max-width: 350px; padding: 5px; border-radius: 4px; border: 1px solid #444; background: #232323; color: #e0e0e0;">
</div>
<div style="overflow-x:auto;">
<table id="headersTable" style="width:100%; border-collapse:collapse; border:1px solid #444;">
<thead>
<tr>
<th style="text-align:left; padding:4px 8px; border:1px solid #444; width: 180px; background:#232323;">Header Name</th>
<th style="text-align:left; padding:4px 8px; border:1px solid #444; background:#232323;">Value</th>
</tr>
</thead>
<tbody>
{{range $k, $v := .AllHeaders}}
<tr>
<td style="vertical-align:top; padding:4px 8px; border:1px solid #444; word-break:break-word;">{{$k}}</td>
<td style="vertical-align:top; padding:4px 8px; border:1px solid #444; white-space:pre-wrap; word-break:break-word;">{{$v}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</details>
<div class="section">
<h2>Basic Information</h2>
<div class="grid">
<div>
<p><b>From:</b> {{.From}}</p>
<p><b>To:</b> {{.To}}</p>
<p><b>Subject:</b> {{.Subject}}</p>
<p><b>Date:</b> {{.Date}}</p>
{{if .ReplyTo}}<p><b>Reply-To:</b> {{.ReplyTo}}</p>{{end}}
</div>
<div>
<p><b>Message-ID:</b> {{.MessageID}}</p>
<p><b>Priority:</b> {{.Priority}}</p>
<p><b>Content Type:</b> {{.ContentType}}</p>
<p><b>Encoding:</b> {{.Encoding}}</p>
</div>
</div>
</div>
<div class="section">
<h2>Mail Flow</h2>
<ul class="mail-flow">
{{range .Received}}<li>{{.}}</li>{{end}}
</ul>
</div>
{{if ne .DeliveryDelay "Insufficient data for delay analysis"}}
<div class="section">
<h2>Delivery Analysis</h2>
<p><b>Delivery Timing:</b> {{.DeliveryDelay}}</p>
{{if .GeoLocation}}<p><b>Geographic Info:</b> {{.GeoLocation}}</p>{{end}}
</div>
{{end}}
<div class="section">
<h2>Security Analysis</h2>
<div class="security-analysis-vertical">
<div class="section">
<h3>SPF Authentication</h3>
<div class="status {{if .SPFPass}}good{{else}}error{{end}}">
{{if .SPFPass}}✓ Passed{{else}}✗ Failed{{end}}
</div>
<p>{{.SPFDetails}}</p>
{{if .SPFRecord}}<pre>{{.SPFRecord}}</pre>{{end}}
{{if .SPFHeader}}
<details class="details"><summary>Show SPF Header</summary><pre>{{.SPFHeader}}</pre></details>
{{end}}
</div>
<div class="section">
<h3>DMARC Policy</h3>
<div class="status {{if .DMARCPass}}good{{else}}error{{end}}">
{{if .DMARCPass}}✓ Passed{{else}}✗ Failed{{end}}
</div>
<p>{{.DMARCDetails}}</p>
{{if .DMARCRecord}}<pre>{{.DMARCRecord}}</pre>{{end}}
{{if .DMARCHeader}}
<details class="details"><summary>Show DMARC Header</summary><pre>{{.DMARCHeader}}</pre></details>
{{end}}
</div>
<div class="section">
<h3>DKIM Signature</h3>
<div class="status {{if .DKIMPass}}good{{else}}error{{end}}">
{{if .DKIMPass}}✓ Present{{else}}✗ Missing{{end}}
</div>
<p>{{.DKIMDetails}}</p>
{{if .DKIM}}
<details class="details"><summary>Show DKIM Header</summary><pre>{{.DKIM}}</pre></details>
{{else if .DKIMHeader}}
<details class="details"><summary>Show DKIM Header</summary><pre>{{.DKIMHeader}}</pre></details>
{{end}}
</div>
</div>
</div>
<div class="section">
<h2>Encryption</h2>
<div class="status {{if .Encrypted}}good{{else}}error{{end}}">
{{if .Encrypted}}Encrypted (TLS){{else}}Not Encrypted{{end}}
</div>
<details><summary>Show Encryption Details</summary><pre>{{.EncryptionDetail}}</pre></details>
</div>
{{if .Warnings}}
<div class="section">
<h2>Warnings</h2>
<ul>
{{range .Warnings}}<li class="status warning">⚠️ {{.}}</li>{{end}}
</ul>
</div>
{{end}}
{{if .SecurityFlags}}
<div class="section">
<h2>Security Flags</h2>
<ul>
{{range .SecurityFlags}}<li class="status">🔒 {{.}}</li>{{end}}
</ul>
</div>
{{end}}
{{if .Blacklists}}
<div class="section">
<h2>Blacklist Status</h2>
<div style="margin-bottom: 6px;">
<b>Checked:</b>
{{if .SendingServer}}IP {{.SendingServer}}{{else if .FromDomain}}Domain {{.FromDomain}}{{end}}
</div>
<div class="status error">⚠️ Listed on the following blacklists:</div>
<ul>
{{range .Blacklists}}<li>{{.}}</li>{{end}}
</ul>
</div>
{{end}}
{{if .SpamFlags}}
<div class="section">
<h2>Spam Analysis</h2>
<div class="status {{if gt (len .SpamFlags) 0}}warning{{else}}good{{end}}">
{{if gt (len .SpamFlags) 0}}⚠️ Spam Indicators Found{{else}}✓ No Spam Indicators{{end}}
</div>
{{if .SpamScore}}<p><b>Spam Score:</b> {{.SpamScore}}</p>{{end}}
{{if .SpamFlags}}
<ul>
{{range .SpamFlags}}<li>{{.}}</li>{{end}}
</ul>
{{end}}
</div>
{{end}}
{{if ne .VirusInfo "No virus scanning information found"}}
<div class="section">
<h2>Virus Scanning</h2>
<div class="status good">🛡️ Virus Scanning Information</div>
<p>{{.VirusInfo}}</p>
</div>
{{end}}
{{if .PhishingRisk}}
<div class="section">
<h2>Security Risk Assessment</h2>
<div class="security-analysis-vertical">
<div class="section">
<h3>Phishing Risk</h3>
<div class="status {{if eq (index (splitString .PhishingRisk " ") 0) "HIGH"}}error{{else if eq (index (splitString .PhishingRisk " ") 0) "MEDIUM"}}warning{{else}}good{{end}}">
{{.PhishingRisk}}
</div>
</div>
<div class="section">
<h3>Spoofing Risk</h3>
<div class="status {{if contains .SpoofingRisk "POTENTIAL"}}warning{{else}}good{{end}}">
{{.SpoofingRisk}}
</div>
</div>
</div>
</div>
{{end}}
{{if .ListInfo}}
<div class="section">
<h2>Mailing List Information</h2>
<ul>
{{range .ListInfo}}<li>{{.}}</li>{{end}}
</ul>
{{if .AutoReply}}<p class="status">📧 Auto-reply message detected</p>{{end}}
{{if .BulkEmail}}<p class="status">📬 Bulk/marketing email detected</p>{{end}}
</div>
{{end}}
{{if .Compliance}}
<div class="section">
<h2>Compliance Information</h2>
<ul>
{{range .Compliance}}<li class="status good">✓ {{.}}</li>{{end}}
</ul>
</div>
{{end}}
{{if .ARC}}
<div class="section">
<h2>ARC (Authenticated Received Chain)</h2>
<details><summary>Show ARC Headers</summary>
<ul>
{{range .ARC}}<li><pre>{{.}}</pre></li>{{end}}
</ul>
</details>
</div>
{{end}}
{{if ne .BIMI "No BIMI record found"}}
<div class="section">
<h2>Brand Indicators (BIMI)</h2>
<p>{{.BIMI}}</p>
</div>
{{end}}
{{if .Attachments}}
<div class="section">
<h2>Attachment Information</h2>
<ul>
{{range .Attachments}}<li>{{.}}</li>{{end}}
</ul>
</div>
{{end}}
{{if .URLs}}
<div class="section">
<h2>URL Information</h2>
<ul>
{{range .URLs}}<li>{{.}}</li>{{end}}
</ul>
</div>
{{end}}
{{if ne .ThreadInfo "No threading information available"}}
<div class="section">
<h2>Message Threading</h2>
<details><summary>Show Threading Information</summary>
<pre>{{.ThreadInfo}}</pre>
</details>
</div>
{{end}}
<div class="section">
<button onclick="exportPDF()" type="button">Export as PDF</button>
<button onclick="exportImage()" type="button">Save as Image</button>
<button onclick="printReport()" type="button">Print Report</button>
</div>
</div>
{{end}}
</div>
{{end}}
{{define "scripts"}}
<script>
// Check if required libraries are loaded
function checkLibraries() {
if (typeof html2canvas === 'undefined') {
console.warn('html2canvas library not loaded');
return false;
}
const hasHtml2pdf = typeof html2pdf !== 'undefined';
const hasJsPDF = typeof window.jsPDF !== 'undefined';
if (!hasHtml2pdf && !hasJsPDF) {
console.warn('Neither html2pdf nor jsPDF library loaded');
return false;
}
if (hasHtml2pdf) {
console.log('Using html2pdf for PDF generation');
} else if (hasJsPDF) {
console.log('Using jsPDF fallback for PDF generation');
}
return true;
}
// Template helper functions for the enhanced features
function splitString(str, delimiter) {
return str.split(delimiter);
}
function contains(str, substr) {
return str.includes(substr);
}
function exportImage() {
if (typeof html2canvas === 'undefined') {
alert('Image export library not loaded. Please refresh the page and try again.');
return;
}
html2canvas(document.querySelector("#report")).then(canvas => {
let link = document.createElement("a");
link.download = "email-analysis.png";
link.href = canvas.toDataURL();
link.click();
}).catch(error => {
console.error('Image export failed:', error);
alert('Failed to export image. Please try again.');
});
}
function printReport() {
// Store original states
const originalDetailsStates = {};
document.querySelectorAll('#report details').forEach((detail, index) => {
originalDetailsStates[index] = detail.open;
});
// Expand details for printing (except ARC)
document.querySelectorAll('#report details').forEach(detail => {
const summary = detail.querySelector('summary');
if (summary && !summary.textContent.includes('Show ARC Headers')) {
detail.open = true;
}
});
// Open print dialog
window.print();
// Restore original states after a short delay
setTimeout(() => {
document.querySelectorAll('#report details').forEach((detail, index) => {
detail.open = originalDetailsStates[index] || false;
});
}, 1000);
}
function exportPDF() {
// Check if libraries are available
if (typeof html2canvas === 'undefined') {
alert('HTML2Canvas library not loaded. Please refresh the page and try again.');
return;
}
// Check if we have either html2pdf or jsPDF available
const hasHtml2pdf = typeof html2pdf !== 'undefined';
const hasJsPDF = typeof window.jsPDF !== 'undefined';
if (!hasHtml2pdf && !hasJsPDF) {
alert('PDF generation library not loaded. Please refresh the page and try again.');
return;
}
// Store original states
const originalDetailsStates = {};
document.querySelectorAll('#report details').forEach((detail, index) => {
originalDetailsStates[index] = detail.open;
});
// Expand only specific details that should be included in PDF (not ARC)
document.querySelectorAll('#report details').forEach(detail => {
const summary = detail.querySelector('summary');
if (summary && !summary.textContent.includes('Show ARC Headers')) {
detail.open = true;
}
});
// Make sure all-headers is expanded
const allHeaders = document.getElementById('all-headers');
if (allHeaders) {
allHeaders.open = true;
}
const element = document.getElementById('report');
if (!element) {
alert('Report element not found. Please ensure the analysis is complete.');
return;
}
// Add temporary styling for PDF export
const tempStyle = document.createElement('style');
tempStyle.id = 'temp-pdf-style';
tempStyle.innerHTML = `
body {
max-width: none !important;
width: 794px !important;
margin: 0 !important;
padding: 0 !important;
background: white !important;
color: black !important;
}
.container {
max-width: none !important;
width: 794px !important;
padding: 15px !important;
margin: 0 !important;
box-sizing: border-box !important;
}
#report {
max-width: none !important;
width: 100% !important;
padding: 10px !important;
margin: 0 !important;
background: white !important;
color: black !important;
box-sizing: border-box !important;
position: relative !important;
left: 0 !important;
top: 0 !important;
}
#report * {
background: white !important;
color: black !important;
max-width: none !important;
}
#report .section {
width: 100% !important;
max-width: none !important;
background: white !important;
border: 1px solid #ccc !important;
margin-bottom: 10px !important;
padding: 10px !important;
box-sizing: border-box !important;
page-break-inside: avoid !important;
}
#report .grid {
display: block !important;
width: 100% !important;
}
#report .grid > div {
display: block !important;
width: 100% !important;
margin-bottom: 10px !important;
}
#report .grid p {
margin: 5px 0 !important;
word-break: break-word !important;
overflow-wrap: break-word !important;
}
#report pre {
background: #f5f5f5 !important;
color: black !important;
border: 1px solid #ccc !important;
padding: 5px !important;
width: 100% !important;
max-width: none !important;
box-sizing: border-box !important;
white-space: pre-wrap !important;
word-break: break-word !important;
page-break-inside: avoid !important;
}
#report table {
width: 100% !important;
max-width: none !important;
border-collapse: collapse !important;
table-layout: fixed !important;
page-break-inside: avoid !important;
}
#report td, #report th {
border: 1px solid #333 !important;
background: white !important;
color: black !important;
padding: 4px !important;
word-wrap: break-word !important;
word-break: break-word !important;
overflow-wrap: break-word !important;
}
#report .status {
background: #f0f0f0 !important;
color: black !important;
border: 1px solid #999 !important;
display: inline-block !important;
padding: 2px 5px !important;
}
#report .status.good {
background: #e8f5e8 !important;
}
#report .status.warning {
background: #fff3cd !important;
}
#report .status.error {
background: #f8d7da !important;
}
#report details {
margin-bottom: 10px !important;
page-break-inside: avoid !important;
}
/* Hide buttons */
button {
display: none !important;
}
`;
document.head.appendChild(tempStyle);
// Force the element to have A4 dimensions
const originalWidth = element.style.width;
const originalMaxWidth = element.style.maxWidth;
element.style.width = '794px'; // A4 width in pixels
element.style.maxWidth = 'none';
// Store these for restoration
const restoreWidth = () => {
element.style.width = originalWidth;
element.style.maxWidth = originalMaxWidth;
};
// Show loading message
const button = document.querySelector('button[onclick="exportPDF()"]');
const originalText = button ? button.textContent : '';
if (button) button.textContent = 'Generating PDF...';
// Wait a moment for styles to be applied
setTimeout(() => {
performPDFExport();
}, 100);
function performPDFExport() {
try {
if (hasHtml2pdf) {
// Use html2pdf if available
const opt = {
margin: [5, 5, 5, 5], // Smaller margins in mm
filename: 'email-header-analysis.pdf',
image: { type: 'jpeg', quality: 0.95 },
html2canvas: {
scale: 2,
useCORS: true,
allowTaint: true,
scrollX: 0,
scrollY: 0,
x: 0,
y: 0,
width: 794, // A4 width in pixels at 96 DPI (210mm)
height: element.scrollHeight,
backgroundColor: '#ffffff',
windowWidth: 794, // A4 width at 96 DPI
windowHeight: 1123 // A4 height at 96 DPI
},
jsPDF: {
unit: 'mm',
format: 'a4',
orientation: 'portrait',
compress: true
}
};
html2pdf().set(opt).from(element).save().then(() => {
restoreOriginalState();
}).catch((error) => {
console.error('PDF generation failed:', error);
alert('Failed to generate PDF. Please try again.');
restoreOriginalState();
});
} else if (hasJsPDF) {
// Fallback to direct jsPDF + html2canvas approach
html2canvas(element, {
scale: 2,
useCORS: true,
allowTaint: true,
scrollX: 0,
scrollY: 0,
x: 0,
y: 0,
width: 794, // A4 width in pixels at 96 DPI (210mm)
height: element.scrollHeight,
backgroundColor: '#ffffff',
windowWidth: 794, // A4 width at 96 DPI
windowHeight: 1123 // A4 height at 96 DPI
}).then(canvas => {
const imgData = canvas.toDataURL('image/jpeg', 0.95);
const pdf = new window.jsPDF.jsPDF('p', 'mm', 'a4');
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
// Use full width with minimal margins
const imgWidth = pdfWidth - 10; // 5mm margin on each side
const imgHeight = (canvas.height * imgWidth) / canvas.width;
let heightLeft = imgHeight;
let position = 5; // 5mm top margin
pdf.addImage(imgData, 'JPEG', 5, position, imgWidth, imgHeight);
heightLeft -= (pdfHeight - 10); // Account for margins
while (heightLeft >= 0) {
position = heightLeft - imgHeight + 5;
pdf.addPage();
pdf.addImage(imgData, 'JPEG', 5, position, imgWidth, imgHeight);
heightLeft -= (pdfHeight - 10);
}
pdf.save('email-header-analysis.pdf');
restoreOriginalState();
}).catch(error => {
console.error('Canvas generation failed:', error);
alert('Failed to generate PDF. Please try again.');
restoreOriginalState();
});
}
} catch (error) {
console.error('PDF export error:', error);
alert('PDF export failed. Please refresh the page and try again.');
restoreOriginalState();
}
function restoreOriginalState() {
document.querySelectorAll('#report details').forEach((detail, index) => {
detail.open = originalDetailsStates[index] || false;
});
// Remove temporary PDF styling
const tempStyle = document.getElementById('temp-pdf-style');
if (tempStyle) {
document.head.removeChild(tempStyle);
}
// Restore element width if restoreWidth function exists
if (typeof restoreWidth === 'function') {
restoreWidth();
}
if (button) button.textContent = originalText;
}
} // End of performPDFExport function
}
// Header table search
document.addEventListener('DOMContentLoaded', function() {
// Check if libraries are loaded
setTimeout(() => {
if (!checkLibraries()) {
console.warn('Some export libraries failed to load. PDF/Image export may not work.');
}
}, 1000);
var search = document.getElementById('headerSearch');
if (search) {
search.addEventListener('input', function() {
var filter = search.value.toLowerCase();
var rows = document.querySelectorAll('#headersTable tbody tr');
rows.forEach(function(row) {
var text = row.textContent.toLowerCase();
row.style.display = text.indexOf(filter) > -1 ? '' : 'none';
});
});
}
});
</script>
{{end}}

View File

@@ -1,374 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Email Header Analyzer</title>
<link rel="stylesheet" href="/static/style.css">
<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<nav>
<a href="/">Analyze New Header</a>
<a href="/dns">DNS Tools</a>
<a href="/password">Password Generator</a>
</nav>
<div class="container">
<h1>Email Header Analyzer</h1>
{{if not .From}}
<form method="POST">
<textarea name="headers" placeholder="Paste email headers here..."></textarea>
<br>
<button type="submit">Analyze Headers</button>
</form>
{{end}}
{{if .From}}
<div id="report" class="container">
<div class="section" style="display: flex; align-items: flex-start; justify-content: space-between; gap: 30px;">
<div style="flex: 1 1 0; min-width: 0;">
<h2>Sender Identification</h2>
<div class="grid">
<div>
<p><b>Envelope Sender (Return-Path):</b> {{.EnvelopeSender}}</p>
<p><b>From Domain:</b> {{.FromDomain}}</p>
<p><b>Sending Server:</b> {{.SendingServer}}</p>
</div>
<div class="score-indicators">
<span class="status {{if .SPFPass}}good{{else}}error{{end}}" title="SPF">SPF {{if .SPFPass}}✓{{else}}✗{{end}}</span>
<span class="status {{if .DMARCPass}}good{{else}}error{{end}}" title="DMARC">DMARC {{if .DMARCPass}}✓{{else}}✗{{end}}</span>
<span class="status {{if .DKIMPass}}good{{else}}error{{end}}" title="DKIM">DKIM {{if .DKIMPass}}✓{{else}}✗{{end}}</span>
<span class="status {{if .Encrypted}}good{{else}}error{{end}}" title="Encrypted">Encrypted {{if .Encrypted}}✓{{else}}✗{{end}}</span>
{{if .Blacklists}}
<span class="status error" title="{{range .Blacklists}}{{.}}, {{end}}">Blacklisted {{len .Blacklists}} times</span>
{{else}}
<span class="status good">Not listed on major blacklists</span>
{{end}}
</div>
</div>
{{if .SenderRep}}
<div>
<b><span>Sender Reputation: </span></b><div class="status {{if contains .SenderRep "EXCELLENT"}}good{{else if contains .SenderRep "GOOD"}}good{{else if contains .SenderRep "FAIR"}}warning{{else}}error{{end}}">
{{.SenderRep}}
</div>
</div>
{{end}}
<div class="explanation">
<small>
<b>Envelope Sender</b> is the real sender used for delivery (can differ from From).<br>
<b>From Domain</b> is the domain shown to the recipient.<br>
<b>Sending Server</b> is the host or IP that actually sent the message (from first Received header).<br>
If these differ, the message may be sent on behalf of another user or via a third-party service.
</small>
</div>
</div>
</div>
<details id="all-headers" class="section" style="margin-top:10px;">
<summary><b style="font-size: 1.5em;">All Email Headers Table</b></summary>
<div style="margin-bottom:10px;">
<input type="text" id="headerSearch" placeholder="Search headers..." style="width: 100%; max-width: 350px; padding: 5px; border-radius: 4px; border: 1px solid #444; background: #232323; color: #e0e0e0;">
</div>
<div style="overflow-x:auto;">
<table id="headersTable" style="width:100%; border-collapse:collapse; border:1px solid #444;">
<thead>
<tr>
<th style="text-align:left; padding:4px 8px; border:1px solid #444; width: 180px; background:#232323;">Header Name</th>
<th style="text-align:left; padding:4px 8px; border:1px solid #444; background:#232323;">Value</th>
</tr>
</thead>
<tbody>
{{range $k, $v := .AllHeaders}}
<tr>
<td style="vertical-align:top; padding:4px 8px; border:1px solid #444; word-break:break-word;">{{$k}}</td>
<td style="vertical-align:top; padding:4px 8px; border:1px solid #444; white-space:pre-wrap; word-break:break-word;">{{$v}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</details>
<div class="section">
<h2>Basic Information</h2>
<div class="grid">
<div>
<p><b>From:</b> {{.From}}</p>
<p><b>To:</b> {{.To}}</p>
<p><b>Subject:</b> {{.Subject}}</p>
<p><b>Date:</b> {{.Date}}</p>
{{if .ReplyTo}}<p><b>Reply-To:</b> {{.ReplyTo}}</p>{{end}}
</div>
<div>
<p><b>Message-ID:</b> {{.MessageID}}</p>
<p><b>Priority:</b> {{.Priority}}</p>
<p><b>Content Type:</b> {{.ContentType}}</p>
<p><b>Encoding:</b> {{.Encoding}}</p>
</div>
</div>
</div>
<div class="section">
<h2>Mail Flow</h2>
<ul class="mail-flow">
{{range .Received}}<li>{{.}}</li>{{end}}
</ul>
</div>
{{if ne .DeliveryDelay "Insufficient data for delay analysis"}}
<div class="section">
<h2>Delivery Analysis</h2>
<p><b>Delivery Timing:</b> {{.DeliveryDelay}}</p>
{{if .GeoLocation}}<p><b>Geographic Info:</b> {{.GeoLocation}}</p>{{end}}
</div>
{{end}}
<div class="section">
<h2>Security Analysis</h2>
<div class="security-analysis-vertical">
<div class="section">
<h3>SPF Authentication</h3>
<div class="status {{if .SPFPass}}good{{else}}error{{end}}">
{{if .SPFPass}}✓ Passed{{else}}✗ Failed{{end}}
</div>
<p>{{.SPFDetails}}</p>
{{if .SPFRecord}}<pre>{{.SPFRecord}}</pre>{{end}}
{{if .SPFHeader}}
<details class="details"><summary>Show SPF Header</summary><pre>{{.SPFHeader}}</pre></details>
{{end}}
</div>
<div class="section">
<h3>DMARC Policy</h3>
<div class="status {{if .DMARCPass}}good{{else}}error{{end}}">
{{if .DMARCPass}}✓ Passed{{else}}✗ Failed{{end}}
</div>
<p>{{.DMARCDetails}}</p>
{{if .DMARCRecord}}<pre>{{.DMARCRecord}}</pre>{{end}}
{{if .DMARCHeader}}
<details class="details"><summary>Show DMARC Header</summary><pre>{{.DMARCHeader}}</pre></details>
{{end}}
</div>
<div class="section">
<h3>DKIM Signature</h3>
<div class="status {{if .DKIMPass}}good{{else}}error{{end}}">
{{if .DKIMPass}}✓ Present{{else}}✗ Missing{{end}}
</div>
<p>{{.DKIMDetails}}</p>
{{if .DKIM}}
<details class="details"><summary>Show DKIM Header</summary><pre>{{.DKIM}}</pre></details>
{{else if .DKIMHeader}}
<details class="details"><summary>Show DKIM Header</summary><pre>{{.DKIMHeader}}</pre></details>
{{end}}
</div>
</div>
</div>
<div class="section">
<h2>Encryption</h2>
<div class="status {{if .Encrypted}}good{{else}}error{{end}}">
{{if .Encrypted}}Encrypted (TLS){{else}}Not Encrypted{{end}}
</div>
<details><summary>Show Encryption Details</summary><pre>{{.EncryptionDetail}}</pre></details>
</div>
{{if .Warnings}}
<div class="section">
<h2>Warnings</h2>
<ul>
{{range .Warnings}}<li class="status warning">⚠️ {{.}}</li>{{end}}
</ul>
</div>
{{end}}
{{if .SecurityFlags}}
<div class="section">
<h2>Security Flags</h2>
<ul>
{{range .SecurityFlags}}<li class="status">🔒 {{.}}</li>{{end}}
</ul>
</div>
{{end}}
{{if .Blacklists}}
<div class="section">
<h2>Blacklist Status</h2>
<div style="margin-bottom: 6px;">
<b>Checked:</b>
{{if .SendingServer}}IP {{.SendingServer}}{{else if .FromDomain}}Domain {{.FromDomain}}{{end}}
</div>
<div class="status error">⚠️ Listed on the following blacklists:</div>
<ul>
{{range .Blacklists}}<li>{{.}}</li>{{end}}
</ul>
</div>
{{end}}
{{if .SpamFlags}}
<div class="section">
<h2>Spam Analysis</h2>
<div class="status {{if gt (len .SpamFlags) 0}}warning{{else}}good{{end}}">
{{if gt (len .SpamFlags) 0}}⚠️ Spam Indicators Found{{else}}✓ No Spam Indicators{{end}}
</div>
{{if .SpamScore}}<p><b>Spam Score:</b> {{.SpamScore}}</p>{{end}}
{{if .SpamFlags}}
<ul>
{{range .SpamFlags}}<li>{{.}}</li>{{end}}
</ul>
{{end}}
</div>
{{end}}
{{if ne .VirusInfo "No virus scanning information found"}}
<div class="section">
<h2>Virus Scanning</h2>
<div class="status good">🛡️ Virus Scanning Information</div>
<p>{{.VirusInfo}}</p>
</div>
{{end}}
{{if .PhishingRisk}}
<div class="section">
<h2>Security Risk Assessment</h2>
<div class="security-analysis-vertical">
<div class="section">
<h3>Phishing Risk</h3>
<div class="status {{if eq (index (splitString .PhishingRisk " ") 0) "HIGH"}}error{{else if eq (index (splitString .PhishingRisk " ") 0) "MEDIUM"}}warning{{else}}good{{end}}">
{{.PhishingRisk}}
</div>
</div>
<div class="section">
<h3>Spoofing Risk</h3>
<div class="status {{if contains .SpoofingRisk "POTENTIAL"}}warning{{else}}good{{end}}">
{{.SpoofingRisk}}
</div>
</div>
</div>
</div>
{{end}}
{{if .ListInfo}}
<div class="section">
<h2>Mailing List Information</h2>
<ul>
{{range .ListInfo}}<li>{{.}}</li>{{end}}
</ul>
{{if .AutoReply}}<p class="status">📧 Auto-reply message detected</p>{{end}}
{{if .BulkEmail}}<p class="status">📬 Bulk/marketing email detected</p>{{end}}
</div>
{{end}}
{{if .Compliance}}
<div class="section">
<h2>Compliance Information</h2>
<ul>
{{range .Compliance}}<li class="status good">✓ {{.}}</li>{{end}}
</ul>
</div>
{{end}}
{{if .ARC}}
<div class="section">
<h2>ARC (Authenticated Received Chain)</h2>
<details><summary>Show ARC Headers</summary>
<ul>
{{range .ARC}}<li><pre>{{.}}</pre></li>{{end}}
</ul>
</details>
</div>
{{end}}
{{if ne .BIMI "No BIMI record found"}}
<div class="section">
<h2>Brand Indicators (BIMI)</h2>
<p>{{.BIMI}}</p>
</div>
{{end}}
{{if .Attachments}}
<div class="section">
<h2>Attachment Information</h2>
<ul>
{{range .Attachments}}<li>{{.}}</li>{{end}}
</ul>
</div>
{{end}}
{{if .URLs}}
<div class="section">
<h2>URL Information</h2>
<ul>
{{range .URLs}}<li>{{.}}</li>{{end}}
</ul>
</div>
{{end}}
{{if ne .ThreadInfo "No threading information available"}}
<div class="section">
<h2>Message Threading</h2>
<details><summary>Show Threading Information</summary>
<pre>{{.ThreadInfo}}</pre>
</details>
</div>
{{end}}
<div class="section">
<button onclick="exportPDF()" type="button">Export as PDF</button>
<button onclick="exportImage()" type="button">Save as Image</button>
</div>
</div>
{{end}}
<script>
// Template helper functions for the enhanced features
function splitString(str, delimiter) {
return str.split(delimiter);
}
function contains(str, substr) {
return str.includes(substr);
}
function exportImage() {
html2canvas(document.querySelector("#report")).then(canvas => {
let link = document.createElement("a");
link.download = "email-analysis.png";
link.href = canvas.toDataURL();
link.click();
});
}
function exportPDF() {
// Expand all details before export
document.querySelectorAll('#report details').forEach(d => d.open = true);
document.getElementById('all-headers').open = true;
const element = document.getElementById('report');
const opt = {
margin: 0.1,
filename: 'email-analysis.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, useCORS: true },
jsPDF: { unit: 'in', format: 'a4', orientation: 'portrait', putOnlyUsedFonts:true }
};
html2pdf().set(opt).from(element).save();
}
// Header table search
document.addEventListener('DOMContentLoaded', function() {
var search = document.getElementById('headerSearch');
if (search) {
search.addEventListener('input', function() {
var filter = search.value.toLowerCase();
var rows = document.querySelectorAll('#headersTable tbody tr');
rows.forEach(function(row) {
var text = row.textContent.toLowerCase();
row.style.display = text.indexOf(filter) > -1 ? '' : 'none';
});
});
}
});
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -243,6 +243,11 @@ nav a {
nav a:hover {
text-decoration: underline;
}
nav a.active {
color: #00ff88;
border-bottom: 2px solid #00ff88;
padding-bottom: 2px;
}
.container, .section, .grid {
word-break: break-word;
@@ -262,27 +267,149 @@ nav a:hover {
line-height: 1.4;
}
/* Text wrapping improvements for better PDF export and display */
pre {
white-space: pre-wrap !important;
word-wrap: break-word !important;
word-break: break-word !important;
overflow-wrap: break-word !important;
max-width: 100% !important;
overflow: hidden !important;
}
table {
table-layout: fixed;
width: 100%;
}
td, th {
word-wrap: break-word;
word-break: break-word;
overflow-wrap: break-word;
max-width: 0;
overflow: hidden;
}
/* Specific improvements for the report container */
#report {
word-wrap: break-word;
overflow-wrap: break-word;
}
#report * {
max-width: 100%;
box-sizing: border-box;
}
/* PDF-specific print styles */
@media print {
body {
background-color: white;
color: black;
font-size: 12px;
line-height: 1.4;
background: white !important;
color: black !important;
}
/* Override all colors for print */
* {
background: white !important;
color: black !important;
border-color: #000 !important;
}
/* Make sure containers use full width */
.container {
max-width: 100% !important;
padding: 10px !important;
margin: 0 !important;
}
#report {
max-width: 100% !important;
padding: 0 !important;
margin: 0 !important;
background: white !important;
}
#report * {
word-wrap: break-word !important;
word-break: break-word !important;
overflow-wrap: break-word !important;
max-width: 100% !important;
background: white !important;
color: black !important;
}
#report pre {
font-size: 10px !important;
white-space: pre-wrap !important;
word-break: break-all !important;
background: #f5f5f5 !important;
border: 1px solid #ccc !important;
padding: 5px !important;
}
#report table {
table-layout: fixed !important;
width: 100% !important;
border-collapse: collapse !important;
}
#report td, #report th {
word-wrap: break-word !important;
word-break: break-word !important;
overflow-wrap: break-word !important;
padding: 4px !important;
font-size: 10px !important;
border: 1px solid #000 !important;
background: white !important;
color: black !important;
}
.section {
background-color: white;
color: black;
box-shadow: none;
border: 1px solid #ccc;
page-break-inside: avoid;
margin-bottom: 15px !important;
padding: 10px !important;
border: 1px solid #ccc !important;
background: white !important;
}
.score-circle, .score-inner {
background: white;
color: black;
.status {
background: #f0f0f0 !important;
color: black !important;
border: 1px solid #999 !important;
padding: 2px 5px !important;
}
pre, code {
background: #f5f5f5;
color: black;
border: 1px solid #ccc;
white-space: pre-wrap;
word-break: break-word;
.status.good {
background: #e8f5e8 !important;
color: black !important;
}
.status.warning {
background: #fff3cd !important;
color: black !important;
}
.status.error {
background: #f8d7da !important;
color: black !important;
}
/* Hide buttons in print */
button {
display: none !important;
}
/* Navigation should be hidden */
nav {
display: none !important;
}
h1, h2, h3 {
color: black !important;
border-bottom: 2px solid black !important;
background: white !important;
}
}
@@ -305,3 +432,202 @@ nav a:hover {
grid-template-columns: 1fr;
}
}
.password-generator {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.password-output {
background: #1a1a1a;
border: 2px solid #333;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
font-family: 'Courier New', monospace;
font-size: 18px;
word-break: break-all;
position: relative;
}
.password-text {
color: #00ff88;
font-weight: bold;
margin-bottom: 10px;
}
.copy-btn {
background: #007acc;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.copy-btn:hover {
background: #005999;
}
.copy-btn.copied {
background: #00aa44;
}
.controls {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin: 20px 0;
}
.control-group {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 8px;
padding: 20px;
}
.control-group h3 {
margin-top: 0;
color: #00ff88;
border-bottom: 1px solid #333;
padding-bottom: 10px;
}
.form-row {
margin: 15px 0;
display: flex;
align-items: center;
gap: 10px;
}
.form-row label {
flex: 1;
color: #ccc;
}
.form-row input[type="number"],
.form-row input[type="text"],
.form-row select {
background: #2a2a2a;
border: 1px solid #444;
color: #fff;
padding: 8px;
border-radius: 4px;
width: 120px;
}
.form-row input[type="text"] {
width: 200px;
}
.form-row input[type="checkbox"] {
width: auto;
}
.generate-btn {
background: #00aa44;
color: white;
border: none;
padding: 15px 30px;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
font-weight: bold;
width: 100%;
margin: 20px 0;
}
.generate-btn:hover {
background: #008833;
}
.tab-buttons {
display: flex;
margin-bottom: 20px;
border-radius: 8px;
overflow: hidden;
border: 1px solid #333;
}
.tab-btn {
flex: 1;
background: #2a2a2a;
color: #ccc;
border: none;
padding: 15px;
cursor: pointer;
font-size: 16px;
}
.tab-btn.active {
background: #007acc;
color: white;
}
.tab-btn:hover:not(.active) {
background: #333;
}
.passphrase-controls {
display: none;
}
.passphrase-controls.active {
display: block;
}
/* Notification styles */
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
border-radius: 6px;
color: white;
font-weight: bold;
font-size: 14px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 1000;
transform: translateX(100%);
transition: transform 0.3s ease-in-out;
}
.notification.show {
transform: translateX(0);
}
.notification.success {
background: #28a745;
}
.notification.info {
background: #17a2b8;
}
.notification.warning {
background: #ffc107;
color: #212529;
}
.notification.error {
background: #dc3545;
}
@media (max-width: 768px) {
.controls {
grid-template-columns: 1fr;
}
.notification {
top: 10px;
right: 10px;
left: 10px;
transform: translateY(-100%);
}
.notification.show {
transform: translateY(0);
}
}