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