diora-web/books/urls.py

21 lines
1.1 KiB
Python
Raw Normal View History

from django.urls import path
Bücher: Cloud-Import aus WebDAV/Nextcloud (SW v41) Verbindungen werden pro Nutzer in den Einstellungen angelegt (Nextcloud, ownCloud, Synology oder generisches WebDAV) — der Server ist bewusst nicht auf eine feste Instanz verdrahtet. Im Bücher-Tab lässt sich der entfernte Ordner durchblättern und eine .epub/.pdf direkt in die Bibliothek ziehen. Der Download läuft über den Server, weil der WebDAV-Host cross-origin ist und keine CORS-Header schickt. Verschlüsselt wird trotzdem erst im Browser: uploadEbook ist in _importEbookBuffer aufgeteilt, das sich lokaler Upload und Cloud-Import teilen. Gespeichert wird wie bisher nur Geheimtext. Weil jeder registrierte Nutzer die Ziel-URL bestimmt und diora im Docker-Netz neben anderen Diensten läuft, ist der Import eine SSRF-Fläche. Dagegen: - assert_safe_url weist Hosts ab, die auf nicht-öffentliche Adressen auflösen (inkl. NAT64 und IPv4-kompatibler v6-Adressen, die is_global durchlässt) - _assert_peer_is_safe prüft die tatsächliche Peer-Adresse nach dem Verbinden — requests löst den Namen ein zweites Mal auf, sonst wäre der Guard per DNS-Rebinding umgehbar - Redirects werden abgelehnt statt verfolgt - identische Fehlermeldung für "nicht auflösbar" und "privat", ohne die IP zu nennen, damit der Endpunkt kein Scanner für interne Dienste wird Antwort-Bodies laufen durch _read_capped, und DTDs werden vor dem Parsen abgewiesen: ElementTree expandiert interne Entities, und seit Python 3.12 gibt es XMLParser.parser nicht mehr, um einen Handler zu setzen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 08:26:22 +00:00
from . import views, webdav
urlpatterns = [
path('', views.book_list, name='book_list'),
path('upload/', views.upload_book, name='upload_book'),
Bücher: Cloud-Import aus WebDAV/Nextcloud (SW v41) Verbindungen werden pro Nutzer in den Einstellungen angelegt (Nextcloud, ownCloud, Synology oder generisches WebDAV) — der Server ist bewusst nicht auf eine feste Instanz verdrahtet. Im Bücher-Tab lässt sich der entfernte Ordner durchblättern und eine .epub/.pdf direkt in die Bibliothek ziehen. Der Download läuft über den Server, weil der WebDAV-Host cross-origin ist und keine CORS-Header schickt. Verschlüsselt wird trotzdem erst im Browser: uploadEbook ist in _importEbookBuffer aufgeteilt, das sich lokaler Upload und Cloud-Import teilen. Gespeichert wird wie bisher nur Geheimtext. Weil jeder registrierte Nutzer die Ziel-URL bestimmt und diora im Docker-Netz neben anderen Diensten läuft, ist der Import eine SSRF-Fläche. Dagegen: - assert_safe_url weist Hosts ab, die auf nicht-öffentliche Adressen auflösen (inkl. NAT64 und IPv4-kompatibler v6-Adressen, die is_global durchlässt) - _assert_peer_is_safe prüft die tatsächliche Peer-Adresse nach dem Verbinden — requests löst den Namen ein zweites Mal auf, sonst wäre der Guard per DNS-Rebinding umgehbar - Redirects werden abgelehnt statt verfolgt - identische Fehlermeldung für "nicht auflösbar" und "privat", ohne die IP zu nennen, damit der Endpunkt kein Scanner für interne Dienste wird Antwort-Bodies laufen durch _read_capped, und DTDs werden vor dem Parsen abgewiesen: ElementTree expandiert interne Entities, und seit Python 3.12 gibt es XMLParser.parser nicht mehr, um einen Handler zu setzen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 08:26:22 +00:00
path('cloud/sources/', webdav.cloud_sources, name='cloud_sources'),
path('cloud/<int:pk>/browse/', webdav.cloud_browse, name='cloud_browse'),
path('cloud/<int:pk>/fetch/', webdav.cloud_fetch, name='cloud_fetch'),
path('metadata-lookup/', views.lookup_book_metadata, name='lookup_book_metadata'),
path('<int:pk>/data/', views.get_book_data, name='get_book_data'),
path('<int:pk>/delete/', views.delete_book, name='delete_book'),
path('<int:pk>/read/', views.set_book_read, name='set_book_read'),
path('<int:pk>/meta/', views.update_book_meta, name='update_book_meta'),
path('<int:pk>/replace-data/', views.replace_book_data, name='replace_book_data'),
path('<int:pk>/rekey/', views.rekey_book, name='rekey_book'),
path('<int:pk>/progress/', views.save_progress, name='save_book_progress'),
path('<int:pk>/highlights/', views.book_highlights, name='book_highlights'),
path('<int:pk>/bookmarks/', views.book_bookmarks, name='book_bookmarks'),
]