Add upload/download tests for size boundaries and concurrency
- Add size boundary tests: 1B, 1KB, 10KB, 100KB, 1MB, 5MB, 10MB, 50MB - Add large file tests (100MB-1GB) marked with @pytest.mark.large - Add chunk boundary tests at 64KB boundaries - Add concurrent upload/download tests (2, 5, 10 parallel) - Add data integrity tests (binary, text, unicode, compressed) - Add generate_content() and sized_content fixture for test helpers - Add @pytest.mark.large and @pytest.mark.concurrent markers - Fix Content-Disposition header encoding for non-ASCII filenames (RFC 5987)
This commit is contained in:
@@ -140,6 +140,31 @@ def sanitize_filename(filename: str) -> str:
|
||||
return re.sub(r'[\r\n"]', "", filename)
|
||||
|
||||
|
||||
def build_content_disposition(filename: str) -> str:
|
||||
"""Build a Content-Disposition header value with proper encoding.
|
||||
|
||||
For ASCII filenames, uses simple: attachment; filename="name"
|
||||
For non-ASCII filenames, uses RFC 5987 encoding with UTF-8.
|
||||
"""
|
||||
from urllib.parse import quote
|
||||
|
||||
sanitized = sanitize_filename(filename)
|
||||
|
||||
# Check if filename is pure ASCII
|
||||
try:
|
||||
sanitized.encode('ascii')
|
||||
# Pure ASCII - simple format
|
||||
return f'attachment; filename="{sanitized}"'
|
||||
except UnicodeEncodeError:
|
||||
# Non-ASCII - use RFC 5987 encoding
|
||||
# Provide both filename (ASCII fallback) and filename* (UTF-8 encoded)
|
||||
ascii_fallback = sanitized.encode('ascii', errors='replace').decode('ascii')
|
||||
# RFC 5987: filename*=charset'language'encoded_value
|
||||
# We use UTF-8 encoding and percent-encode non-ASCII chars
|
||||
encoded = quote(sanitized, safe='')
|
||||
return f'attachment; filename="{ascii_fallback}"; filename*=UTF-8\'\'{encoded}'
|
||||
|
||||
|
||||
def get_user_id_from_request(
|
||||
request: Request,
|
||||
db: Session,
|
||||
@@ -2924,7 +2949,7 @@ def download_artifact(
|
||||
)
|
||||
|
||||
headers = {
|
||||
"Content-Disposition": f'attachment; filename="{filename}"',
|
||||
"Content-Disposition": build_content_disposition(filename),
|
||||
"Accept-Ranges": "bytes",
|
||||
"Content-Length": str(content_length),
|
||||
**checksum_headers,
|
||||
@@ -2942,7 +2967,7 @@ def download_artifact(
|
||||
|
||||
# Full download with optional verification
|
||||
base_headers = {
|
||||
"Content-Disposition": f'attachment; filename="{filename}"',
|
||||
"Content-Disposition": build_content_disposition(filename),
|
||||
"Accept-Ranges": "bytes",
|
||||
**checksum_headers,
|
||||
}
|
||||
@@ -3124,7 +3149,7 @@ def head_artifact(
|
||||
|
||||
# Build headers with checksum information
|
||||
headers = {
|
||||
"Content-Disposition": f'attachment; filename="{filename}"',
|
||||
"Content-Disposition": build_content_disposition(filename),
|
||||
"Accept-Ranges": "bytes",
|
||||
"Content-Length": str(artifact.size),
|
||||
"X-Artifact-Id": artifact.id,
|
||||
|
||||
Reference in New Issue
Block a user