shashank created CAMEL-25905:
--------------------------------

             Summary: camel-file - with checksumFileAlgorithm and charset, the 
producer decodes the body with the JVM default charset (non-ASCII replaced) and 
writes a checksum that does not match the written file
                 Key: CAMEL-25905
                 URL: https://issues.apache.org/jira/browse/CAMEL-25905
             Project: Camel
          Issue Type: Bug
          Components: camel-file
            Reporter: shashank
            Assignee: shashank


{{FileOperations.storeFile}} (main 74ee085f7d64, lines 349-375) implements 
{{checksumFileAlgorithm}} (CAMEL-22846, "calculate a checksum from the file 
that has been written") by wrapping the body in a {{DigestInputStream}}:

{code:java}
if (endpoint.getChecksumFileAlgorithm() != null) {
    MessageDigest digest = 
MessageDigest.getInstance(endpoint.getChecksumFileAlgorithm());
    InputStream is = exchange.getMessage().getBody(FileInputStream.class);
    if (is == null) {
        is = mandatoryBodyStream(exchange, exchange.getMessage(), file);
    }
    dis = new DigestInputStream(is, digest);
}
if (charset != null) {
    handleReaderAsFileSource(exchange, file, charset, dis);
} ...
{code}

and {{handleReaderAsFileSource}} (lines 439-456) skips the {{Reader}} 
conversion when it gets that stream and reads it with {{new 
InputStreamReader(is)}}, the default charset of the JVM. So adding 
{{checksumFileAlgorithm}} to a producer with {{charset}} changes what is 
written, and the checksum is that of the body, not of the file:

* the bytes of a file consumed with {{charset=ISO-8859-1}} (or any body whose 
bytes are in the exchange charset) are decoded as UTF-8: every non-ASCII 
character is written as U+FFFD. Without the checksum option the same route 
writes the file right 
({{from("file:in?charset=ISO-8859-1").to("file:out?charset=UTF-8&checksumFileAlgorithm=sha256")}}
 turns {{Grüße aus Köln}} into {{Gr??e aus K?ln}});
* the checksum (the {{CamelFileChecksum}} header and the {{.sha256}} file) is 
calculated over the bytes before re-encoding, so it does not match the written 
file as soon as the encoding changes a byte: a receiver that verifies the 
checksum file rejects every such file;
* a String body whose exchange carries the endpoint charset (for example sent 
with a {{ProducerTemplate}} to 
{{file:out?charset=ISO-8859-1&checksumFileAlgorithm=sha256}}) is converted to 
ISO-8859-1 bytes, decoded as UTF-8 and fails with 
{{UnmappableCharacterException}};
* with {{fileExist=Append}} and {{appendChars}}, the appended characters are 
written after the digest stream was read, so the checksum leaves them out.

*Fix*: the digest is updated with the bytes written to the file: a 
{{DigestOutputStream}} under the writer of the charset path, and the buffer and 
the {{appendChars}} bytes of the stream path. The body is decoded as without 
the checksum option (the {{Reader}} conversion with the exchange charset). The 
writer of the charset path still reports unmappable characters, as 
{{Files.newBufferedWriter}} did. Upgrade note in the 4.24 guide (checksums of 
such files differ from the ones written before).

*Tests*: new {{FileProducerChecksumOfWrittenFileTest}} in camel-core (4 tests). 
On main: {{transcodedFileHasItsOwnChecksumAndContent}} fails with {{array 
lengths differ, expected: <17> but was: <20>}} (three U+FFFD of three bytes 
each), {{stringWrittenWithCharsetHasTheChecksumOfTheFile}} fails with 
{{CamelExecutionException}} caused by {{UnmappableCharacterException}}, 
{{appendCharsArePartOfTheChecksum}} fails with {{expected: <d2a84f4b...> but 
was: <a591a6d4...>}} (the checksum of {{Hello World}} without the newline). 
{{bodyWithoutCharsetKeepsItsChecksum}} is a control and passes on main. The 9 
tests of {{FileProducerChecksumFileAlgorithmTest}} pass with the fix. The tests 
of the {{org.apache.camel.component.file}} package of camel-core: 478 run, 0 
failures, 12 skipped; camel-file: 28 run, 0 failures (one flake of 
{{FileProducerNoForcedWritesTest}}, passed on rerun; it does not use the 
checksum option).

*How it was found*: comparing the code paths of the file producer for one 
option (here: the checksum path replaces the input of the charset path). The 
JUnit tests above are the evidence.

*Affected*: main, camel-4.23.x, camel-4.22.x and camel-4.18.x ({{new 
DigestInputStream}} and {{new InputStreamReader(is)}} at the same lines; read, 
not run). 4.14.x has an older implementation 
({{GenericFileProducer.writeChecksumFile}} re-reads the body after the write): 
no decoding problem there, but the same checksum of the body instead of the 
file.

Not changed: with {{fileExist=Append}} to an existing file, the checksum covers 
the bytes this exchange wrote, not the whole file. The remote file producers 
(ftp, sftp, scp, smb, azure-files) expose {{checksumFileAlgorithm}} too 
(inherited from {{GenericFileEndpoint}}) but since the checksum moved into 
{{FileOperations}} (CAMEL-22846, 4.18) they never calculate one, so no header 
and no checksum file are written (4.14.x wrote one for them); possible 
follow-up.

_Filed with Claude Code on behalf of allthingssecurity._



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

Reply via email to