Skip to content
This repository was archived by the owner on Mar 31, 2026. It is now read-only.

Commit 5581988

Browse files
authored
fix(storage): skip downloading blobs whose name contain ":" eg: C: D: etc when application runs in Windows. (#1774)
skip downloading blobs whose name contain `":" ` eg: `C:` `D:` etc when application runs in Windows.
1 parent c5735c3 commit 5581988

3 files changed

Lines changed: 67 additions & 2 deletions

File tree

google/cloud/storage/exceptions.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@
3333
DataCorruptionDynamicParent = Exception
3434

3535

36+
class InvalidPathError(Exception):
37+
"""Raised when the provided path string is malformed."""
38+
39+
pass
40+
41+
3642
class InvalidResponse(InvalidResponseDynamicParent):
3743
"""Error class for responses which are not in the correct state.
3844

google/cloud/storage/transfer_manager.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939

4040
from google.cloud.storage._media.requests.upload import XMLMPUContainer
4141
from google.cloud.storage._media.requests.upload import XMLMPUPart
42-
from google.cloud.storage.exceptions import DataCorruption
42+
from google.cloud.storage.exceptions import DataCorruption, InvalidPathError
4343

4444
TM_DEFAULT_CHUNK_SIZE = 32 * 1024 * 1024
4545
DEFAULT_MAX_WORKERS = 8
@@ -263,6 +263,8 @@ def upload_many(
263263

264264

265265
def _resolve_path(target_dir, blob_path):
266+
if os.name == "nt" and ":" in blob_path:
267+
raise InvalidPathError(f"{blob_path} cannot be downloaded into {target_dir}")
266268
target_dir = Path(target_dir)
267269
blob_path = Path(blob_path)
268270
# blob_path.anchor will be '/' if `blob_path` is full path else it'll empty.
@@ -818,7 +820,13 @@ def download_many_to_path(
818820

819821
for i, blob_name in enumerate(blob_names):
820822
full_blob_name = blob_name_prefix + blob_name
821-
resolved_path = _resolve_path(destination_directory, blob_name)
823+
try:
824+
resolved_path = _resolve_path(destination_directory, blob_name)
825+
except InvalidPathError as e:
826+
msg = f"The blob {blob_name} will **NOT** be downloaded. {e}"
827+
warnings.warn(msg)
828+
results[i] = UserWarning(msg)
829+
continue
822830
if not resolved_path.parent.is_relative_to(
823831
Path(destination_directory).resolve()
824832
):

tests/unit/test_transfer_manager.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,57 @@ def test_upload_many_from_filenames_additional_properties():
513513
assert getattr(blob, attrib) == value
514514

515515

516+
517+
def test__resolve_path_raises_invalid_path_error_on_windows():
518+
from google.cloud.storage.transfer_manager import _resolve_path, InvalidPathError
519+
520+
with mock.patch("os.name", "nt"):
521+
with pytest.raises(InvalidPathError) as exc_info:
522+
_resolve_path("C:\\target", "C:\\target\\file.txt")
523+
assert "cannot be downloaded into" in str(exc_info.value)
524+
525+
# Test that it DOES NOT raise on non-windows
526+
with mock.patch("os.name", "posix"):
527+
# Should not raise
528+
_resolve_path("/target", "C:\\target\\file.txt")
529+
530+
531+
def test_download_many_to_path_raises_invalid_path_error():
532+
bucket = mock.Mock()
533+
534+
BLOBNAMES = ["C:\\target\\file.txt"]
535+
PATH_ROOT = "mypath/"
536+
BLOB_NAME_PREFIX = "myprefix/"
537+
DOWNLOAD_KWARGS = {"accept-encoding": "fake-gzip"}
538+
MAX_WORKERS = 7
539+
DEADLINE = 10
540+
WORKER_TYPE = transfer_manager.THREAD
541+
542+
with mock.patch("os.name", "nt"):
543+
import warnings
544+
545+
with warnings.catch_warnings(record=True) as w:
546+
warnings.simplefilter("always")
547+
results = transfer_manager.download_many_to_path(
548+
bucket,
549+
BLOBNAMES,
550+
destination_directory=PATH_ROOT,
551+
blob_name_prefix=BLOB_NAME_PREFIX,
552+
download_kwargs=DOWNLOAD_KWARGS,
553+
deadline=DEADLINE,
554+
create_directories=False,
555+
raise_exception=True,
556+
max_workers=MAX_WORKERS,
557+
worker_type=WORKER_TYPE,
558+
skip_if_exists=True,
559+
)
560+
561+
assert len(w) == 1
562+
assert "will **NOT** be downloaded" in str(w[0].message)
563+
assert len(results) == 1
564+
assert isinstance(results[0], UserWarning)
565+
566+
516567
def test_download_many_to_path():
517568
bucket = mock.Mock()
518569

0 commit comments

Comments
 (0)