Skip to content

Fail the upload when the converter returns no document - #545

Merged
andiwand merged 1 commit into
mainfrom
fix/online-convert-missing-location
Jul 28, 2026
Merged

Fail the upload when the converter returns no document#545
andiwand merged 1 commit into
mainfrom
fix/online-convert-missing-location

Conversation

@andiwand

Copy link
Copy Markdown
Member

What

doOnlineConvert read the Location header without checking it was there:

val redirectUrl = connection.getHeaderField("Location")
return Uri.parse(basePath + redirectUrl)

use.opendocument.app answers a converted document with a redirect to it, so when it refuses a file there is no Location, getHeaderField returns null, and the null goes straight into the concatenation. The loader then reported success for the uri https://use.opendocument.appnull/.

How it shows up

Opening a truncated .odt on an emulator:

D/ODR      CORE failed
E/ODR      app.opendocument.core.OdrException: not a zip file
I/smn      loader_error_CORE
...user accepts the "upload this file?" offer...
I/smn      loader_success_ONLINE content_type content
E/ODR      java.lang.RuntimeException: loading https://use.opendocument.appnull/ failed:
           -2 net::ERR_NAME_NOT_RESOLVED

Two things go wrong: the user gets a WebView DNS error page rather than the "could not open" dialog and the offer to reopen, and the log line blames DNS instead of the server refusing the document.

The fix

Treat a missing Location the way doTransferUpload already treats an empty body — throw an IOException carrying the response code and the error body, so loadSync routes it through callOnError.

The guard is on the header rather than on the status code alone, so a 2xx-with-Location response keeps working; the code is included for diagnostics.

Notes

The file that surfaced this is genuinely corrupt — it carries two end-of-central-directory records, and the authoritative last one points at offset 24822, which is zeros, while the real central directory sits at 24089. unzip rejects it too, so the core's not a zip file is correct and unchanged here. This PR is only about the fallback path lying about having succeeded.

The bug predates the Kotlin conversion; the pre-conversion Java had the identical two lines.

Not covered by a unit test: doOnlineConvert is private, needs a live HttpURLConnection, and Uri.parse needs a real Android runtime — the existing OnlineLoaderTest only exercises the pure isSupported/isConvertible predicates, and there is no MockWebServer/Robolectric setup to hang a test on.

Test plan

  • ./gradlew spotlessCheck testProDebugUnitTest — green

use.opendocument.app answers a converted document with a redirect to it, and
doOnlineConvert read the Location header without checking it was there. When the
server refuses the file it answers without one, getHeaderField returns null, and
the null went straight into the string concatenation - so the loader reported
success for the uri "https://use.opendocument.appnull/". The user got a webview
DNS error page instead of the "could not open" dialog, and the log a
net::ERR_NAME_NOT_RESOLVED that says nothing about what actually went wrong.

Treat a missing Location the way doTransferUpload already treats an empty body:
throw, with the response code and the error body in the message, so loadSync
reports the error and the offer to reopen comes back.

Found by opening a truncated .odt whose central directory offset points past the
entries: the core rejects it as "not a zip file" and the converter, given the
same file, refuses it too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UgPw1bJBn1sfdw4LuL5dkW
@andiwand
andiwand merged commit 4c99cb1 into main Jul 28, 2026
7 checks passed
@andiwand
andiwand deleted the fix/online-convert-missing-location branch July 28, 2026 09:45
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