Skip to content

Commit 22e1d1f

Browse files
authored
Merge pull request #504 from BHVampire/fix/binary-attachment-corruption
fix: prevent binary attachment corruption (LF to CRLF conversion)
2 parents 2561a68 + 797b42c commit 22e1d1f

File tree

2 files changed

+61
-1
lines changed

2 files changed

+61
-1
lines changed

src/Documents/Commands/Batches/SingleNodeBatchCommand.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export class SingleNodeBatchCommand extends RavenCommand<BatchCommandResult> imp
9393
for (let i = 0; i < attachments.length; i++) {
9494
const part = attachments[i].body;
9595
const payload = part instanceof Readable ? await readToBuffer(part) : part;
96-
body.append("attachment_" + i, payload);
96+
body.append("attachment_" + i, new Blob([payload], { type: "application/octet-stream" }));
9797
}
9898
}
9999

test/Ported/Attachments/AttachmentsSessionTest.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,66 @@ describe("Attachments Session", function () {
319319
}
320320
});
321321

322+
it("binary attachment preserves all byte values including LF (0x0A)", async () => {
323+
// This test verifies that binary attachments are not corrupted.
324+
// Previously, LF bytes (0x0A) were being converted to CRLF (0x0D 0x0A)
325+
// when FormData.append() received a Buffer without Blob wrapper.
326+
327+
// Create binary data with all possible byte values (0x00 to 0xFF)
328+
const binaryData = new Uint8Array(256);
329+
for (let i = 0; i < 256; i++) {
330+
binaryData[i] = i;
331+
}
332+
const originalBuffer = Buffer.from(binaryData);
333+
334+
{
335+
const session = store.openSession();
336+
const user = new User();
337+
user.name = "BinaryTest";
338+
await session.store(user, "users/binary");
339+
session.advanced.attachments.store(
340+
user,
341+
"allbytes.bin",
342+
originalBuffer,
343+
"application/octet-stream"
344+
);
345+
await session.saveChanges();
346+
}
347+
348+
{
349+
const session = store.openSession();
350+
const result = await session.advanced.attachments.get("users/binary", "allbytes.bin");
351+
352+
let retrievedBuffer = Buffer.from([]);
353+
result.data.pipe(new Writable({
354+
write(chunk, enc, cb) {
355+
retrievedBuffer = Buffer.concat([retrievedBuffer, chunk]);
356+
cb();
357+
}
358+
}));
359+
360+
await finishedAsync(result.data);
361+
result.dispose();
362+
363+
// Size should be exactly 256 bytes
364+
assert.strictEqual(
365+
retrievedBuffer.length,
366+
originalBuffer.length,
367+
`Binary attachment corrupted: expected ${originalBuffer.length} bytes, got ${retrievedBuffer.length} bytes`
368+
);
369+
370+
// Content should be identical
371+
assert.ok(
372+
Buffer.compare(retrievedBuffer, originalBuffer) === 0,
373+
"Binary attachment content was modified during storage/retrieval"
374+
);
375+
376+
// Specifically verify byte 0x0A (LF) was not converted to 0x0D 0x0A (CRLF)
377+
assert.strictEqual(retrievedBuffer[10], 0x0A, "Byte at index 10 should be 0x0A (LF)");
378+
assert.strictEqual(retrievedBuffer[11], 0x0B, "Byte at index 11 should be 0x0B, not 0x0A from CRLF expansion");
379+
}
380+
});
381+
322382
it("attachment exists", async () => {
323383
{
324384
const session = store.openSession();

0 commit comments

Comments
 (0)