Skip to content

fix(sdk-python): decode multipart download path labels#68

Open
mu-hashmi wants to merge 3 commits into
mainfrom
fix/download-backslash-paths
Open

fix(sdk-python): decode multipart download path labels#68
mu-hashmi wants to merge 3 commits into
mainfrom
fix/download-backslash-paths

Conversation

@mu-hashmi

@mu-hashmi mu-hashmi commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

fs.download_file fails on Windows sandboxes when the remote path uses backslashes:

sandbox.fs.download_file(r"C:\Windows\Temp\x")
# DaytonaError: Failed to download files: 'x'

The same file downloads fine spelled C:/Windows/Temp/x, and uploads accept backslash paths — only downloads break.

Root cause. The bulk-download response is multipart; each part echoes the requested path in its Content-Disposition so the SDK can match parts back to requests by exact string lookup. That header was parsed with python_multipart.parse_options_header, which deliberately strips a backslash-containing filename down to its basename:

>>> parse_options_header(r'form-data; name="file"; filename="C:\\Windows\\Temp\\x"')
# filename → b'x'

The lookup for 'x' then raises KeyError — the cryptic 'x' in the error message is that KeyError leaking through the error-prefix decorator.

Fix: Parse Content-Disposition in the SDK instead, preferring the RFC 5987 filename* parameter the daemon already sends (percent-encoded, so it round-trips any path byte-exactly) and falling back to plain filename with quoted-pair unescaping. Preferring filename* also fixes a second latent bug: plain filename is Latin-1-only and the daemon substitutes _ for characters outside it, so non-ASCII paths could never match either. Part header values are now decoded as Latin-1 (per HTTP) rather than UTF-8-with-ignore, which silently dropped bytes.

An alternative considered and rejected: matching parts to requests positionally (the daemon writes one part per requested path, in order). It would sidestep header parsing entirely, but gives up self-identifying parts and would silently mis-pair files if the response order ever changed.

Verification. pytest tests/test_filesystem.py — 45 passed. New coverage: backslash and forward-slash paths, non-ASCII paths via filename*, the filename-only fallback, mixed success/error parts, and malformed filename* rejection, for both the sync and async clients.


Open in Devin Review (Beta)

Summary by cubic

Fixes bulk download path matching by correctly decoding multipart Content-Disposition labels, so Windows backslash and non-ASCII paths download reliably in both sync and async clients.

  • Bug Fixes
    • Added a safe Content-Disposition parser in daytona.common.file_transfer that prefers RFC 5987 filename*, falls back to quoted filename, and strictly validates percent-encoding to reject malformed labels while preserving exact paths (including backslashes).
    • Decode part header values as Latin-1 (per HTTP) to prevent byte loss and enable exact label matching.
    • Added tests covering backslash paths, filename* (including malformed), filename fallback, mixed success/error parts, and truncated responses in sync and async clients.

Written for commit e184686. Summary will update on new commits.

Review in cubic

Signed-off-by: Muhammad Hashmi <mhashmi@berkeley.edu>
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA. ✅ Thank you!
Posted by the CLA Assistant Lite bot.

@beta-devin-ai-integration beta-devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review (Beta)

Signed-off-by: Muhammad Hashmi <mhashmi@berkeley.edu>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found and verified against the latest diff

You’re at about 95% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="sdk-python/src/daytona/common/file_transfer.py">

<violation number="1" location="sdk-python/src/daytona/common/file_transfer.py:93">
P3: Malformed `filename*` responses are now rejected in several new branches, but the added sync/async tests only generate valid UTF-8 percent encoding or omit `filename*` entirely. Adding cases for an invalid escape (for example `%GG`), unsupported charset, and invalid UTF-8 bytes would protect this protocol-error behavior and verify both clients surface `DaytonaError` as intended.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread sdk-python/src/daytona/_sync/filesystem.py
return name, parameters.get("filename")

extended_parts = extended_filename.split("'", 2)
if len(extended_parts) != 3 or extended_parts[0].lower() != "utf-8":

@cubic-dev-ai cubic-dev-ai Bot Jul 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Malformed filename* responses are now rejected in several new branches, but the added sync/async tests only generate valid UTF-8 percent encoding or omit filename* entirely. Adding cases for an invalid escape (for example %GG), unsupported charset, and invalid UTF-8 bytes would protect this protocol-error behavior and verify both clients surface DaytonaError as intended.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At sdk-python/src/daytona/common/file_transfer.py, line 93:

<comment>Malformed `filename*` responses are now rejected in several new branches, but the added sync/async tests only generate valid UTF-8 percent encoding or omit `filename*` entirely. Adding cases for an invalid escape (for example `%GG`), unsupported charset, and invalid UTF-8 bytes would protect this protocol-error behavior and verify both clients surface `DaytonaError` as intended.</comment>

<file context>
@@ -13,6 +15,100 @@
+        return name, parameters.get("filename")
+
+    extended_parts = extended_filename.split("'", 2)
+    if len(extended_parts) != 3 or extended_parts[0].lower() != "utf-8":
+        raise DaytonaError("Invalid Content-Disposition filename* parameter")
+
</file context>
Fix with cubic

Signed-off-by: Muhammad Hashmi <mhashmi@berkeley.edu>
@mu-hashmi

Copy link
Copy Markdown
Collaborator Author

I have read the CLA Document and I hereby sign the CLA

github-actions Bot added a commit that referenced this pull request Jul 13, 2026
@mu-hashmi mu-hashmi requested a review from MDzaja July 13, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant