Skip to content

Instantly share code, notes, and snippets.

@t-book
Created June 25, 2026 13:09
Show Gist options
  • Select an option

  • Save t-book/cba464714f475011f44eb9ac7436a78d to your computer and use it in GitHub Desktop.

Select an option

Save t-book/cba464714f475011f44eb9ac7436a78d to your computer and use it in GitHub Desktop.
tonischonbuchner@MacBook-Pro-von-Toni ~/dev/github/csgis/QFieldCloud $% git diff
diff --git a/docker-app/qfieldcloud/filestorage/backend.py b/docker-app/qfieldcloud/filestorage/backend.py
index 7854ddcb..0b5aab18 100644
--- a/docker-app/qfieldcloud/filestorage/backend.py
+++ b/docker-app/qfieldcloud/filestorage/backend.py
@@ -25,12 +25,17 @@ class QfcBackendStorageMixin(ABC):
"Subclassing QFC specific storages must implement this method."
)
- def patch_nginx_download_redirect(self, response: HttpResponse) -> None:
+ def patch_nginx_download_redirect(
+ self, response: HttpResponse, content_disposition: str | None = None
+ ) -> None:
"""Patches a nginx redirect response for usage with the storage backend.
At the moment, does nothing.
Arguments:
response: HTTP redirect response to patch.
+ content_disposition: optional Content-Disposition header value to
+ forward to the client (used by backends that cannot set it on
+ the storage object itself, e.g. WebDAV).
"""
pass
@@ -51,12 +56,16 @@ class QfcS3Boto3Storage(QfcBackendStorageMixin, S3Storage):
return True
- def patch_nginx_download_redirect(self, response: HttpResponse) -> None:
+ def patch_nginx_download_redirect(
+ self, response: HttpResponse, content_disposition: str | None = None
+ ) -> None:
"""Patches a nginx redirect response for usage with S3.
- At the moment, does nothing.
+ Does nothing: S3 sets Content-Disposition via the presigned URL
+ response parameters, so no nginx-level forwarding is required.
Arguments:
response: HTTP redirect response to patch.
+ content_disposition: ignored for S3 (handled by presigned URL).
"""
pass
@@ -284,16 +293,22 @@ class QfcWebDavStorage(QfcBackendStorageMixin, Storage):
"""
return self.get_public_url(name)
- def patch_nginx_download_redirect(self, response: HttpResponse) -> None:
+ def patch_nginx_download_redirect(
+ self, response: HttpResponse, content_disposition: str | None = None
+ ) -> None:
"""Patches a nginx redirect response for usage with WebDAV.
Adds configured webdav/HTTP basic auth, required for nginx redirect.
+ Optionally forwards a Content-Disposition value to the client, since
+ WebDAV cannot set it on the stored object itself.
Arguments:
response: HTTP redirect response to patch.
+ content_disposition: optional Content-Disposition header value.
"""
b64_auth = base64.b64encode(self.basic_auth.encode()).decode()
- basic_auth = f"Basic {b64_auth}"
- response["webdav_auth"] = basic_auth
+ response["webdav_auth"] = f"Basic {b64_auth}"
+ if content_disposition:
+ response["webdav_content_disposition"] = content_disposition
def get_available_name(self, name: str, max_length: int | None = None) -> str:
"""Returns a filename that is available on the configured webdav storage.
diff --git a/docker-app/qfieldcloud/filestorage/view_helpers.py b/docker-app/qfieldcloud/filestorage/view_helpers.py
index 865d67f5..3ff6e323 100644
--- a/docker-app/qfieldcloud/filestorage/view_helpers.py
+++ b/docker-app/qfieldcloud/filestorage/view_helpers.py
@@ -306,6 +306,7 @@ def download_field_file(
if_unmodified_since_int, tz=tz.utc
)
+ response_content_disposition: str | None = None
if as_attachment:
parameters.update(
{
@@ -313,6 +314,7 @@ def download_field_file(
"ResponseContentDisposition": f'attachment;filename="{filename}"',
}
)
+ response_content_disposition = f'attachment; filename="{filename}"'
url = field_file.storage.url(
storage_filename,
@@ -330,7 +332,9 @@ def download_field_file(
if range:
response["file_range"] = range.header
- field_file.storage.patch_nginx_download_redirect(response) # type: ignore
+ field_file.storage.patch_nginx_download_redirect(
+ response, content_disposition=response_content_disposition
+ ) # type: ignore
return response
elif settings.DEBUG or settings.IN_TEST_SUITE:
diff --git a/docker-nginx/templates/includes/qfieldcloud.conf.template b/docker-nginx/templates/includes/qfieldcloud.conf.template
index 5bce54c1..0165fadb 100644
--- a/docker-nginx/templates/includes/qfieldcloud.conf.template
+++ b/docker-nginx/templates/includes/qfieldcloud.conf.template
@@ -70,6 +70,9 @@ location /storage-download/ {
set $webdav_auth "$upstream_http_webdav_auth";
# if a Range header is provided
set $file_range "$upstream_http_file_range";
+ # webdav storage cannot set Content-Disposition on the object itself;
+ # the app forwards it via this header so we can return it to the client.
+ set $webdav_content_disposition "$upstream_http_webdav_content_disposition";
# Add Docker's DNS resolver with IPv6 turned off
resolver 127.0.0.11 ipv6=off;
@@ -113,6 +116,12 @@ location /storage-download/ {
proxy_intercept_errors on;
+ # Return the app-provided Content-Disposition (filename) to the client.
+ # Hide any Content-Disposition coming from the storage upstream first,
+ # so we don't end up with a duplicate / wrong one.
+ proxy_hide_header Content-Disposition;
+ add_header Content-Disposition $webdav_content_disposition;
+
proxy_pass $redirect_uri;
error_page 404 =404 /pages/404.html;
error_page 403 =403 /pages/403.html;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment