Skip to content

Swift and Kotlin classify a present-but-unreadable upload file differently (MediaFileUnreadable vs MediaFileNotFound) #1558

Description

@jkmassel

Summary

Root Cause

  • Kotlin pre-screens every file field before building the body: canBeUploaded() = exists() && isFile && canRead() (native/kotlin/api/kotlin/src/main/kotlin/rs/wordpress/api/kotlin/WpRequestExecutor.kt:286). A chmod 000 file fails canRead() and a directory fails isFile, so both throw MediaFileNotFound at WpRequestExecutor.kt:143-144 — before the stream is ever opened.
  • Swift builds the field with FileManager.default.attributesOfItem(atPath:) (native/swift/Sources/wordpress-api/MultipartForm.swift:63), a stat that needs no read permission on the file, so construction succeeds. The failure surfaces later at serialization (open()/read()streamStatus == .error / bytesRead < 0, MultipartForm.swift:204-234) as .inaccessibleFile(filePath:), which maps to MediaFileUnreadable (SafeRequestExecutor.swift:652).
  • The genuinely-absent-file case and the deleted-between-check-and-open case agree across platforms (both MediaFileNotFound, both MediaFileUnreadable respectively). Only the present-but-unreadable / non-regular case diverges.

Impact

A shared cross-platform consumer keying on the error type behaves inconsistently by platform:

  • User messaging: iOS shows "could not be read", Android shows "not found" — for a file that is sitting right there. The Android copy is actively misleading (it exists).
  • Analytics: the same root cause double-buckets across MediaFileUnreadable and MediaFileNotFound, so neither platform's rate is comparable.
  • Retry / recovery: a consumer that treats NotFound as terminal but Unreadable as retryable (e.g. an iCloud-evicted file that may re-materialize — the PR's own motivation) retries on iOS and gives up on Android for identical input.

The input class isn't exotic on mobile: NSFileProtectionComplete files on a locked device, and file-provider / iCloud-evicted items, are all stat-succeeds-but-read-fails.

Notes

Suggested Fix

  1. Split Kotlin's canBeUploaded() so exists() && isFile() && !canRead() (and a directory) route to MediaFileUnreadable rather than MediaFileNotFound, matching Swift. ⚠️ This touches a pre-flight predicate with its own TOCTOU surface, so it needs its own test.
  2. Either way, soften Classify multipart mid-read failures as MediaFileUnreadable #1546's CHANGELOG line so it stops promising "Both executors produce it" for a present-but-unreadable file until the classification is actually aligned.

Related issues

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions