90 lines
3.1 KiB
Python
90 lines
3.1 KiB
Python
|
|
"""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
|