shashank created CAMEL-25757:
--------------------------------

             Summary: camel-azure-files - resumeDownload never completes an 
interrupted download: the 206 Partial Content answer to the ranged download is 
taken as a failure, and the file is then never routed
                 Key: CAMEL-25757
                 URL: https://issues.apache.org/jira/browse/CAMEL-25757
             Project: Camel
          Issue Type: Bug
          Components: camel-azure
            Reporter: shashank
            Assignee: shashank


With {{resumeDownload=true}} and a {{localWorkDirectory}}, an interrupted 
download leaves {{<name>.inprogress}} in the local work directory. On the next 
poll {{FilesOperations.retrieveFileToFileInLocalWorkDirectory}} (main 
ddca15435112, lines 376-387) asks the file service for the rest of the file and 
accepts only 200:

{code:java}
ShareFileRange range = new ShareFileRange(0);
if (resumeDownload && existingSize > 0) {
    range = new ShareFileRange(existingSize);
}
var ret = cwd().getFileClient(remoteName).downloadWithResponse(os, range, null, 
endpoint.getDataTimeout(), Context.NONE);
result = ret.getStatusCode() == HTTP_OK;
{code}

With a range that does not start at 0 the SDK sends {{x-ms-range: 
bytes=<existingSize>-}} ({{ShareFileRange.toHeaderValue()}}), and Azure Files 
answers a ranged Get File with *206 Partial Content*. The Azure Files REST 
reference ("Specifying the range header for FileREST operations", format 
{{bytes=startByte-}}) says: "If the offset is valid and does not exceed the 
file's total length, the request will return a status code 206 (Partial 
Content)." The SDK's generated {{FilesService.download}} also declares 200 and 
206 as the expected answers. So:

# poll 1 after the interruption: the rest of the file is appended to 
{{<name>.inprogress}} (which now holds the whole file), but {{result}} is 
{{false}}: the file is not renamed, the consumer reports "Cannot retrieve file" 
and does not route it;
# every later poll: the range now starts at the end of the file; the service 
answers 416 (Requested Range Not Satisfiable, as HTTP range rules give for a 
start at or after the end), the download throws, and the {{.inprogress}} file 
is kept because {{resumeDownload=true}}. (Even if the service answered such a 
range with an empty 206, it is still not 200 and the result is the same.)

The file is never routed again, and an error is logged on every poll, until 
someone deletes the {{.inprogress}} file by hand. This is the only case 
{{resumeDownload}} exists for, so the option never works. A download without a 
range (no {{.inprogress}} file, or {{resumeDownload=false}}) is answered with 
200 and is not affected.

*Fix*: a 206 answer to a download whose range starts after 0 is a successful 
retrieve ({{HTTP_PARTIAL_CONTENT}} next to {{HTTP_OK}}). The status of the 
other downloads is still checked against 200 only.

*Tests*: new {{FilesOperationsResumeDownloadTest}} (2 tests). The real 
{{FilesOperations}} and the real Azure SDK client, with an {{HttpClient}} that 
answers Get File like Azure Files (200 without a range, 206 with a range inside 
the file, 416 with a range at or after its end). 
{{resumedDownloadIsCompletedAndRenamed}}: a 4-byte {{hello.txt.inprogress}} of 
a 10-byte file; the SDK asks for {{bytes=4-}}; on main {{retrieveFile}} returns 
false ({{expected: <true> but was: <false>}}); with the fix it returns true and 
the local work file holds the 10 bytes. 
{{downloadWithoutPartialFileIsRetrieved}} (no {{.inprogress}} file) passes on 
main and with the fix; it shows that the normal download is unchanged. 
camel-azure-files unit tests: 30 run, 0 failures.

*How it was found*: a Z3 model of the byte ranges of a resumed download across 
polls (size of the {{.inprogress}} file before each poll, remote size S, the 
status the service gives for the range, the {{result}} check, the rename). On 
the code of main the formula "the file is routed in one of the 6 polls after an 
interruption at byte k" is unsatisfiable for every S > 0 and 0 < k < S 
(counterexample S=2, k=1: in-progress sizes 1, 2, 2, 2, ...); with the fix the 
file is routed on the first poll and the routed size is always S. The JUnit 
test above is the evidence.

Not changed: a {{.inprogress}} file that already holds the whole file (the JVM 
stopped after the last byte and before the rename) still gets 416 on the next 
poll; that needs a comparison with the remote size and is a separate case.

*Affected*: main, camel-4.23.x, camel-4.22.x, camel-4.18.x and camel-4.14.x 
(the same lines; read, not run).

_Filed with Claude Code on behalf of allthingssecurity._




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to