Remote File Handling and Validation#
When a workflow input file is submitted with "transfer_method": "remote_url" and a bare URL (no pre-uploaded upload_file_id), the request flows through four stages: metadata probing, filename extraction, type resolution, and config validation. The relevant source files are:
| File | Role |
|---|---|
api/factories/file_factory/remote.py | HTTP HEAD probe, filename extraction, MIME detection |
api/factories/file_factory/builders.py | Orchestration, type resolution, File object construction |
api/factories/file_factory/validation.py | Extension-based config validation |
Entry point is _build_from_remote_url() in builders.py.
Stage 1: Remote Metadata Probing#
get_remote_file_info(url) fires an SSRF-safe HTTP HEAD request via remote_fetcher.make_request("HEAD", url, follow_redirects=True). On HTTP 200, it reads:
- Content-Disposition → passed to
extract_filename()(see next section) - Content-Length → stored as
file_size - Content-Type → used as fallback MIME type if the filename extension doesn't resolve one
If no filename can be determined at all, a UUID-based fallback filename is generated using the guessed extension from the MIME type (e.g., <uuid>.jpeg). The function returns (mime_type, filename, file_size) .
Stage 2: Filename Extraction#
extract_filename(url_or_path, content_disposition) resolves a safe filename using the following priority chain:
-
RFC 5987
filename*— Regex-extracts thefilename*=<charset>'<lang>'<pct-encoded-value>form, percent-decodes viaurllib.parse.unquote(value, encoding=charset, errors="replace"). This was overhauled in PR #26230 to properly handle charset/language tags and prevent path injection. -
Plain
filename— Falls back to werkzeug'sparse_options_header(), strips surrounding quotes, then percent-decodes . -
URL path basename — When no
Content-Dispositionfilename is available, usesurllib.parse.urlparse(url_or_path).pathto isolate the URL path (discarding query strings and hash fragments), then decodes viaurllib.parse.unquote(..., errors="replace"). This was fixed in PR #35706 to correctly handle presigned S3 URLs withX-Amz-*query parameters.
Security: The final result always passes through os.path.basename() to strip any path traversal sequences. Whitespace-only or empty results return None .
Stage 3: Type Resolution#
Back in _build_from_remote_url(), the resolved filename extension and MIME type are passed to standardize_file_type(extension, mime_type) to produce a FileType enum value .
_resolve_file_type() then reconciles the detected type with the caller-supplied type field:
- If no
typeis specified, the detected type wins. - If
type == "custom",FileType.CUSTOMis returned unconditionally — extension-whitelist enforcement is deferred to the config validation stage. - If
strict_type_validation=Trueand the detected type differs from the specified type, aValueErroris raised with the message"Detected file type does not match the specified type. Please verify the file.".
When is strict validation active? strict_type_validation=True is set when invoke_from == InvokeFrom.SERVICE_API, i.e., all Service API calls enforce strict type checking .
Stage 4: Config Validation#
After the File object is built, build_from_mapping() calls is_file_valid_with_config() if a FileUploadConfig is present. Validation logic:
- Tool files bypass config —
TOOL_FILEtransfer method always returnsTrue. - Type allowlist —
input_file_typemust be inconfig.allowed_file_types, orCUSTOMmust be in that list . - Extension whitelist (CUSTOM bucket) — When the file falls into the CUSTOM bucket (either explicitly typed as
CUSTOMor when its type isn't in the allowlist),config.allowed_file_extensionsis enforced. An explicitly empty list means deny all . Extension matching is case- and dot-insensitive via_normalize_extension(). - Transfer method check — IMAGE files check
image_config.transfer_methods; all others checkallowed_file_upload_methods.
Common Error: "Detected file type does not match the specified type"#
This error requires all three conditions to hold simultaneously :
- Service API invocation (
strict_type_validation=True) - Non-null
typefield in the input mapping (note:"custom"is a common default) - Type mismatch — the extension/MIME-derived
FileTypediffers from the specified type
Fixes and workarounds:
- Pass a
typevalue that matches the file's actual extension (e.g.,"document"for.txt,.pdf,.docx) . - Upload via
/files/uploadfirst to obtain anupload_file_id, then reference it withtransfer_method: "local_file"— this avoids live URL probing entirely. - For presigned S3 URLs, ensure the URL path contains a file extension (e.g., ends in
.jpg) because Dify's filename extraction depends on it whenContent-Dispositionis absent. - The
"custom"type only avoids strict-type errors if the workflow's variable is also configured asCUSTOM; otherwise the CUSTOM extension whitelist applies.