diff --git a/ChangeLog.md b/ChangeLog.md index 18bd2b7e7..167f01c02 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -13,6 +13,7 @@ Blob: - GetBlob on Archive tier blobs now fails as expected. - Fixed issue of download 0 size blob with range > 0 should have header "Content-Range: bytes \*/0" in returned error. (issue #2458) - Aligned behavior with Azure to ignore invalid range requests for blob downloads. (issue #2458) +- Consider both Content-MD5 and x-ms-blob-content-md5 when creating a blob. Table: diff --git a/src/blob/handlers/BlockBlobHandler.ts b/src/blob/handlers/BlockBlobHandler.ts index 84703faff..ec9b11a12 100644 --- a/src/blob/handlers/BlockBlobHandler.ts +++ b/src/blob/handlers/BlockBlobHandler.ts @@ -46,6 +46,7 @@ export default class BlockBlobHandler context.request!.getHeader("content-type") || "application/octet-stream"; const contentMD5 = context.request!.getHeader("content-md5") + || context.request!.getHeader("x-ms-blob-content-md5") ? options.blobHTTPHeaders.blobContentMD5 || context.request!.getHeader("content-md5") : undefined; @@ -182,6 +183,7 @@ export default class BlockBlobHandler // https://learn.microsoft.com/en-us/rest/api/storageservices/put-block // options.blobHTTPHeaders = options.blobHTTPHeaders || {}; const contentMD5 = context.request!.getHeader("content-md5") + || context.request!.getHeader("x-ms-blob-content-md5") ? options.transactionalContentMD5 || context.request!.getHeader("content-md5") : undefined; diff --git a/tests/blob/RequestPolicy/CustomHeaderPolicyFactory.ts b/tests/blob/RequestPolicy/CustomHeaderPolicyFactory.ts new file mode 100644 index 000000000..0244b2b94 --- /dev/null +++ b/tests/blob/RequestPolicy/CustomHeaderPolicyFactory.ts @@ -0,0 +1,38 @@ +import { BaseRequestPolicy, WebResource } from "@azure/storage-blob"; + +// Create a policy factory with create() method provided +// In TypeScript, following factory class needs to implement Azure.RequestPolicyFactory type +export default class CustomHeaderPolicyFactory { + // Constructor to accept parameters + private key: string; + private value: string; + + constructor(key: string, value: string) { + this.key = key; + this.value = value; + } + + create(nextPolicy: any, options: any) { + return new CustomHeaderPolicy(nextPolicy, options, this.key, this.value); + } +} + +// Create a policy by extending from Azure.BaseRequestPolicy +class CustomHeaderPolicy extends BaseRequestPolicy { + private key: string; + private value: string; + + constructor(nextPolicy: any, options: any, key: string, value: string) { + super(nextPolicy, options); + this.key = key; + this.value = value; + } + + // Customize HTTP requests and responses by overriding sendRequest + // Parameter request is Azure.WebResource type + async sendRequest(request: WebResource) { + request.headers.set(this.key, this.value); + + return await this._nextPolicy.sendRequest(request); + } +} diff --git a/tests/blob/apis/blob.test.ts b/tests/blob/apis/blob.test.ts index dc0260466..bfabba7cf 100644 --- a/tests/blob/apis/blob.test.ts +++ b/tests/blob/apis/blob.test.ts @@ -18,6 +18,7 @@ import { getUniqueName, sleep } from "../../testutils"; +import CustomHeaderPolicyFactory from "../RequestPolicy/CustomHeaderPolicyFactory"; import RangePolicyFactory from "../RequestPolicy/RangePolicyFactory"; // Set true to enable debug log @@ -2527,6 +2528,35 @@ describe("BlobAPIs", () => { assert.deepStrictEqual(result, tags); }); + it("upload invalid x-ms-blob-content-md5 @loki @sql", async () => { + const pipeline = newPipeline( + new StorageSharedKeyCredential( + EMULATOR_ACCOUNT_NAME, + EMULATOR_ACCOUNT_KEY + ), + { + retryOptions: { maxTries: 1 }, + // Make sure socket is closed once the operation is done. + keepAliveOptions: { enable: false } + } + ); + pipeline.factories.unshift( + new CustomHeaderPolicyFactory("x-ms-blob-content-md5", "invalid-md5") + ); + const serviceClient = new BlobServiceClient(baseURL, pipeline); + const containerClient = serviceClient.getContainerClient(containerName); + + const blobClient = containerClient.getBlockBlobClient(blobName); + try { + await blobClient.upload("hello", 5, { tier: "Hot" }); + assert.fail("Expected MD5 error"); + } catch (err) { + assert.deepStrictEqual((err as any).statusCode, 400); + assert.deepStrictEqual((err as any).code, 'InvalidOperation'); + assert.deepStrictEqual((err as any).details.errorCode, 'InvalidOperation'); + } + }); + it("Acquire Lease on Breaking Lease status, if LeaseId not match, throw LeaseIdMismatchWithLease error @loki @sql", async () => { // TODO: implement the case later });