UNPKG

azurite

Version:

An open source Azure Storage API compatible server

343 lines 19.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BlobBatchHandler = void 0; const tslib_1 = require("tslib"); const ms_rest_js_1 = require("@azure/ms-rest-js"); const AccountSASAuthenticator_1 = tslib_1.__importDefault(require("../authentication/AccountSASAuthenticator")); const BlobSASAuthenticator_1 = tslib_1.__importDefault(require("../authentication/BlobSASAuthenticator")); const BlobSharedKeyAuthenticator_1 = tslib_1.__importDefault(require("../authentication/BlobSharedKeyAuthenticator")); const BlobTokenAuthenticator_1 = tslib_1.__importDefault(require("../authentication/BlobTokenAuthenticator")); const PublicAccessAuthenticator_1 = tslib_1.__importDefault(require("../authentication/PublicAccessAuthenticator")); const BlobStorageContext_1 = tslib_1.__importDefault(require("../context/BlobStorageContext")); const StorageError_1 = tslib_1.__importDefault(require("../errors/StorageError")); const StorageErrorFactory_1 = tslib_1.__importDefault(require("../errors/StorageErrorFactory")); const operation_1 = tslib_1.__importDefault(require("../generated/artifacts/operation")); const Context_1 = tslib_1.__importDefault(require("../generated/Context")); const MiddlewareError_1 = tslib_1.__importDefault(require("../generated/errors/MiddlewareError")); const deserializer_middleware_1 = tslib_1.__importDefault(require("../generated/middleware/deserializer.middleware")); const dispatch_middleware_1 = tslib_1.__importDefault(require("../generated/middleware/dispatch.middleware")); const end_middleware_1 = tslib_1.__importDefault(require("../generated/middleware/end.middleware")); const error_middleware_1 = tslib_1.__importDefault(require("../generated/middleware/error.middleware")); const HandlerMiddlewareFactory_1 = tslib_1.__importDefault(require("../generated/middleware/HandlerMiddlewareFactory")); const serializer_middleware_1 = tslib_1.__importDefault(require("../generated/middleware/serializer.middleware")); const AuthenticationMiddlewareFactory_1 = tslib_1.__importDefault(require("../middlewares/AuthenticationMiddlewareFactory")); const blobStorageContext_middleware_1 = require("../middlewares/blobStorageContext.middleware"); const constants_1 = require("../utils/constants"); const AppendBlobHandler_1 = tslib_1.__importDefault(require("./AppendBlobHandler")); const BlobBatchSubRequest_1 = require("./BlobBatchSubRequest"); const BlobBatchSubResponse_1 = require("./BlobBatchSubResponse"); const BlobHandler_1 = tslib_1.__importDefault(require("./BlobHandler")); const BlockBlobHandler_1 = tslib_1.__importDefault(require("./BlockBlobHandler")); const ContainerHandler_1 = tslib_1.__importDefault(require("./ContainerHandler")); const PageBlobHandler_1 = tslib_1.__importDefault(require("./PageBlobHandler")); const PageBlobRangesManager_1 = tslib_1.__importDefault(require("./PageBlobRangesManager")); const ServiceHandler_1 = tslib_1.__importDefault(require("./ServiceHandler")); class BlobBatchHandler { constructor(accountDataStore, oauth, metadataStore, extentStore, logger, loose, disableProductStyle) { this.accountDataStore = accountDataStore; this.oauth = oauth; this.metadataStore = metadataStore; this.extentStore = extentStore; this.logger = logger; this.loose = loose; this.disableProductStyle = disableProductStyle; const subRequestContextMiddleware = (req, res, locals, next) => { const urlbuilder = ms_rest_js_1.URLBuilder.parse(req.getUrl()); (0, blobStorageContext_middleware_1.internalBlobStorageContextMiddleware)(new BlobStorageContext_1.default(locals, constants_1.DEFAULT_CONTEXT_PATH), req, res, urlbuilder.getHost(), urlbuilder.getPath(), next, true, this.disableProductStyle, this.loose); }; const subRequestDispatchMiddleware = (req, res, locals, next) => { (0, dispatch_middleware_1.default)(new Context_1.default(locals, constants_1.DEFAULT_CONTEXT_PATH, req, res), req, next, this.logger); }; // AuthN middleware, like shared key auth or SAS auth const authenticationMiddlewareFactory = new AuthenticationMiddlewareFactory_1.default(this.logger); this.authenticators = [ new PublicAccessAuthenticator_1.default(this.metadataStore, logger), new BlobSharedKeyAuthenticator_1.default(this.accountDataStore, logger), new AccountSASAuthenticator_1.default(this.accountDataStore, this.metadataStore, logger), new BlobSASAuthenticator_1.default(this.accountDataStore, this.metadataStore, logger) ]; if (this.oauth !== undefined) { this.authenticators.push(new BlobTokenAuthenticator_1.default(this.accountDataStore, this.oauth, logger)); } const subRequestAuthenticationMiddleware = (req, res, locals, next) => { const context = new BlobStorageContext_1.default(locals, constants_1.DEFAULT_CONTEXT_PATH); authenticationMiddlewareFactory.authenticate(context, req, res, this.authenticators).then(pass => { // TODO: To support public access, we need to modify here to reject request later in handler if (pass) { next(); } else { next(StorageErrorFactory_1.default.getAuthorizationFailure(context.contextId)); } }) .catch(errorInfo => next(errorInfo)); }; const subRequestDeserializeMiddleware = (req, res, locals, next) => { (0, deserializer_middleware_1.default)(new Context_1.default(locals, constants_1.DEFAULT_CONTEXT_PATH, req, res), req, next, this.logger); }; this.handlers = { appendBlobHandler: new AppendBlobHandler_1.default(this.metadataStore, this.extentStore, this.logger, this.loose), blobHandler: new BlobHandler_1.default(this.metadataStore, this.extentStore, this.logger, this.loose, new PageBlobRangesManager_1.default()), blockBlobHandler: new BlockBlobHandler_1.default(this.metadataStore, this.extentStore, this.logger, this.loose), containerHandler: new ContainerHandler_1.default(this.accountDataStore, this.oauth, this.metadataStore, this.extentStore, this.logger, this.loose), pageBlobHandler: new PageBlobHandler_1.default(this.metadataStore, this.extentStore, this.logger, this.loose, new PageBlobRangesManager_1.default()), serviceHandler: new ServiceHandler_1.default(this.accountDataStore, this.oauth, this.metadataStore, this.extentStore, this.logger, this.loose) }; const handlerMiddlewareFactory = new HandlerMiddlewareFactory_1.default(this.handlers, this.logger); const subRequestHandlerMiddleware = (req, res, locals, next) => { handlerMiddlewareFactory.createHandlerMiddleware()(new Context_1.default(locals, constants_1.DEFAULT_CONTEXT_PATH, req, res), next); }; const subRequestSerializeMiddleWare = (req, res, locals, next) => { (0, serializer_middleware_1.default)(new Context_1.default(locals, constants_1.DEFAULT_CONTEXT_PATH, req, res), res, next, this.logger); }; const subRequestErrorMiddleWare = (err, req, res, locals, next) => { (0, error_middleware_1.default)(new Context_1.default(locals, constants_1.DEFAULT_CONTEXT_PATH, req, res), err, req, res, next, this.logger); }; const subRequestEndMiddleWare = (req, res, locals, next) => { (0, end_middleware_1.default)(new Context_1.default(locals, constants_1.DEFAULT_CONTEXT_PATH, req, res), res, this.logger); next(); }; this.handlePipeline = [ subRequestContextMiddleware, subRequestDispatchMiddleware, subRequestAuthenticationMiddleware, subRequestDeserializeMiddleware, subRequestHandlerMiddleware, subRequestSerializeMiddleWare, subRequestEndMiddleWare ]; this.operationFinder = [ subRequestContextMiddleware, subRequestDispatchMiddleware, ]; this.errorHandler = subRequestErrorMiddleWare; } async streamToBuffer2(stream, buffer, encoding) { let pos = 0; // Position in stream const bufferSize = buffer.length; return new Promise((resolve, reject) => { stream.on("readable", () => { let chunk = stream.read(); if (!chunk) { return; } if (typeof chunk === "string") { chunk = Buffer.from(chunk, encoding); } if (pos + chunk.length > bufferSize) { reject(new Error(`Stream exceeds buffer size. Buffer size: ${bufferSize}`)); return; } buffer.fill(chunk, pos, pos + chunk.length); pos += chunk.length; }); stream.on("end", () => { resolve(pos); }); stream.on("error", reject); }); } async requestBodyToString(body) { let buffer = Buffer.alloc(4 * 1024 * 1024); const responseLength = await this.streamToBuffer2(body, buffer); // Slice the buffer to trim the empty ending. buffer = buffer.slice(0, responseLength); return buffer.toString(); } async getSubRequestOperation(request) { const subRequestHandlePipeline = this.operationFinder; const fakeResponse = new BlobBatchSubResponse_1.BlobBatchSubResponse(0, "HTTP/1.1"); return new Promise((resolve, reject) => { const locals = {}; let i = 0; const next = (error) => { if (error) { reject(error); } else { ++i; if (i < subRequestHandlePipeline.length) { subRequestHandlePipeline[i](request, fakeResponse, locals, next); } else { resolve((new Context_1.default(locals, constants_1.DEFAULT_CONTEXT_PATH, request, fakeResponse)).operation); } } }; subRequestHandlePipeline[i](request, fakeResponse, locals, next); }); } async parseSubRequests(commonRequestId, perRequestPrefix, batchRequestEnding, subRequestPathPrefix, request, body) { const requestAll = body.split(batchRequestEnding); const response1 = requestAll[0]; // string after ending is useless const response2 = response1.split(perRequestPrefix); const subRequests = response2.slice(1); const blobBatchSubRequests = []; let previousOperation; for (const subRequest of subRequests) { const requestLines = subRequest.split(`${constants_1.HTTP_LINE_ENDING}`); // Content-Encoding // Content-Type // Content-ID // empty line // Operation infos if (requestLines.length < 5) throw new Error("Bad request"); // Get Content_ID let lineIndex = 0; let content_id; while (lineIndex < requestLines.length) { if (requestLines[lineIndex] === '') break; const header = requestLines[lineIndex].split(constants_1.HTTP_HEADER_DELIMITER, 2); if (header.length !== 2) throw new Error("Bad Request"); if (header[0].toLocaleLowerCase() === "content-id") { content_id = parseInt(header[1], 10); } ++lineIndex; } if (content_id === undefined) throw new Error("Bad request"); // "DELETE /container166063791875402779/blob0 HTTP/1.1" ++lineIndex; const operationInfos = requestLines[lineIndex].split(" "); if (operationInfos.length < 3) throw new Error("Bad request"); const requestPath = operationInfos[1].startsWith("/") ? operationInfos[1] : "/" + operationInfos[1]; if (!requestPath.startsWith(subRequestPathPrefix)) { throw new Error("Request from a different container"); } const url = `${request.getEndpoint()}${requestPath}`; const method = operationInfos[0]; const blobBatchSubRequest = new BlobBatchSubRequest_1.BlobBatchSubRequest(content_id, url, method, operationInfos[2], {}); ++lineIndex; while (lineIndex < requestLines.length) { if (requestLines[lineIndex] === '') break; // Last line const header = requestLines[lineIndex].split(constants_1.HTTP_HEADER_DELIMITER, 2); if (header.length !== 2) throw new Error("Bad Request"); blobBatchSubRequest.setHeader(header[0], header[1]); ++lineIndex; } const operation = await this.getSubRequestOperation(blobBatchSubRequest); if (operation !== operation_1.default.Blob_Delete && operation !== operation_1.default.Blob_SetTier) { throw new Error("Not supported operation"); } if (previousOperation === undefined) { previousOperation = operation; } else if (operation !== previousOperation) { throw new StorageError_1.default(400, "AllBatchSubRequestsShouldBeSameApi", "All batch subrequests should be the same api.", commonRequestId); } blobBatchSubRequests.push(blobBatchSubRequest); } if (blobBatchSubRequests.length === 0) { throw new Error("Bad Request"); } return blobBatchSubRequests; } serializeSubResponse(subResponsePrefix, responseEnding, subResponses) { let responseBody = ""; subResponses.forEach(subResponse => { responseBody += subResponsePrefix, responseBody += "Content-Type: application/http" + constants_1.HTTP_LINE_ENDING; if (subResponse.content_id !== undefined) { responseBody += "Content-ID" + constants_1.HTTP_HEADER_DELIMITER + subResponse.content_id.toString() + constants_1.HTTP_LINE_ENDING; } responseBody += constants_1.HTTP_LINE_ENDING; responseBody += subResponse.protocolWithVersion + " " + subResponse.getStatusCode().toString() + " " + subResponse.getStatusMessage() + constants_1.HTTP_LINE_ENDING; const headers = subResponse.getHeaders(); for (const key of Object.keys(headers)) { responseBody += key + constants_1.HTTP_HEADER_DELIMITER + headers[key] + constants_1.HTTP_LINE_ENDING; } const bodyContent = subResponse.getBodyContent(); if (bodyContent !== "") { responseBody += constants_1.HTTP_LINE_ENDING + bodyContent + constants_1.HTTP_LINE_ENDING; } responseBody += constants_1.HTTP_LINE_ENDING; }); responseBody += responseEnding; return responseBody; } async submitBatch(body, requestBatchBoundary, subRequestPathPrefix, batchRequest, context) { const perRequestPrefix = `--${requestBatchBoundary}${constants_1.HTTP_LINE_ENDING}`; const batchRequestEnding = `--${requestBatchBoundary}--`; const requestBody = await this.requestBodyToString(body); let subRequests; let error; try { subRequests = await this.parseSubRequests(context.contextId, perRequestPrefix, batchRequestEnding, subRequestPathPrefix, batchRequest, requestBody); } catch (err) { if ((err instanceof MiddlewareError_1.default) && err.hasOwnProperty("storageErrorCode") && err.hasOwnProperty("storageErrorMessage") && err.hasOwnProperty("storageRequestID")) { error = err; } else { error = new StorageError_1.default(400, "InvalidInput", "One of the request inputs is not valid.", context.contextId); } } const subResponses = []; if (subRequests && subRequests.length > 256) { error = new StorageError_1.default(400, "ExceedsMaxBatchRequestCount", "The batch operation exceeds maximum number of allowed subrequests.", context.contextId); } if (error) { this.logger.error(`BlobBatchHandler: ${error.message}`, context.contextId); const errorResponse = new BlobBatchSubResponse_1.BlobBatchSubResponse(undefined, "HTTP/1.1"); await this.HandleOneFailedRequest(error, batchRequest, errorResponse); subResponses.push(errorResponse); } else { for (const subRequest of subRequests) { this.logger.info(`BlobBatchHandler: starting on subrequest ${subRequest.content_id}`, context.contextId); const subResponse = new BlobBatchSubResponse_1.BlobBatchSubResponse(subRequest.content_id, subRequest.protocolWithVersion); await this.HandleOneSubRequest(subRequest, subResponse); subResponses.push(subResponse); this.logger.info(`BlobBatchHandler: completed on subrequest ${subRequest.content_id} ${subResponse.getHeader("x-ms-request-id")}`, context.contextId); } } return this.serializeSubResponse(perRequestPrefix, batchRequestEnding, subResponses); } HandleOneSubRequest(request, response) { const subRequestHandlePipeline = this.handlePipeline; const subRequestErrorHandler = this.errorHandler; let completed = false; return new Promise((resolve, reject) => { const locals = {}; let i = 0; const next = (error) => { if (completed) { resolve(); return; } if (error) { subRequestErrorHandler(error, request, response, locals, next); completed = true; } else { ++i; if (i < subRequestHandlePipeline.length) { subRequestHandlePipeline[i](request, response, locals, next); } else { resolve(); } } }; subRequestHandlePipeline[i](request, response, locals, next); }); } HandleOneFailedRequest(err, request, response) { const subRequestErrorHandler = this.errorHandler; return new Promise((resolve, reject) => { subRequestErrorHandler(err, request, response, {}, resolve); }); } } exports.BlobBatchHandler = BlobBatchHandler; //# sourceMappingURL=BlobBatchHandler.js.map