MCPcopy Create free account
hub / github.com/Dispatcharr/Dispatcharr / download_backup

Function download_backup

apps/backups/api_views.py:156–231  ·  view source on GitHub ↗

Download a backup file. Requires either: - Valid admin authentication, OR - Valid download_token query parameter

(request, filename)

Source from the content-addressed store, hash-verified

154@api_view(["GET"])
155@permission_classes([AllowAny])
156def download_backup(request, filename):
157 """Download a backup file.
158
159 Requires either:
160 - Valid admin authentication, OR
161 - Valid download_token query parameter
162 """
163 # Check for token-based auth (avoids CORS preflight issues)
164 token = request.query_params.get("token")
165 if token:
166 if not _verify_task_token(filename, token):
167 return Response(
168 {"detail": "Invalid download token"},
169 status=status.HTTP_403_FORBIDDEN,
170 )
171 else:
172 # Fall back to admin auth check
173 if not request.user.is_authenticated or getattr(request.user, 'user_level', 0) < 10:
174 return Response(
175 {"detail": "Authentication required"},
176 status=status.HTTP_401_UNAUTHORIZED,
177 )
178
179 try:
180 # Security: prevent path traversal by checking for suspicious characters
181 if ".." in filename or "/" in filename or "\\" in filename:
182 raise Http404("Invalid filename")
183
184 backup_dir = services.get_backup_dir()
185 backup_file = (backup_dir / filename).resolve()
186
187 # Security: ensure the resolved path is still within backup_dir
188 if not str(backup_file).startswith(str(backup_dir.resolve())):
189 raise Http404("Invalid filename")
190
191 if not backup_file.exists() or not backup_file.is_file():
192 raise Http404("Backup file not found")
193
194 file_size = backup_file.stat().st_size
195
196 # Use X-Accel-Redirect for nginx (AIO container) - nginx serves file directly
197 # Fall back to streaming for non-nginx deployments
198 use_nginx_accel = os.environ.get("USE_NGINX_ACCEL", "").lower() == "true"
199 logger.info(f"[DOWNLOAD] File: {filename}, Size: {file_size}, USE_NGINX_ACCEL: {use_nginx_accel}")
200
201 if use_nginx_accel:
202 # X-Accel-Redirect: Django returns immediately, nginx serves file
203 logger.info(f"[DOWNLOAD] Using X-Accel-Redirect: /protected-backups/{filename}")
204 response = HttpResponse()
205 response["X-Accel-Redirect"] = f"/protected-backups/{filename}"
206 response["Content-Type"] = "application/zip"
207 response["Content-Length"] = file_size
208 response["Content-Disposition"] = f'attachment; filename="{filename}"'
209 return response
210 else:
211 # Streaming fallback for non-nginx deployments
212 logger.info(f"[DOWNLOAD] Using streaming fallback (no nginx)")
213 def file_iterator(file_path, chunk_size=2 * 1024 * 1024):

Callers

nothing calls this directly

Calls 4

_verify_task_tokenFunction · 0.85
file_iteratorFunction · 0.85
getMethod · 0.45
existsMethod · 0.45

Tested by

no test coverage detected