Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ General:

Blob:

- Fixed Stage Block responses for API version `2019-02-02` and later to return `Content-MD5` when the request supplies `Content-MD5`, matching Azure Storage while preserving CRC64 responses for requests without MD5. (issue #2394)
- Bumped the default Blob service API version to `2026-10-06`, and added `2026-10-06` to the supported Blob service API versions.
- Fixed Blob GET and HEAD requests for nonexistent blobs with `If-Match` so they now return `BlobNotFound` instead of `ConditionNotMet`, matching Azure Storage. (issue #2751)
- Added opt-in, per-account Blob Versioning configured with `--accountConfigFilePath` or `--accountConfigAsJson`; Azurite preserves previous versions on supported blob writes and supports listing, reading, restoring, and deleting specific versions.
Expand Down
11 changes: 9 additions & 2 deletions src/blob/handlers/BlockBlobHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -487,13 +487,20 @@ export default class BlockBlobHandler
persistency,
context.contextId
);
const { crc64: calculatedCRC64 } =
const { md5: calculatedContentMD5, crc64: calculatedCRC64 } =
await computeAndValidateTransactionalChecksums(
stream,
{ md5: contentMD5, crc64: contentCRC64 },
context.contextId,
{ crc64: contentMD5 === undefined }
);
const requestApiVersion = context.request!.getHeader(
HeaderConstants.X_MS_VERSION
) || BLOB_API_VERSION;
// Blob API versions are validated zero-padded YYYY-MM-DD strings here, so
// lexicographic comparison matches chronological order.
const shouldReturnContentMD5 =
contentMD5 !== undefined && requestApiVersion >= "2019-02-02";
Comment on lines +502 to +503

const block: BlockModel = {
accountName,
Expand All @@ -514,7 +521,7 @@ export default class BlockBlobHandler

const response: Models.BlockBlobStageBlockResponse = {
statusCode: 201,
contentMD5: undefined, // TODO: Block content MD5
contentMD5: shouldReturnContentMD5 ? calculatedContentMD5 : undefined,
xMsContentCrc64: calculatedCRC64,
requestId: blobCtx.contextId,
version: BLOB_API_VERSION,
Expand Down
25 changes: 23 additions & 2 deletions tests/blob/apis/blockblob.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1958,26 +1958,47 @@ describe("BlockBlobAPIs", () => {
assert.fail("Did not throw an exception.");
});

it("stageBlock with md5 hash check @loki @sql", async () => {
it("stageBlock with md5 hash check returns Content-MD5 after 2019-02-02 @loki @sql", async () => {
const body = "HelloWorld";
const md5 = crypto.createHash("md5").update(body, "utf8").digest();
const options = {
transactionalContentMD5: new Uint8Array(md5)
};

await blockBlobClient.stageBlock(
const result = await blockBlobClient.stageBlock(
Comment thread
Copilot marked this conversation as resolved.
Resolved
base64encode("1"),
body,
body.length,
options
);
assert.deepStrictEqual(Buffer.from(result.contentMD5!), md5);
assert.equal(result.xMsContentCrc64, undefined);

const listResponse = await blockBlobClient.getBlockList("uncommitted");
assert.equal(listResponse.uncommittedBlocks!.length, 1);
assert.equal(listResponse.uncommittedBlocks![0].name, base64encode("1"));
assert.equal(listResponse.uncommittedBlocks![0].size, body.length);
});

it("stageBlock with md5 hash check returns Content-MD5 for 2019-02-02 @loki @sql", async () => {
const body = "HelloWorld";
const md5 = crypto.createHash("md5").update(body, "utf8").digest();
const oldVersionClient = getBlockBlobClientWithRawHeaders(
containerName,
blobName,
[{ key: "x-ms-version", value: "2019-02-02" }]
);

const result = await oldVersionClient.stageBlock(
base64encode("1"),
body,
body.length,
{ transactionalContentMD5: new Uint8Array(md5) }
);
assert.deepStrictEqual(Buffer.from(result.contentMD5!), md5);
assert.equal(result.xMsContentCrc64, undefined);
});

it("stageBlock with correct crc64 should succeed @loki @sql", async () => {
const body = "HelloWorld";
const crc64 = getCRC64FromString(body);
Expand Down
Loading