broadfield-dev commited on
Commit
8959b8d
·
verified ·
1 Parent(s): 28d22ac

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +197 -0
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)