in src/blob/handlers/BlockBlobHandler.ts [30:162]
public async upload(
body: NodeJS.ReadableStream,
contentLength: number,
options: Models.BlockBlobUploadOptionalParams,
context: Context
): Promise<Models.BlockBlobUploadResponse> {
// TODO: Check Lease status, and set to available if it's expired, see sample in BlobHandler.setMetadata()
const blobCtx = new BlobStorageContext(context);
const accountName = blobCtx.account!;
const containerName = blobCtx.container!;
const blobName = blobCtx.blob!;
const date = context.startTime!;
const etag = newEtag();
options.blobHTTPHeaders = options.blobHTTPHeaders || {};
const contentType =
options.blobHTTPHeaders.blobContentType ||
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;
await this.metadataStore.checkContainerExist(
context,
accountName,
containerName
);
const persistency = await this.extentStore.appendExtent(
body,
context.contextId
);
if (persistency.count !== contentLength) {
throw StorageErrorFactory.getInvalidOperation(
blobCtx.contextId!,
`The size of the request body ${persistency.count} mismatches the content-length ${contentLength}.`
);
}
// Calculate MD5 for validation
const stream = await this.extentStore.readExtent(
persistency,
context.contextId
);
const calculatedContentMD5 = await getMD5FromStream(stream);
if (contentMD5 !== undefined) {
if (typeof contentMD5 === "string") {
const calculatedContentMD5String = Buffer.from(
calculatedContentMD5
).toString("base64");
if (contentMD5 !== calculatedContentMD5String) {
throw StorageErrorFactory.getInvalidOperation(
context.contextId!,
"Provided contentMD5 doesn't match."
);
}
} else {
if (!Buffer.from(contentMD5).equals(calculatedContentMD5)) {
throw StorageErrorFactory.getInvalidOperation(
context.contextId!,
"Provided contentMD5 doesn't match."
);
}
}
}
const blob: BlobModel = {
deleted: false,
// Preserve metadata key case
metadata: convertRawHeadersToMetadata(blobCtx.request!.getRawHeaders(), context.contextId!),
accountName,
containerName,
name: blobName,
properties: {
creationTime: date,
lastModified: date,
etag,
contentLength,
contentType,
contentEncoding: options.blobHTTPHeaders.blobContentEncoding,
contentLanguage: options.blobHTTPHeaders.blobContentLanguage,
contentMD5: calculatedContentMD5,
contentDisposition: options.blobHTTPHeaders.blobContentDisposition,
cacheControl: options.blobHTTPHeaders.blobCacheControl,
blobType: Models.BlobType.BlockBlob,
leaseStatus: Models.LeaseStatusType.Unlocked,
leaseState: Models.LeaseStateType.Available,
serverEncrypted: true,
accessTier: Models.AccessTier.Hot,
accessTierInferred: true,
accessTierChangeTime: date
},
snapshot: "",
isCommitted: true,
persistency,
blobTags: options.blobTagsString === undefined ? undefined : getTagsFromString(options.blobTagsString, context.contextId!),
};
if (options.tier !== undefined) {
blob.properties.accessTier = this.parseTier(options.tier);
if (blob.properties.accessTier === undefined) {
throw StorageErrorFactory.getInvalidHeaderValue(context.contextId, {
HeaderName: "x-ms-access-tier",
HeaderValue: `${options.tier}`
});
}
blob.properties.accessTierInferred = false;
}
// TODO: Need a lock for multi keys including containerName and blobName
// TODO: Provide a specified function.
await this.metadataStore.createBlob(
context,
blob,
options.leaseAccessConditions,
options.modifiedAccessConditions
);
const response: Models.BlockBlobUploadResponse = {
statusCode: 201,
eTag: etag,
lastModified: date,
contentMD5: blob.properties.contentMD5,
requestId: blobCtx.contextId,
version: BLOB_API_VERSION,
date,
isServerEncrypted: true,
clientRequestId: options.requestId
};
return response;
}