pyservx 1.0.3__py3-none-any.whl → 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyservx might be problematic. Click here for more details.

@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import os
4
+ import zipfile
5
+ from io import BytesIO
6
+
7
+ def format_size(size):
8
+ if size < 1024:
9
+ return f"{size} B"
10
+ elif size < 1024**2:
11
+ return f"{size / 1024:.2f} KB"
12
+ elif size < 1024**3:
13
+ return f"{size / (1024**2):.2f} MB"
14
+ else:
15
+ return f"{size / (1024**3):.2f} GB"
16
+
17
+ def zip_folder(folder_path):
18
+ memory_file = BytesIO()
19
+ with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_DEFLATED) as zipf:
20
+ for root, _, files in os.walk(folder_path):
21
+ for file in files:
22
+ abs_path = os.path.join(root, file)
23
+ rel_path = os.path.relpath(abs_path, folder_path)
24
+ zipf.write(abs_path, rel_path)
25
+ memory_file.seek(0)
26
+ return memory_file
27
+
28
+ def write_file_in_chunks(file_path, file_content, progress_callback=None):
29
+ total_size = len(file_content)
30
+ bytes_written = 0
31
+ chunk_size = 8192 # 8KB chunks
32
+
33
+ with open(file_path, 'wb') as f:
34
+ for i in range(0, total_size, chunk_size):
35
+ chunk = file_content[i:i + chunk_size]
36
+ f.write(chunk)
37
+ bytes_written += len(chunk)
38
+ if progress_callback:
39
+ progress_callback(bytes_written, total_size)
40
+
41
+ def read_file_in_chunks(file_path, chunk_size=8192, progress_callback=None):
42
+ file_size = os.path.getsize(file_path)
43
+ bytes_read = 0
44
+ with open(file_path, 'rb') as f:
45
+ while True:
46
+ chunk = f.read(chunk_size)
47
+ if not chunk:
48
+ break
49
+ bytes_read += len(chunk)
50
+ if progress_callback:
51
+ progress_callback(bytes_read, file_size)
52
+ yield chunk
@@ -0,0 +1,443 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import html
4
+ import os
5
+ import urllib.parse
6
+ import datetime
7
+
8
+ def format_size(size):
9
+ if size < 1024:
10
+ return f"{size} B"
11
+ elif size < 1024**2:
12
+ return f"{size / 1024:.2f} KB"
13
+ elif size < 1024**3:
14
+ return f"{size / (1024**2):.2f} MB"
15
+ else:
16
+ return f"{size / (1024**3):.2f} GB"
17
+
18
+ def list_directory_page(handler, path):
19
+ try:
20
+ entries = os.listdir(path)
21
+ except OSError:
22
+ handler.send_error(404, "Cannot list directory")
23
+ return None
24
+
25
+ query_params = urllib.parse.parse_qs(urllib.parse.urlparse(handler.path).query)
26
+ search_query = query_params.get('q', [''])[0]
27
+ sort_by = query_params.get('sort', ['name'])[0]
28
+ sort_order = query_params.get('order', ['asc'])[0]
29
+
30
+ if search_query:
31
+ entries = [e for e in entries if search_query.lower() in e.lower()]
32
+
33
+ def sort_key(item):
34
+ item_path = os.path.join(path, item)
35
+ if os.path.isdir(item_path):
36
+ return (0, item.lower()) # Directories first
37
+ if sort_by == 'size':
38
+ return (1, os.path.getsize(item_path))
39
+ elif sort_by == 'date':
40
+ return (1, os.path.getmtime(item_path))
41
+ else:
42
+ return (1, item.lower())
43
+
44
+ entries.sort(key=sort_key, reverse=sort_order == 'desc')
45
+
46
+ displaypath = html.escape(urllib.parse.unquote(handler.path))
47
+
48
+ # Build list items for directories and files
49
+ list_rows = []
50
+ # Parent directory link if not root
51
+ if handler.path != '/':
52
+ parent = os.path.dirname(handler.path.rstrip('/'))
53
+ if not parent.endswith('/'):
54
+ parent += '/'
55
+ list_rows.append(f"""
56
+ <tr class="hover:bg-green-900/20">
57
+ <td class="py-2 px-4 border-b border-green-700/50"><a href="{html.escape(parent)}" class="text-neon block">.. (Parent Directory)</a></td>
58
+ <td class="py-2 px-4 border-b border-green-700/50 text-right">-</td>
59
+ <td class="py-2 px-4 border-b border-green-700/50 text-right">-</td>
60
+ </tr>
61
+ """)
62
+
63
+ for name in entries:
64
+ fullpath = os.path.join(path, name)
65
+ displayname = name + '/' if os.path.isdir(fullpath) else name
66
+ href = urllib.parse.quote(name)
67
+ if os.path.isdir(fullpath):
68
+ href += '/'
69
+
70
+ size = "-"
71
+ date_modified = "-"
72
+ if os.path.isfile(fullpath):
73
+ size = format_size(os.path.getsize(fullpath))
74
+ date_modified = datetime.datetime.fromtimestamp(os.path.getmtime(fullpath)).strftime('%Y-%m-%d %H:%M:%S')
75
+
76
+ # Add download folder zip link for directories
77
+ if os.path.isdir(fullpath):
78
+ list_rows.append(
79
+ f"""
80
+ <tr class="hover:bg-green-900/20">
81
+ <td class="py-2 px-4 border-b border-green-700/50">
82
+ <a href="{href}" class="text-neon block">{html.escape(displayname)}</a>
83
+ </td>
84
+ <td class="py-2 px-4 border-b border-green-700/50 text-right">{size}</td>
85
+ <td class="py-2 px-4 border-b border-green-700/50 text-right">{date_modified}</td>
86
+ </tr>
87
+ <tr class="hover:bg-green-900/20">
88
+ <td class="py-2 px-4 border-b border-green-700/50" colspan="3">
89
+ <a href="{href}download_folder" class="text-neon block">📦 Zip Download</a>
90
+ </td>
91
+ </tr>
92
+ """
93
+ )
94
+ else:
95
+ list_rows.append(f"""
96
+ <tr class="hover:bg-green-900/20">
97
+ <td class="py-2 px-4 border-b border-green-700/50"><a href="{href}" class="text-neon block">{html.escape(displayname)}</a></td>
98
+ <td class="py-2 px-4 border-b border-green-700/50 text-right">{size}</td>
99
+ <td class="py-2 px-4 border-b border-green-700/50 text-right">{date_modified}</td>
100
+ </tr>
101
+ """)
102
+
103
+ list_html = '\n'.join(list_rows)
104
+
105
+ return f"""<!DOCTYPE html>
106
+ <html lang="en">
107
+ <head>
108
+ <meta charset="UTF-8" />
109
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
110
+ <title>PyServeX - Index of {displaypath}</title>
111
+ <script src="https://cdn.tailwindcss.com"></script>
112
+ <style>
113
+ @import url('https://fonts.googleapis.com/css2?family=VT323&display=swap');
114
+
115
+ body {{
116
+ font-family: 'VT323', monospace;
117
+ background: #000000;
118
+ min-height: 100vh;
119
+ margin: 0;
120
+ overflow-x: hidden;
121
+ }}
122
+
123
+ .text-neon {{
124
+ color: #00ff00;
125
+ }}
126
+
127
+ .typewriter h1 {{
128
+ overflow: hidden;
129
+ white-space: nowrap;
130
+ animation: typing 3s steps(40, end), blink-caret 0.5s step-end infinite;
131
+ margin: 0 auto;
132
+ text-align: center;
133
+ }}
134
+
135
+ @keyframes typing {{
136
+ from {{ width: 0; }}
137
+ to {{ width: 100%; }}
138
+ }}
139
+
140
+ @keyframes blink-caret {{
141
+ from, to {{ border-right: 2px solid #00ff00; }}
142
+ 50% {{ border-right: 2px solid transparent; }}
143
+ }}
144
+
145
+ .glitch {{
146
+ position: relative;
147
+ animation: glitch 2s infinite;
148
+ }}
149
+
150
+ @keyframes glitch {{
151
+ 0% {{ transform: translate(0); }}
152
+ 10% {{ transform: translate(-2px, 2px); }}
153
+ 20% {{ transform: translate(2px, -2px); }}
154
+ 30% {{ transform: translate(-2px, 2px); }}
155
+ 40% {{ transform: translate(0); }}
156
+ 100% {{ transform: translate(0); }}
157
+ }}
158
+
159
+ .scanline {{
160
+ position: absolute;
161
+ top: 0;
162
+ left: 0;
163
+ width: 100%;
164
+ height: 100%;
165
+ background: linear-gradient(
166
+ to bottom,
167
+ rgba(255, 255, 255, 0),
168
+ rgba(255, 255, 255, 0.1) 50%,
169
+ rgba(255, 255, 255, 0)
170
+ );
171
+ animation: scan 4s linear infinite;
172
+ pointer-events: none;
173
+ }}
174
+
175
+ @keyframes scan {{
176
+ 0% {{ transform: translateY(-100%); }}
177
+ 100% {{ transform: translateY(100%); }}
178
+ }}
179
+
180
+ .particle {{
181
+ position: absolute;
182
+ width: 3px;
183
+ height: 3px;
184
+ background: #00ff00;
185
+ opacity: 0.5;
186
+ animation: flicker 3s infinite;
187
+ }}
188
+
189
+ @keyframes flicker {{
190
+ 0% {{ opacity: 0.5; }}
191
+ 50% {{ opacity: 0.1; }}
192
+ 100% {{ opacity: 0.5; }}
193
+ }}
194
+
195
+ main {{
196
+ margin-top: 100px; /* Adjust based on header height */
197
+ padding: 2rem;
198
+ color: #00ff00;
199
+ text-align: left;
200
+ max-width: 900px;
201
+ margin-left: auto;
202
+ margin-right: auto;
203
+ }}
204
+
205
+ ul {{
206
+ list-style-type: none;
207
+ padding-left: 0;
208
+ }}
209
+
210
+ li {{
211
+ margin-bottom: 0.7rem;
212
+ font-size: 1.2rem;
213
+ }}
214
+
215
+ a {{
216
+ text-decoration: none;
217
+ }}
218
+
219
+ a:hover {{
220
+ text-decoration: underline;
221
+ }}
222
+
223
+ .upload-form, .search-form {{
224
+ margin-top: 1.5rem;
225
+ padding: 1rem;
226
+ border: 1px solid #00ff00;
227
+ border-radius: 5px;
228
+ }}
229
+
230
+ .upload-form label, .search-form label {{
231
+ display: block;
232
+ margin-bottom: 0.5rem;
233
+ }}
234
+
235
+ .upload-form input[type="file"], .search-form input[type="text"] {{
236
+ color: #00ff00;
237
+ background: #000000;
238
+ border: 1px solid #00ff00;
239
+ padding: 0.5rem;
240
+ }}
241
+
242
+ .upload-form button, .search-form button {{
243
+ background: #00ff00;
244
+ color: #000000;
245
+ padding: 0.5rem 1rem;
246
+ border: none;
247
+ cursor: pointer;
248
+ font-family: 'VT323', monospace;
249
+ font-size: 1.2rem;
250
+ }}
251
+
252
+ .upload-form button:hover, .search-form button:hover {{
253
+ background: #00cc00;
254
+ }}
255
+
256
+ table {{
257
+ width: 100%;
258
+ border-collapse: collapse;
259
+ margin-top: 1.5rem;
260
+ }}
261
+
262
+ th, td {{
263
+ text-align: left;
264
+ padding: 0.75rem;
265
+ border-bottom: 1px solid rgba(0, 255, 0, 0.3);
266
+ }}
267
+
268
+ th {{
269
+ background-color: rgba(0, 255, 0, 0.1);
270
+ color: #00ff00;
271
+ font-weight: normal;
272
+ cursor: pointer;
273
+ }}
274
+
275
+ th:hover {{
276
+ background-color: rgba(0, 255, 0, 0.2);
277
+ }}
278
+
279
+ tr:nth-child(even) {{
280
+ background-color: rgba(0, 255, 0, 0.05);
281
+ }}
282
+
283
+ /* Progress bar and popup styles */
284
+ #progressBarContainer {{
285
+ display: none;
286
+ margin-top: 1rem;
287
+ background-color: #333;
288
+ border: 1px solid #00ff00;
289
+ padding: 0.5rem;
290
+ border-radius: 5px;
291
+ }}
292
+
293
+ #progressBar {{
294
+ width: 0%;
295
+ height: 20px;
296
+ background-color: #00ff00;
297
+ text-align: center;
298
+ line-height: 20px;
299
+ color: #000;
300
+ font-size: 0.8rem;
301
+ }}
302
+
303
+ #progressText {{
304
+ color: #00ff00;
305
+ margin-top: 0.5rem;
306
+ font-size: 0.9rem;
307
+ }}
308
+
309
+ #successPopup {{
310
+ display: none;
311
+ position: fixed;
312
+ top: 20px;
313
+ left: 50%;
314
+ transform: translateX(-50%);
315
+ background-color: #00ff00;
316
+ color: #000;
317
+ padding: 1rem 2rem;
318
+ border-radius: 5px;
319
+ font-size: 1.2rem;
320
+ z-index: 1000;
321
+ box-shadow: 0 0 10px rgba(0, 255, 0, 0.5);
322
+ }}
323
+ </style>
324
+ </head>
325
+ <body>
326
+ <div class="scanline"></div>
327
+ <header>
328
+ <div class="text-center">
329
+ <h1 class="text-4xl md:text-6xl text-neon typewriter glitch">PyServeX</h1>
330
+ </div>
331
+ </header>
332
+ <main>
333
+ <h2 class="text-3xl mb-4 text-neon">Index of {displaypath}</h2>
334
+ <div class="search-form mb-6 p-4 border border-green-700/50 rounded-lg">
335
+ <form action="{html.escape(handler.path)}" method="GET" class="flex flex-col sm:flex-row items-center space-y-2 sm:space-y-0 sm:space-x-2">
336
+ <input type="text" name="q" placeholder="Search files..." value="{html.escape(search_query)}" class="flex-grow p-2 bg-black text-neon border border-green-700/50 rounded-md focus:outline-none focus:ring-1 focus:ring-green-500">
337
+ <button type="submit" class="bg-green-500 text-black py-2 px-4 rounded-md hover:bg-green-600 transition-colors duration-200">Search</button>
338
+ </form>
339
+ </div>
340
+ <div class="overflow-x-auto">
341
+ <table class="min-w-full bg-black border border-green-700/50 rounded-lg">
342
+ <thead>
343
+ <tr>
344
+ <th class="py-3 px-4 border-b border-green-700/50 text-left text-neon">
345
+ <a href="?sort=name&order={{'desc' if sort_by == 'name' and sort_order == 'asc' else 'asc'}}" class="block">Name</a>
346
+ </th>
347
+ <th class="py-3 px-4 border-b border-green-700/50 text-right text-neon">
348
+ <a href="?sort=size&order={{'desc' if sort_by == 'size' and sort_order == 'asc' else 'asc'}}" class="block">Size</a>
349
+ </th>
350
+ <th class="py-3 px-4 border-b border-green-700/50 text-right text-neon">
351
+ <a href="?sort=date&order={{'desc' if sort_by == 'date' and sort_order == 'asc' else 'asc'}}" class="block">Date Modified</a>
352
+ </th>
353
+ </tr>
354
+ </thead>
355
+ <tbody>
356
+ {list_html}
357
+ </tbody>
358
+ </table>
359
+ </div>
360
+ <div class="upload-form mt-6 p-4 border border-green-700/50 rounded-lg">
361
+ <form id="uploadForm" action="{html.escape(handler.path)}upload" method="POST" enctype="multipart/form-data" class="flex flex-col sm:flex-row items-center space-y-2 sm:space-y-0 sm:space-x-2">
362
+ <label for="file-upload" class="text-neon">Upload files:</label>
363
+ <input type="file" id="file-upload" name="file" multiple class="flex-grow p-2 bg-black text-neon border border-green-700/50 rounded-md focus:outline-none focus:ring-1 focus:ring-green-500" />
364
+ <button type="submit" class="bg-green-500 text-black py-2 px-4 rounded-md hover:bg-green-600 transition-colors duration-200">Upload</button>
365
+ </form>
366
+ <div id="progressBarContainer">
367
+ <div id="progressBar"></div>
368
+ <div id="progressText"></div>
369
+ </div>
370
+ </div>
371
+ </main>
372
+
373
+ <div id="successPopup"></div>
374
+
375
+ <script>
376
+ // Generate random particles for hacker effect
377
+ function createParticles() {{
378
+ const numParticles = 30;
379
+ for (let i = 0; i < numParticles; i++) {{
380
+ const particle = document.createElement('div');
381
+ particle.classList.add('particle');
382
+ particle.style.left = `${{Math.random() * 100}}vw`;
383
+ particle.style.top = `${{Math.random() * 100}}vh`;
384
+ particle.style.animationDelay = `${{Math.random() * 3}}s`;
385
+ document.body.appendChild(particle);
386
+ }}
387
+ }}
388
+
389
+ // Handle file uploads with progress bar
390
+ document.getElementById('uploadForm').addEventListener('submit', function(e) {{
391
+ e.preventDefault();
392
+
393
+ const formData = new FormData(this);
394
+ const xhr = new XMLHttpRequest();
395
+
396
+ const progressBarContainer = document.getElementById('progressBarContainer');
397
+ const progressBar = document.getElementById('progressBar');
398
+ const progressText = document.getElementById('progressText');
399
+ const successPopup = document.getElementById('successPopup');
400
+
401
+ progressBarContainer.style.display = 'block';
402
+ progressBar.style.width = '0%';
403
+ progressText.textContent = 'Uploading...';
404
+
405
+ xhr.upload.addEventListener('progress', function(event) {{
406
+ if (event.lengthComputable) {{
407
+ const percent = (event.loaded / event.total) * 100;
408
+ progressBar.style.width = percent.toFixed(2) + '%';
409
+ progressText.textContent = `Uploading: ${{percent.toFixed(2)}}%`;
410
+ }}
411
+ }});
412
+
413
+ xhr.addEventListener('load', function() {{
414
+ progressBarContainer.style.display = 'none';
415
+ if (xhr.status === 200) {{
416
+ const response = JSON.parse(xhr.responseText);
417
+ successPopup.textContent = response.message;
418
+ successPopup.style.display = 'block';
419
+ setTimeout(() => {{
420
+ successPopup.style.display = 'none';
421
+ window.location.reload(); // Reload page to show new files
422
+ }}, 2000);
423
+ }} else {{
424
+ progressText.textContent = 'Upload failed!';
425
+ alert('Upload failed: ' + xhr.statusText);
426
+ }}
427
+ }});
428
+
429
+ xhr.addEventListener('error', function() {{
430
+ progressBarContainer.style.display = 'none';
431
+ progressText.textContent = 'Upload failed!';
432
+ alert('Upload failed due to a network error.');
433
+ }});
434
+
435
+ xhr.open('POST', this.action);
436
+ xhr.send(formData);
437
+ }});
438
+
439
+ window.onload = createParticles;
440
+ </script>
441
+ </body>
442
+ </html>
443
+ """
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import http.server
4
+ import os
5
+ import posixpath
6
+ import urllib.parse
7
+ import shutil
8
+ import logging
9
+ import json
10
+ import time
11
+ from . import html_generator
12
+ from . import file_operations
13
+
14
+ class FileRequestHandler(http.server.SimpleHTTPRequestHandler):
15
+ def translate_path(self, path):
16
+ # Prevent path traversal attacks
17
+ path = posixpath.normpath(urllib.parse.unquote(path))
18
+ rel_path = path.lstrip('/')
19
+ abs_path = os.path.abspath(os.path.join(self.base_dir, rel_path))
20
+ if not abs_path.startswith(self.base_dir):
21
+ logging.warning(f"Path traversal attempt detected: {path}")
22
+ return self.base_dir # Prevent access outside the base directory
23
+ return abs_path
24
+
25
+ def do_GET(self):
26
+ if self.path.endswith('/download_folder'):
27
+ folder_path = self.translate_path(self.path.replace('/download_folder', ''))
28
+ if os.path.isdir(folder_path):
29
+ zip_file = file_operations.zip_folder(folder_path)
30
+ self.send_response(200)
31
+ self.send_header("Content-Type", "application/zip")
32
+ self.send_header("Content-Disposition", f"attachment; filename={os.path.basename(folder_path)}.zip")
33
+ self.end_headers()
34
+ shutil.copyfileobj(zip_file, self.wfile)
35
+ else:
36
+ self.send_error(404, "Folder not found")
37
+ return
38
+
39
+ if os.path.isdir(self.translate_path(self.path)):
40
+ self.list_directory(self.translate_path(self.path))
41
+ else:
42
+ # Handle file downloads with progress tracking
43
+ path = self.translate_path(self.path)
44
+ if os.path.isfile(path):
45
+ try:
46
+ file_size = os.path.getsize(path)
47
+ self.send_response(200)
48
+ self.send_header("Content-type", self.guess_type(path))
49
+ self.send_header("Content-Length", str(file_size))
50
+ self.end_headers()
51
+
52
+ start_time = time.time()
53
+ for chunk in file_operations.read_file_in_chunks(path):
54
+ self.wfile.write(chunk)
55
+ end_time = time.time()
56
+ duration = end_time - start_time
57
+ speed_bps = file_size / duration if duration > 0 else 0
58
+ logging.info(f"Downloaded {os.path.basename(path)} ({file_operations.format_size(file_size)}) in {duration:.2f}s at {file_operations.format_size(speed_bps)}/s")
59
+
60
+ except OSError:
61
+ self.send_error(404, "File not found")
62
+ else:
63
+ super().do_GET()
64
+
65
+ def do_POST(self):
66
+ if self.path.endswith('/upload'):
67
+ content_length = int(self.headers.get('Content-Length', 0))
68
+
69
+ # Parse multipart form data
70
+ content_type = self.headers.get('Content-Type', '')
71
+ if not content_type.startswith('multipart/form-data'):
72
+ self.send_error(400, "Invalid content type")
73
+ return
74
+
75
+ boundary = content_type.split('boundary=')[1].encode()
76
+ body = self.rfile.read(content_length)
77
+
78
+ # Simple parsing of multipart form data
79
+ parts = body.split(b'--' + boundary)
80
+ uploaded_files = []
81
+ for part in parts:
82
+ if b'filename="' in part:
83
+ # Extract filename
84
+ start = part.find(b'filename="') + 10
85
+ end = part.find(b'"', start)
86
+ filename = part[start:end].decode('utf-8')
87
+ # Sanitize filename
88
+ filename = os.path.basename(filename)
89
+ if not filename:
90
+ continue
91
+
92
+ # Extract file content
93
+ content_start = part.find(b'\r\n\r\n') + 4
94
+ content_end = part.rfind(b'\r\n--' + boundary)
95
+ if content_end == -1:
96
+ content_end = len(part) - 2
97
+ file_content = part[content_start:content_end]
98
+
99
+ # Save file to the target directory
100
+ target_dir = self.translate_path(self.path.replace('/upload', ''))
101
+ if not os.path.isdir(target_dir):
102
+ self.send_error(404, "Target directory not found")
103
+ return
104
+
105
+ file_path = os.path.join(target_dir, filename)
106
+ try:
107
+ start_time = time.time()
108
+ file_operations.write_file_in_chunks(file_path, file_content)
109
+ end_time = time.time()
110
+ duration = end_time - start_time
111
+ file_size_bytes = len(file_content)
112
+ speed_bps = file_size_bytes / duration if duration > 0 else 0
113
+
114
+ logging.info(f"Uploaded {filename} ({file_operations.format_size(file_size_bytes)}) in {duration:.2f}s at {file_operations.format_size(speed_bps)}/s")
115
+ uploaded_files.append(filename)
116
+ except OSError:
117
+ self.send_error(500, "Error saving file")
118
+ return
119
+
120
+ if not uploaded_files:
121
+ self.send_error(400, "No file provided")
122
+ return
123
+
124
+ # Log the upload and redirect URL
125
+ redirect_url = self.path.replace('/upload', '') or '/'
126
+ logging.info(f"Files uploaded: {', '.join(uploaded_files)} to {target_dir}")
127
+ logging.info(f"Redirecting to: {redirect_url}")
128
+
129
+ self.send_response(200)
130
+ self.send_header("Content-type", "application/json")
131
+ self.end_headers()
132
+ response_data = {"status": "success", "message": "Files uploaded successfully!"}
133
+ self.wfile.write(json.dumps(response_data).encode('utf-8'))
134
+ return
135
+ else:
136
+ self.send_error(405, "Method not allowed")
137
+
138
+ def list_directory(self, path):
139
+ html_content = html_generator.list_directory_page(self, path)
140
+ encoded = html_content.encode('utf-8', 'surrogateescape')
141
+ self.send_response(200)
142
+ self.send_header("Content-type", "text/html; charset=utf-8")
143
+ self.send_header("Content-Length", str(len(encoded)))
144
+ self.end_headers()
145
+ self.wfile.write(encoded)
146
+ return