feat(dashboard): endpoint de download fișiere binare + tool epub-to-audio
- dashboard/handlers/files.py: handle_files_download() servește mp3/wav/zip din WORKSPACE_DIR cu Content-Disposition attachment, resolve dedicat (nu _resolve_sandboxed, care nu ajunge niciodată la WORKSPACE_DIR) - dashboard/api.py: rutează /api/files/download - tools/epub_to_audio.py: tool nou de conversie EPUB → audio - cron/jobs.json, memory/kb/index.json: stare auto-generată (job runs, regenerare index KB) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -72,6 +72,42 @@ class FilesHandlers:
|
||||
except Exception as e:
|
||||
self.send_json({'error': str(e)}, 500)
|
||||
|
||||
def handle_files_download(self):
|
||||
"""Stream a binary file (audio, zip, etc.) from WORKSPACE_DIR as a download.
|
||||
|
||||
Dedicated resolution (not `_resolve_sandboxed`, which always resolves
|
||||
against ALLOWED_WORKSPACES[0]/BASE_DIR and never reaches WORKSPACE_DIR)
|
||||
so this stays isolated from the shared file-browser sandbox logic.
|
||||
"""
|
||||
params = parse_qs(urlparse(self.path).query)
|
||||
path = params.get('path', [''])[0]
|
||||
|
||||
try:
|
||||
target = (constants.WORKSPACE_DIR / path).resolve()
|
||||
target.relative_to(constants.WORKSPACE_DIR.resolve())
|
||||
except (ValueError, OSError):
|
||||
self.send_json({'error': 'Access denied'}, 403)
|
||||
return
|
||||
if not target.is_file():
|
||||
self.send_json({'error': 'Not found'}, 404)
|
||||
return
|
||||
|
||||
ext = target.suffix.lstrip('.').lower()
|
||||
ctype = {
|
||||
'mp3': 'audio/mpeg',
|
||||
'wav': 'audio/wav',
|
||||
'zip': 'application/zip',
|
||||
}.get(ext, 'application/octet-stream')
|
||||
|
||||
data = target.read_bytes()
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', ctype)
|
||||
self.send_header('Content-Length', str(len(data)))
|
||||
self.send_header('Content-Disposition', f'attachment; filename="{target.name}"')
|
||||
self.send_header('Cache-Control', 'public, max-age=3600')
|
||||
self.end_headers()
|
||||
self.wfile.write(data)
|
||||
|
||||
def handle_files_post(self):
|
||||
"""Save file content."""
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user