Spaces:
Sleeping
Sleeping
File size: 7,823 Bytes
8959b8d c93b3cb 8959b8d 7e95396 8959b8d 7e95396 8959b8d 7e95396 8959b8d 7e95396 8959b8d 7e95396 8959b8d 7e95396 8959b8d 7e95396 8959b8d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
import os
import io
import gradio as gr
import json
import tempfile
import zipfile
from PIL import Image
# --- Required Core Logic ---
# Ensure keylock1 and repo_to_md are in the same directory or accessible in the Python path.
from keylock import core as keylock_core
from repo_to_md.core import markdown_to_files
# --- Helper Functions for the Decoder UI ---
def retrieve_and_process_files(stego_image_pil: Image.Image, password: str):
"""
Extracts data from an image and prepares the file browser UI components for update.
This version handles cases where the password is an empty string.
"""
# Initial state for all UI components
status = "An error occurred."
file_data_state, file_buffers_state = [], {}
file_browser_visibility = gr.update(visible=False)
filename_choices = gr.update(choices=[], value=None)
accordion_update = gr.update(label="File Content")
code_update = gr.update(value="")
if stego_image_pil is None:
status = "Error: Please upload or paste an image."
return status, file_data_state, file_buffers_state, file_browser_visibility, filename_choices, accordion_update, code_update
# The check for a mandatory password has been removed. An empty string is now a valid input.
try:
# Step 1: Extract and decrypt data from the image.
# The password (which can be empty) is passed directly to the core function.
extracted_data = keylock_core.extract_data_from_image(stego_image_pil.convert("RGB"))
decrypted_bytes = keylock_core.decrypt_data(extracted_data, password)
# Step 2: Decode the extracted data (JSON or plain text)
try:
data = json.loads(decrypted_bytes.decode('utf-8'))
md_output = data.get('repo_md', json.dumps(data, indent=2))
status = f"Success! Data extracted for repo: {data.get('repo_name', 'N/A')}"
except (json.JSONDecodeError, UnicodeDecodeError):
md_output = decrypted_bytes.decode('utf-8', errors='ignore')
status = "Warning: Decrypted data was not valid JSON, but was decoded as text."
# Step 3: Process the markdown content into files
file_data, file_buffers = markdown_to_files(md_output)
if isinstance(file_data, str):
status = f"Extraction successful, but file parsing failed: {file_data}"
return status, [], {}, file_browser_visibility, filename_choices, accordion_update, code_update
# Step 4: Prepare the UI updates
file_data_state, file_buffers_state = file_data, file_buffers
filenames = [f['filename'] for f in file_data]
if filenames:
file_browser_visibility = gr.update(visible=True)
filename_choices = gr.update(choices=filenames, value=filenames[0])
first_file = file_data[0]
accordion_update = gr.update(label=f"File Content: {first_file['filename']}")
code_update = gr.update(value=first_file['content'])
else:
status += " (No files were found in the decoded content)."
except ValueError:
status = "Error: Failed to decrypt. This usually means the password is wrong (or was required but not provided)."
except Exception as e:
status = f"An unexpected error occurred: {e}"
return status, file_data_state, file_buffers_state, file_browser_visibility, filename_choices, accordion_update, code_update
def update_file_preview(selected_filename, file_data):
"""Updates the file content preview when a new file is selected from the dropdown."""
if not selected_filename or not file_data:
return gr.update(label="File Content"), gr.update(value="")
selected_file = next((f for f in file_data if f['filename'] == selected_filename), None)
if selected_file:
return gr.update(label=f"File Content: {selected_file['filename']}"), gr.update(value=selected_file['content'])
return gr.update(label="File Content"), gr.update(value="Error: File not found in state.")
def download_all_zip(buffers):
"""Creates a zip file from the file buffers and returns its path for download."""
if not buffers:
return None
with tempfile.NamedTemporaryFile(delete=False, suffix=".zip", prefix="decoded_files_") as tmp:
with zipfile.ZipFile(tmp.name, "w", zipfile.ZIP_DEFLATED) as zf:
for filename, content in buffers.items():
zf.writestr(filename, content)
return tmp.name
# --- GRADIO UI DEFINITION ---
with gr.Blocks(theme=gr.themes.Soft(), title="KeyLock Decoder") as demo:
gr.Markdown("# KeyLock Image Decoder")
gr.Markdown("Upload or paste your KeyLock image, enter the password (if one was used), and click 'Extract Files' to view and download the contents.")
# State variables to hold data between user interactions
file_data_state = gr.State([])
file_buffers_state = gr.State({})
with gr.Row():
with gr.Column(scale=1):
# --- INPUTS ---
extract_stego_image_upload = gr.Image(
label="Upload or Paste KeyLock Image",
type="pil",
sources=["upload", "clipboard"]
)
extract_password_input = gr.Textbox(
label="Decryption Password (Optional)",
type="password",
placeholder="Leave blank if no password was set",
info="If a password was used during encryption, it is required here."
)
extract_button = gr.Button(
value="Extract Files",
variant="primary"
)
with gr.Column(scale=2):
# --- OUTPUTS ---
extract_output_status = gr.Textbox(
label="Extraction Status",
interactive=False,
placeholder="Status messages will appear here..."
)
with gr.Column(visible=False) as file_browser_ui:
gr.Markdown("--- \n ### Decoded Files")
download_all_zip_btn = gr.Button("Download All as .zip")
file_selector_dd = gr.Dropdown(
label="Select a file to preview",
interactive=True
)
with gr.Accordion("File Content", open=True) as file_preview_accordion:
file_preview_code = gr.Code(
language="markdown", # Will auto-detect based on file in many cases
interactive=False,
label="File Preview"
)
# Hidden component to handle the zip download
download_zip_output = gr.File(
label="Download .zip file",
interactive=False
)
# --- EVENT HANDLERS ---
extract_button.click(
fn=retrieve_and_process_files,
inputs=[
extract_stego_image_upload,
extract_password_input
],
outputs=[
extract_output_status,
file_data_state,
file_buffers_state,
file_browser_ui,
file_selector_dd,
file_preview_accordion,
file_preview_code
]
)
file_selector_dd.change(
fn=update_file_preview,
inputs=[
file_selector_dd,
file_data_state
],
outputs=[
file_preview_accordion,
file_preview_code
]
)
download_all_zip_btn.click(
fn=download_all_zip,
inputs=[file_buffers_state],
outputs=[download_zip_output]
)
if __name__ == "__main__":
demo.launch(debug=True) |