Compare commits

...

13 Commits

Author SHA1 Message Date
Sam Pullara
44956e7cd5
Merge 019eaab4c9 into fc0cb10bcb 2026-01-07 14:46:40 +05:30
Sam Pullara
019eaab4c9
Merge branch 'comfyanonymous:master' into master 2026-01-06 14:24:31 -08:00
Sam Pullara
f330220f66
Merge branch 'comfyanonymous:master' into master 2025-12-15 09:37:39 -08:00
Sam Pullara
d35e0fcdd7
Merge branch 'comfyanonymous:master' into master 2025-12-01 12:41:36 -08:00
Sam Pullara
3d0331813d
Merge branch 'comfyanonymous:master' into master 2025-11-26 15:18:24 -08:00
Sam Pullara
afd4b725db
Merge pull request #1 from spullara/claude/fix-download-file-extension-01KxyJS9CQduLtV75QhZiPsT
Fix file download issue - add attachment disposition type to Content-…
2025-11-19 16:46:45 -08:00
Claude
149506beea
Fix file download issue - add attachment disposition type to Content-Disposition headers
Files were downloading with filename "view" instead of the actual filename because
the Content-Disposition header was missing the disposition type (attachment/inline).
Changed from `filename="..."` to `attachment; filename="..."` in all 4 locations
in the /view endpoint to ensure proper filename handling by browsers.

This fixes downloads for videos, audio, and other file types served through the
/view endpoint.
2025-11-20 00:40:19 +00:00
Sam Pullara
dfca61be7f
Merge branch 'comfyanonymous:master' into master 2025-11-19 13:33:33 -08:00
Sam Pullara
39a5c5621e
Merge branch 'comfyanonymous:master' into master 2025-11-12 15:06:53 -08:00
Sam Pullara
0d20e44618
Merge branch 'comfyanonymous:master' into master 2025-10-31 13:41:24 -07:00
Sam Pullara
5f415089fc
Merge branch 'comfyanonymous:master' into master 2025-10-30 15:19:16 -07:00
Sam Pullara
6d23bfde7f add tests for saving json files formatted nicely 2025-10-29 13:26:49 -07:00
Sam Pullara
0eff10fd21 store json files pretty printed for better source control compatibiility 2025-10-29 13:17:56 -07:00
3 changed files with 89 additions and 6 deletions

View File

@ -377,8 +377,22 @@ class UserManager():
try:
body = await request.read()
with open(path, "wb") as f:
f.write(body)
# Pretty print JSON files for better source control
if path.lower().endswith('.json'):
try:
# Parse JSON and re-serialize with indentation
json_data = json.loads(body.decode('utf-8'))
formatted_json = json.dumps(json_data, indent=2)
with open(path, "w", encoding='utf-8') as f:
f.write(formatted_json)
except (json.JSONDecodeError, UnicodeDecodeError):
# If JSON parsing fails, save as-is
with open(path, "wb") as f:
f.write(body)
else:
# Non-JSON files are saved as-is
with open(path, "wb") as f:
f.write(body)
except OSError as e:
logging.warning(f"Error saving file '{path}': {e}")
return web.Response(

View File

@ -518,7 +518,7 @@ class PromptServer():
buffer.seek(0)
return web.Response(body=buffer.read(), content_type=f'image/{image_format}',
headers={"Content-Disposition": f"filename=\"{filename}\""})
headers={"Content-Disposition": f"attachment; filename=\"{filename}\""})
if 'channel' not in request.rel_url.query:
channel = 'rgba'
@ -538,7 +538,7 @@ class PromptServer():
buffer.seek(0)
return web.Response(body=buffer.read(), content_type='image/png',
headers={"Content-Disposition": f"filename=\"{filename}\""})
headers={"Content-Disposition": f"attachment; filename=\"{filename}\""})
elif channel == 'a':
with Image.open(file) as img:
@ -555,7 +555,7 @@ class PromptServer():
alpha_buffer.seek(0)
return web.Response(body=alpha_buffer.read(), content_type='image/png',
headers={"Content-Disposition": f"filename=\"{filename}\""})
headers={"Content-Disposition": f"attachment; filename=\"{filename}\""})
else:
# Get content type from mimetype, defaulting to 'application/octet-stream'
content_type = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
@ -567,7 +567,7 @@ class PromptServer():
return web.FileResponse(
file,
headers={
"Content-Disposition": f"filename=\"{filename}\"",
"Content-Disposition": f"attachment; filename=\"{filename}\"",
"Content-Type": content_type
}
)

View File

@ -287,3 +287,72 @@ async def test_listuserdata_v2_url_encoded_path(aiohttp_client, app, tmp_path):
assert entry["name"] == "file.txt"
# Ensure the path is correctly decoded and uses forward slash
assert entry["path"] == "my dir/file.txt"
async def test_post_userdata_json_pretty_print(aiohttp_client, app, tmp_path):
"""Test that JSON files are saved with pretty printing (indentation)"""
import json
client = await aiohttp_client(app)
# Create a compact JSON workflow
workflow_data = {
"nodes": [
{"id": "1", "type": "LoadImage", "inputs": {"image": "test.png"}},
{"id": "2", "type": "SaveImage", "inputs": {"images": ["1", 0]}}
],
"metadata": {"version": "1.0", "author": "test"}
}
compact_json = json.dumps(workflow_data).encode('utf-8')
# Save as JSON file
resp = await client.post("/userdata/workflow.json", data=compact_json)
assert resp.status == 200
# Read the saved file and verify it's pretty-printed
with open(tmp_path / "workflow.json", "r", encoding='utf-8') as f:
saved_content = f.read()
# Verify the file contains indentation (pretty-printed)
assert " " in saved_content # Should have 2-space indentation
assert "\n" in saved_content # Should have newlines
# Verify the content is still valid JSON and matches original data
saved_data = json.loads(saved_content)
assert saved_data == workflow_data
# Verify it's actually formatted (not compact)
# Compact JSON would be much shorter
assert len(saved_content) > len(compact_json)
async def test_post_userdata_json_invalid_fallback(aiohttp_client, app, tmp_path):
"""Test that invalid JSON is saved as-is without error"""
client = await aiohttp_client(app)
# Create invalid JSON content
invalid_json = b'{"invalid": json content}'
# Save as JSON file - should not fail
resp = await client.post("/userdata/invalid.json", data=invalid_json)
assert resp.status == 200
# Verify file was saved as-is
with open(tmp_path / "invalid.json", "rb") as f:
assert f.read() == invalid_json
async def test_post_userdata_non_json_unchanged(aiohttp_client, app, tmp_path):
"""Test that non-JSON files are saved unchanged"""
client = await aiohttp_client(app)
# Create binary content
binary_content = b'\x00\x01\x02\x03\x04\x05'
# Save as non-JSON file
resp = await client.post("/userdata/test.bin", data=binary_content)
assert resp.status == 200
# Verify file was saved exactly as-is
with open(tmp_path / "test.bin", "rb") as f:
assert f.read() == binary_content