"""Cloud import endpoints for the ebook library. These let the browser browse a user-configured WebDAV server and pull one book out of it. The fetch is proxied through the server rather than done in the browser because the WebDAV host is a different origin and will not send CORS headers for it. That proxy is a deliberate, narrow relaxation of the same rule the ISBN lookup in books/views.py bends: the server sees the file's bytes in memory for the duration of one request, but never persists them. The book is encrypted in the browser and only then POSTed to /books/upload/ as ciphertext, exactly like a local upload — nothing readable is ever stored server-side. """ from django.conf import settings from django.http import HttpResponse, JsonResponse from django.utils import timezone from django.views.decorators.http import require_http_methods from accounts.models import WebDAVSource from accounts.webdav import WebDAVError, WebDAVInputError, list_directory, fetch_file from .views import _require_auth def _get_source(request, pk): return WebDAVSource.objects.filter(pk=pk, user=request.user).first() @require_http_methods(['GET']) def cloud_sources(request): unauthorized = _require_auth(request) if unauthorized: return unauthorized sources = request.user.webdav_sources.all() return JsonResponse({'sources': [ {'id': s.id, 'label': s.label, 'host': s.base_url} for s in sources ]}) @require_http_methods(['GET']) def cloud_browse(request, pk): unauthorized = _require_auth(request) if unauthorized: return unauthorized source = _get_source(request, pk) if not source: return JsonResponse({'error': 'unknown source'}, status=404) path = request.GET.get('path', '') try: entries = list_directory(source, path) except WebDAVInputError as exc: return JsonResponse({'error': str(exc)}, status=400) except WebDAVError as exc: return JsonResponse({'error': str(exc)}, status=502) source.last_used_at = timezone.now() source.save(update_fields=['last_used_at']) return JsonResponse({'ok': True, 'path': path, 'entries': entries}) @require_http_methods(['GET']) def cloud_fetch(request, pk): unauthorized = _require_auth(request) if unauthorized: return unauthorized source = _get_source(request, pk) if not source: return JsonResponse({'error': 'unknown source'}, status=404) path = request.GET.get('path', '') try: payload = fetch_file(source, path, settings.EBOOK_MAX_BYTES) except WebDAVInputError as exc: return JsonResponse({'error': str(exc)}, status=400) except WebDAVError as exc: return JsonResponse({'error': str(exc)}, status=502) response = HttpResponse(payload, content_type='application/octet-stream') # These are the book's plaintext bytes, on their way to be encrypted in the # browser. Keeping them out of the HTTP disk cache (and any reverse proxy # in front) is what makes "nothing readable is stored" true end to end. response['Cache-Control'] = 'no-store' return response