Fix async encode returning HTML errors instead of JSON

When encode form was submitted in async mode, validation errors
returned HTML (render_template) instead of JSON, causing
"Unexpected token '<'" parse errors in the browser.

Added _error_response() helper that returns JSON in async mode
and HTML flash in sync mode.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee
2026-01-09 23:17:51 -05:00
parent 2ed108f3a0
commit 175362ce4c

View File

@@ -1116,6 +1116,13 @@ def encode_page():
# Check if async mode requested # Check if async mode requested
is_async = request.form.get("async") == "true" or request.headers.get("X-Async") == "true" is_async = request.form.get("async") == "true" or request.headers.get("X-Async") == "true"
def _error_response(msg):
"""Return error as JSON (async) or HTML flash (sync)."""
if is_async:
return jsonify({"error": msg}), 400
flash(msg, "error")
return render_template("encode.html", has_qrcode_read=HAS_QRCODE_READ)
try: try:
# Get files # Get files
ref_photo = request.files.get("reference_photo") ref_photo = request.files.get("reference_photo")
@@ -1124,12 +1131,10 @@ def encode_page():
payload_file = request.files.get("payload_file") payload_file = request.files.get("payload_file")
if not ref_photo or not carrier: if not ref_photo or not carrier:
flash("Both reference photo and carrier image are required", "error") return _error_response("Both reference photo and carrier image are required")
return render_template("encode.html", has_qrcode_read=HAS_QRCODE_READ)
if not allowed_image(ref_photo.filename) or not allowed_image(carrier.filename): if not allowed_image(ref_photo.filename) or not allowed_image(carrier.filename):
flash("Invalid file type. Use PNG, JPG, or BMP", "error") return _error_response("Invalid file type. Use PNG, JPG, or BMP")
return render_template("encode.html", has_qrcode_read=HAS_QRCODE_READ)
# Get form data - v3.2.0: renamed from day_phrase to passphrase # Get form data - v3.2.0: renamed from day_phrase to passphrase
message = request.form.get("message", "") message = request.form.get("message", "")
@@ -1158,8 +1163,7 @@ def encode_page():
# Check DCT availability # Check DCT availability
if embed_mode == "dct" and not has_dct_support(): if embed_mode == "dct" and not has_dct_support():
flash("DCT mode requires scipy. Install with: pip install scipy", "error") return _error_response("DCT mode requires scipy. Install with: pip install scipy")
return render_template("encode.html", has_qrcode_read=HAS_QRCODE_READ)
# Determine payload # Determine payload
if payload_type == "file" and payload_file and payload_file.filename: if payload_type == "file" and payload_file and payload_file.filename:
@@ -1168,8 +1172,7 @@ def encode_page():
result = validate_file_payload(file_data, payload_file.filename) result = validate_file_payload(file_data, payload_file.filename)
if not result.is_valid: if not result.is_valid:
flash(result.error_message, "error") return _error_response(result.error_message)
return render_template("encode.html", has_qrcode_read=HAS_QRCODE_READ)
mime_type, _ = mimetypes.guess_type(payload_file.filename) mime_type, _ = mimetypes.guess_type(payload_file.filename)
payload = FilePayload( payload = FilePayload(
@@ -1179,20 +1182,17 @@ def encode_page():
# Text message # Text message
result = validate_message(message) result = validate_message(message)
if not result.is_valid: if not result.is_valid:
flash(result.error_message, "error") return _error_response(result.error_message)
return render_template("encode.html", has_qrcode_read=HAS_QRCODE_READ)
payload = message payload = message
# v3.2.0: Renamed from day_phrase # v3.2.0: Renamed from day_phrase
if not passphrase: if not passphrase:
flash("Passphrase is required", "error") return _error_response("Passphrase is required")
return render_template("encode.html", has_qrcode_read=HAS_QRCODE_READ)
# v3.2.0: Validate passphrase # v3.2.0: Validate passphrase
result = validate_passphrase(passphrase) result = validate_passphrase(passphrase)
if not result.is_valid: if not result.is_valid:
flash(result.error_message, "error") return _error_response(result.error_message)
return render_template("encode.html", has_qrcode_read=HAS_QRCODE_READ)
# Show warning if passphrase is short # Show warning if passphrase is short
if result.warning: if result.warning:
@@ -1223,21 +1223,18 @@ def encode_page():
rsa_key_data = key_pem.encode("utf-8") rsa_key_data = key_pem.encode("utf-8")
rsa_key_from_qr = True rsa_key_from_qr = True
else: else:
flash("Could not extract RSA key from QR code image.", "error") return _error_response("Could not extract RSA key from QR code image.")
return render_template("encode.html", has_qrcode_read=HAS_QRCODE_READ)
# Validate security factors # Validate security factors
result = validate_security_factors(pin, rsa_key_data) result = validate_security_factors(pin, rsa_key_data)
if not result.is_valid: if not result.is_valid:
flash(result.error_message, "error") return _error_response(result.error_message)
return render_template("encode.html", has_qrcode_read=HAS_QRCODE_READ)
# Validate PIN if provided # Validate PIN if provided
if pin: if pin:
result = validate_pin(pin) result = validate_pin(pin)
if not result.is_valid: if not result.is_valid:
flash(result.error_message, "error") return _error_response(result.error_message)
return render_template("encode.html", has_qrcode_read=HAS_QRCODE_READ)
# Determine key password # Determine key password
key_password = None if rsa_key_from_qr else (rsa_password if rsa_password else None) key_password = None if rsa_key_from_qr else (rsa_password if rsa_password else None)
@@ -1246,14 +1243,12 @@ def encode_page():
if rsa_key_data: if rsa_key_data:
result = validate_rsa_key(rsa_key_data, key_password) result = validate_rsa_key(rsa_key_data, key_password)
if not result.is_valid: if not result.is_valid:
flash(result.error_message, "error") return _error_response(result.error_message)
return render_template("encode.html", has_qrcode_read=HAS_QRCODE_READ)
# Validate carrier image # Validate carrier image
result = validate_image(carrier_data, "Carrier image") result = validate_image(carrier_data, "Carrier image")
if not result.is_valid: if not result.is_valid:
flash(result.error_message, "error") return _error_response(result.error_message)
return render_template("encode.html", has_qrcode_read=HAS_QRCODE_READ)
# Pre-check payload capacity BEFORE encode (fail fast) # Pre-check payload capacity BEFORE encode (fail fast)
from stegasoo.steganography import will_fit_by_mode from stegasoo.steganography import will_fit_by_mode
@@ -1273,8 +1268,7 @@ def encode_page():
alt_check = will_fit_by_mode(payload_size, carrier_data, embed_mode="lsb") alt_check = will_fit_by_mode(payload_size, carrier_data, embed_mode="lsb")
if alt_check.get("fits"): if alt_check.get("fits"):
error_msg += " - Try LSB mode instead." error_msg += " - Try LSB mode instead."
flash(error_msg, "error") return _error_response(error_msg)
return render_template("encode.html", has_qrcode_read=HAS_QRCODE_READ)
# Build encode params for either sync or async # Build encode params for either sync or async
encode_params = { encode_params = {
@@ -1375,14 +1369,11 @@ def encode_page():
return redirect(url_for("encode_result", file_id=file_id)) return redirect(url_for("encode_result", file_id=file_id))
except CapacityError as e: except CapacityError as e:
flash(str(e), "error") return _error_response(str(e))
return render_template("encode.html", has_qrcode_read=HAS_QRCODE_READ)
except StegasooError as e: except StegasooError as e:
flash(str(e), "error") return _error_response(str(e))
return render_template("encode.html", has_qrcode_read=HAS_QRCODE_READ)
except Exception as e: except Exception as e:
flash(f"Error: {e}", "error") return _error_response(f"Error: {e}")
return render_template("encode.html", has_qrcode_read=HAS_QRCODE_READ)
return render_template("encode.html", has_qrcode_read=HAS_QRCODE_READ) return render_template("encode.html", has_qrcode_read=HAS_QRCODE_READ)