Skip to content
Open
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 @@ -13,6 +13,7 @@ General:

Blob:

- Fixed Stage Block responses for API versions after `2019-02-02` to return `Content-MD5` when the request supplies `Content-MD5`, matching Azure Storage while preserving CRC64 responses for requests without MD5. (issue #2394)
- Fixed block blob uploads with `If-None-Match: *` returning `BlobAlreadyExists` before validating an active lease, matching Azure Storage's `LeaseIdMissing` and lease mismatch error precedence. (issue #2637)
- Fixed service- and container-level Filter Blobs requests failing when the optional `where` query parameter is omitted.
- Fixed blob operations hanging when a client disconnects before the operation queue processes the request. (issue #2575)
Expand Down
15 changes: 13 additions & 2 deletions src/blob/handlers/BlockBlobHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import {
validateTransactionalChecksumHeaders
} from "../utils/utils";

const STAGE_BLOCK_CONTENT_MD5_RESPONSE_API_VERSION = "2019-02-02";

/**
* Agents for the loopback self-request stageBlockFromURL makes to read a copy
* source, keyed by the certificate they pin. Shared so requests reuse one
Expand Down Expand Up @@ -485,13 +487,22 @@ 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 isAfterContentMD5ResponseVersion =
requestApiVersion > STAGE_BLOCK_CONTENT_MD5_RESPONSE_API_VERSION;
const shouldReturnContentMD5 =
contentMD5 !== undefined && isAfterContentMD5ResponseVersion;

const block: BlockModel = {
accountName,
Expand All @@ -512,7 +523,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.
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 omits 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.equal(result.contentMD5, undefined);
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