From b6a806ae6f7b258392e9dec8e2d1d36d55c2bc63 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Tue, 5 Aug 2025 14:51:15 +0300 Subject: [PATCH] fix: do not overwrite with empty file if the server returns HTTP 304 when `If-None-Match` is sent When `If-None-Match` is sent, the server may return HTTP 304 Not Modified with empty response body. This is problematic, as our code was assuming that there is always a file returned by the server if the HTTP status code < 400 --- qfieldcloud_sdk/sdk.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/qfieldcloud_sdk/sdk.py b/qfieldcloud_sdk/sdk.py index 8b9d409..803e142 100644 --- a/qfieldcloud_sdk/sdk.py +++ b/qfieldcloud_sdk/sdk.py @@ -1128,8 +1128,11 @@ def download_file( ``` """ + headers: dict[str, str] = {} if remote_etag and local_filename.exists(): - if calc_etag(str(local_filename)) == remote_etag: + local_etag = calc_etag(str(local_filename)) + + if local_etag == remote_etag: if show_progress: print( f"{remote_filename}: Already present locally. Download skipped." @@ -1140,6 +1143,8 @@ def download_file( ) return None + headers["If-None-Match"] = local_etag + if download_type == FileTransferType.PROJECT: url = f"files/{project_id}/{remote_filename}" elif download_type == FileTransferType.PACKAGE: @@ -1147,7 +1152,13 @@ def download_file( else: raise NotImplementedError() - resp = self._request("GET", url, stream=True) + resp = self._request("GET", url, stream=True, headers=headers) + + # Since we are sending the `If-None-Match` header to check the `ETag` of the remote file, + # we shall expect HTTP 304 in case the local and remote `ETag` match. + # The early return will prevent overwriting the file with empty contents of the 304 response. + if resp.status_code == 304: + return None if not local_filename.parent.exists(): local_filename.parent.mkdir(parents=True)