Added file support and increased file limits.

This commit is contained in:
Aaron D. Lee
2025-12-28 03:44:17 -05:00
parent 130835990e
commit 5bd49cb581
16 changed files with 1576 additions and 178 deletions

View File

@@ -7,10 +7,11 @@
<div class="col-lg-8">
<div class="card">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-unlock-fill me-2"></i>Decode Secret Message</h5>
<h5 class="mb-0"><i class="bi bi-unlock-fill me-2"></i>Decode Secret Message or File</h5>
</div>
<div class="card-body">
{% if decoded_message %}
<!-- Text Message Result -->
<div class="alert alert-success">
<h6><i class="bi bi-check-circle me-2"></i>Message Decrypted Successfully!</h6>
</div>
@@ -23,17 +24,40 @@
</button>
</div>
i<!--then<div class="mb-4">
<label class="form-label text-muted">Decoded Message:</label>
<div class="alert-message">{{ decoded_message }}</div>
</div>-->
<a href="/decode" class="btn btn-outline-light w-100 mt-3">
<i class="bi bi-arrow-repeat me-2"></i>Decode Another
</a>
{% elif decoded_file %}
<!-- File Result -->
<div class="alert alert-success">
<h6><i class="bi bi-check-circle me-2"></i>File Decrypted Successfully!</h6>
</div>
<div class="text-center mb-4">
<i class="bi bi-file-earmark-check text-success" style="font-size: 4rem;"></i>
<h5 class="mt-3">{{ filename }}</h5>
<p class="text-muted mb-1">{{ file_size }}</p>
{% if mime_type %}
<small class="text-muted">Type: {{ mime_type }}</small>
{% endif %}
</div>
<a href="{{ url_for('decode_download', file_id=file_id) }}" class="btn btn-primary btn-lg w-100 mb-3">
<i class="bi bi-download me-2"></i>Download File
</a>
<div class="alert alert-warning small">
<i class="bi bi-clock me-1"></i>
<strong>File expires in 5 minutes.</strong> Download now.
</div>
<a href="/decode" class="btn btn-outline-light w-100">
<i class="bi bi-arrow-repeat me-2"></i>Decode Another Message
<i class="bi bi-arrow-repeat me-2"></i>Decode Another
</a>
{% else %}
<!-- Decode Form -->
<form method="POST" enctype="multipart/form-data" id="decodeForm">
<div class="row">
<div class="col-md-6 mb-3">
@@ -66,7 +90,7 @@
<img class="drop-zone-preview d-none">
</div>
<div class="form-text">
The image containing the hidden message
The image containing the hidden message/file
</div>
</div>
</div>
@@ -130,7 +154,7 @@
</div>
<button type="submit" class="btn btn-primary btn-lg w-100" id="decodeBtn">
<i class="bi bi-unlock me-2"></i>Decode Message
<i class="bi bi-unlock me-2"></i>Decode
</button>
</form>
@@ -138,6 +162,7 @@
</div>
</div>
{% if not decoded_message and not decoded_file %}
<div class="card mt-4">
<div class="card-body">
<h6 class="text-muted mb-3"><i class="bi bi-question-circle me-2"></i>Troubleshooting</h6>
@@ -165,6 +190,7 @@
</ul>
</div>
</div>
{% endif %}
</div>
</div>
{% endblock %}
@@ -209,7 +235,7 @@ function updateDayLabel(dayName) {
}
}
// 1. PIN Toggle
// PIN Toggle
document.getElementById('togglePin')?.addEventListener('click', function() {
const input = document.getElementById('pinInput');
const icon = this.querySelector('i');
@@ -222,9 +248,8 @@ document.getElementById('togglePin')?.addEventListener('click', function() {
}
});
// 2. Paste from Clipboard
// Paste from Clipboard
document.addEventListener('paste', function(e) {
// Only run if the form exists (we are not on the success page)
if (!document.getElementById('decodeForm')) return;
const items = e.clipboardData.items;
@@ -232,7 +257,6 @@ document.addEventListener('paste', function(e) {
if (items[i].type.indexOf("image") !== -1) {
const blob = items[i].getAsFile();
// Priority for Decode: Fill Stego Image first (most common), then Reference
const stegoInput = document.querySelector('input[name="stego_image"]');
const refInput = document.querySelector('input[name="reference_photo"]');

View File

@@ -7,7 +7,7 @@
<div class="col-lg-8">
<div class="card">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-lock-fill me-2"></i>Encode Secret Message</h5>
<h5 class="mb-0"><i class="bi bi-lock-fill me-2"></i>Encode Secret Message or File</h5>
</div>
<div class="card-body">
<form method="POST" enctype="multipart/form-data" id="encodeForm">
@@ -49,15 +49,34 @@
</div>
</div>
<!-- Payload Type Selector -->
<div class="mb-3">
<label class="form-label">
<i class="bi bi-box me-1"></i> What to Encode
</label>
<div class="btn-group w-100" role="group">
<input type="radio" class="btn-check" name="payload_type" id="payloadText" value="text" checked>
<label class="btn btn-outline-primary" for="payloadText">
<i class="bi bi-chat-left-text me-1"></i> Text Message
</label>
<input type="radio" class="btn-check" name="payload_type" id="payloadFile" value="file">
<label class="btn btn-outline-primary" for="payloadFile">
<i class="bi bi-file-earmark me-1"></i> File
</label>
</div>
</div>
<!-- Text Message Input -->
<div class="mb-3" id="textPayloadSection">
<label class="form-label">
<i class="bi bi-chat-left-text me-1"></i> Secret Message
</label>
<textarea name="message" class="form-control" rows="4" id="messageInput"
placeholder="Enter your secret message here..." required></textarea>
placeholder="Enter your secret message here..."></textarea>
<div class="d-flex justify-content-between form-text">
<span>
<span id="charCount">0</span> / 50,000 characters
<span id="charCount">0</span> / 250,000 characters
<span id="charWarning" class="text-warning d-none ms-2">
<i class="bi bi-exclamation-triangle"></i> Getting long!
</span>
@@ -66,6 +85,29 @@
</div>
</div>
<!-- File Upload Input -->
<div class="mb-3 d-none" id="filePayloadSection">
<label class="form-label">
<i class="bi bi-file-earmark me-1"></i> File to Embed
</label>
<div class="drop-zone" id="payloadDropZone">
<input type="file" name="payload_file" id="payloadFileInput">
<div class="drop-zone-label" id="payloadDropLabel">
<i class="bi bi-cloud-arrow-up fs-3 d-block mb-2 text-muted"></i>
<span class="text-muted">Drop any file or click to browse</span>
<div class="small text-muted mt-1">Max {{ max_payload_kb }} KB</div>
</div>
</div>
<div class="form-text">
Supports any file type: PDF, ZIP, documents, etc.
</div>
<div id="fileInfo" class="d-none mt-2 p-2 bg-dark rounded">
<i class="bi bi-file-earmark-check text-success me-2"></i>
<span id="fileInfoName"></span>
<span class="text-muted">(<span id="fileInfoSize"></span>)</span>
</div>
</div>
<div class="mb-3">
<label class="form-label" id="dayPhraseLabel">
<i class="bi bi-chat-quote me-1"></i> {{ day_of_week }}'s Phrase
@@ -121,7 +163,7 @@
</div>
<button type="submit" class="btn btn-primary btn-lg w-100" id="encodeBtn">
<i class="bi bi-lock me-2"></i>Encode Message
<i class="bi bi-lock me-2"></i>Encode
</button>
</form>
@@ -146,8 +188,8 @@
<i class="bi bi-info-circle me-1"></i>
<strong>Limits:</strong>
Carrier image max ~4 megapixels (2000×2000).
Files max 5MB each.
Message max 50KB.
Files max 10MB upload.
Payload max {{ max_payload_kb }} KB.
</div>
</div>
</div>
@@ -177,6 +219,57 @@ if (dateInput) {
dateInput.value = localDate;
}
// Payload type switching
const payloadTextRadio = document.getElementById('payloadText');
const payloadFileRadio = document.getElementById('payloadFile');
const textSection = document.getElementById('textPayloadSection');
const fileSection = document.getElementById('filePayloadSection');
const messageInput = document.getElementById('messageInput');
const payloadFileInput = document.getElementById('payloadFileInput');
function updatePayloadSection() {
const isText = payloadTextRadio.checked;
textSection.classList.toggle('d-none', !isText);
fileSection.classList.toggle('d-none', isText);
// Update required attribute
if (isText) {
messageInput.required = true;
payloadFileInput.required = false;
} else {
messageInput.required = false;
payloadFileInput.required = true;
}
}
payloadTextRadio.addEventListener('change', updatePayloadSection);
payloadFileRadio.addEventListener('change', updatePayloadSection);
// File payload info display
const fileInfo = document.getElementById('fileInfo');
const fileInfoName = document.getElementById('fileInfoName');
const fileInfoSize = document.getElementById('fileInfoSize');
const payloadDropLabel = document.getElementById('payloadDropLabel');
payloadFileInput.addEventListener('change', function() {
if (this.files && this.files[0]) {
const file = this.files[0];
fileInfoName.textContent = file.name;
fileInfoSize.textContent = formatFileSize(file.size);
fileInfo.classList.remove('d-none');
payloadDropLabel.innerHTML = `<i class="bi bi-check-circle text-success fs-3 d-block mb-2"></i><span>${file.name}</span>`;
} else {
fileInfo.classList.add('d-none');
payloadDropLabel.innerHTML = `<i class="bi bi-cloud-arrow-up fs-3 d-block mb-2 text-muted"></i><span class="text-muted">Drop any file or click to browse</span><div class="small text-muted mt-1">Max {{ max_payload_kb }} KB</div>`;
}
});
function formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
// Show RSA password field when key is selected
const rsaKeyInput = document.getElementById('rsaKeyInput');
const rsaPasswordGroup = document.getElementById('rsaPasswordGroup');
@@ -192,12 +285,11 @@ document.getElementById('encodeForm').addEventListener('submit', function(e) {
btn.disabled = true;
});
// Character counter
const messageInput = document.getElementById('messageInput');
// Character counter for text
const charCount = document.getElementById('charCount');
const charWarning = document.getElementById('charWarning');
const charPercent = document.getElementById('charPercent');
const maxChars = 50000;
const maxChars = 250000;
messageInput.addEventListener('input', function() {
const len = this.value.length;
@@ -210,11 +302,12 @@ messageInput.addEventListener('input', function() {
charCount.classList.toggle('text-danger', len > maxChars * 0.95);
});
// Drag & drop with preview
// Drag & drop with preview for images
document.querySelectorAll('.drop-zone').forEach(zone => {
const input = zone.querySelector('input[type="file"]');
const label = zone.querySelector('.drop-zone-label');
const preview = zone.querySelector('.drop-zone-preview');
const isPayloadZone = zone.id === 'payloadDropZone';
['dragenter', 'dragover'].forEach(evt => {
zone.addEventListener(evt, e => {
@@ -233,29 +326,38 @@ document.querySelectorAll('.drop-zone').forEach(zone => {
zone.addEventListener('drop', e => {
if (e.dataTransfer.files.length) {
input.files = e.dataTransfer.files;
showPreview(e.dataTransfer.files[0]);
input.dispatchEvent(new Event('change'));
if (!isPayloadZone) {
showPreview(e.dataTransfer.files[0]);
}
}
});
input.addEventListener('change', function() {
if (this.files && this.files[0]) {
showPreview(this.files[0]);
}
});
if (!isPayloadZone) {
input.addEventListener('change', function() {
if (this.files && this.files[0]) {
showPreview(this.files[0]);
}
});
}
function showPreview(file) {
if (!file.type.startsWith('image/')) return;
const reader = new FileReader();
reader.onload = e => {
preview.src = e.target.result;
preview.classList.remove('d-none');
if (preview) {
preview.src = e.target.result;
preview.classList.remove('d-none');
}
label.innerHTML = '<i class="bi bi-check-circle text-success me-1"></i>' + file.name;
};
reader.readAsDataURL(file);
}
});
// 1. PIN Toggle Logic
// PIN Toggle Logic
document.getElementById('togglePin').addEventListener('click', function() {
const input = document.getElementById('pinInput');
const icon = this.querySelector('i');
@@ -268,17 +370,16 @@ document.getElementById('togglePin').addEventListener('click', function() {
}
});
// 2. Prevent Same File Selection
// Prevent Same File Selection
function checkDuplicateFiles() {
const refInput = document.querySelector('input[name="reference_photo"]');
const carInput = document.querySelector('input[name="carrier"]');
if (refInput.files[0] && carInput.files[0]) {
// Compare name and size as a proxy for identical files
if (refInput.files[0].name === carInput.files[0].name &&
refInput.files[0].size === carInput.files[0].size) {
alert("Security Warning: You cannot use the same image for both Reference and Carrier!");
carInput.value = ''; // Clear carrier
carInput.value = '';
document.getElementById('carrierPreview').classList.add('d-none');
document.querySelector('#carrierDropZone .drop-zone-label').innerHTML =
'<i class="bi bi-cloud-arrow-up fs-3 d-block mb-2 text-muted"></i>' +
@@ -289,14 +390,13 @@ function checkDuplicateFiles() {
document.querySelector('input[name="reference_photo"]').addEventListener('change', checkDuplicateFiles);
document.querySelector('input[name="carrier"]').addEventListener('change', checkDuplicateFiles);
// 3. Paste from Clipboard
// Paste from Clipboard
document.addEventListener('paste', function(e) {
const items = e.clipboardData.items;
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf("image") !== -1) {
const blob = items[i].getAsFile();
// Priority: Fill Carrier first, if empty. Else fill Reference.
const carrierInput = document.querySelector('input[name="carrier"]');
const refInput = document.querySelector('input[name="reference_photo"]');
@@ -307,7 +407,7 @@ document.addEventListener('paste', function(e) {
targetInput.files = container.files;
targetInput.dispatchEvent(new Event('change'));
break; // Only paste one image at a time
break;
}
}
});