minio
Version:
S3 Compatible Cloud Storage client
1,461 lines (1,154 loc) • 144 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _exportNames = {
Client: true,
CopyConditions: true,
PostPolicy: true
};
exports.PostPolicy = exports.CopyConditions = exports.Client = void 0;
var _fs = _interopRequireDefault(require("fs"));
var _http = _interopRequireDefault(require("http"));
var _https = _interopRequireDefault(require("https"));
var _stream = _interopRequireDefault(require("stream"));
var _blockStream = _interopRequireDefault(require("block-stream2"));
var _xml = _interopRequireDefault(require("xml"));
var _xml2js = _interopRequireDefault(require("xml2js"));
var _async = _interopRequireDefault(require("async"));
var _queryString = _interopRequireDefault(require("query-string"));
var _mkdirp = _interopRequireDefault(require("mkdirp"));
var _path = _interopRequireDefault(require("path"));
var _lodash = _interopRequireDefault(require("lodash"));
var _webEncoding = require("web-encoding");
var _helpers = require("./helpers.js");
var _signing = require("./signing.js");
var _objectUploader = _interopRequireDefault(require("./object-uploader"));
var transformers = _interopRequireWildcard(require("./transformers"));
var errors = _interopRequireWildcard(require("./errors.js"));
var _s3Endpoints = require("./s3-endpoints.js");
var _notification = require("./notification");
Object.keys(_notification).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
if (key in exports && exports[key] === _notification[key]) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _notification[key];
}
});
});
var _extensions = _interopRequireDefault(require("./extensions"));
var _CredentialProvider = _interopRequireDefault(require("./CredentialProvider"));
var _xmlParsers = require("./xml-parsers");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/*
* MinIO Javascript Library for Amazon S3 Compatible Cloud Storage, (C) 2015 MinIO, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var Package = require('../../package.json');
class Client {
constructor(params) {
if (typeof params.secure !== 'undefined') throw new Error('"secure" option deprecated, "useSSL" should be used instead'); // Default values if not specified.
if (typeof params.useSSL === 'undefined') params.useSSL = true;
if (!params.port) params.port = 0; // Validate input params.
if (!(0, _helpers.isValidEndpoint)(params.endPoint)) {
throw new errors.InvalidEndpointError(`Invalid endPoint : ${params.endPoint}`);
}
if (!(0, _helpers.isValidPort)(params.port)) {
throw new errors.InvalidArgumentError(`Invalid port : ${params.port}`);
}
if (!(0, _helpers.isBoolean)(params.useSSL)) {
throw new errors.InvalidArgumentError(`Invalid useSSL flag type : ${params.useSSL}, expected to be of type "boolean"`);
} // Validate region only if its set.
if (params.region) {
if (!(0, _helpers.isString)(params.region)) {
throw new errors.InvalidArgumentError(`Invalid region : ${params.region}`);
}
}
var host = params.endPoint.toLowerCase();
var port = params.port;
var protocol = '';
var transport; // Validate if configuration is not using SSL
// for constructing relevant endpoints.
if (params.useSSL === false) {
transport = _http.default;
protocol = 'http:';
if (port === 0) {
port = 80;
}
} else {
// Defaults to secure.
transport = _https.default;
protocol = 'https:';
if (port === 0) {
port = 443;
}
} // if custom transport is set, use it.
if (params.transport) {
if (!(0, _helpers.isObject)(params.transport)) {
throw new errors.InvalidArgumentError('Invalid transport type : ${params.transport}, expected to be type "object"');
}
transport = params.transport;
} // User Agent should always following the below style.
// Please open an issue to discuss any new changes here.
//
// MinIO (OS; ARCH) LIB/VER APP/VER
//
var libraryComments = `(${process.platform}; ${process.arch})`;
var libraryAgent = `MinIO ${libraryComments} minio-js/${Package.version}`; // User agent block ends.
this.transport = transport;
this.host = host;
this.port = port;
this.protocol = protocol;
this.accessKey = params.accessKey;
this.secretKey = params.secretKey;
this.sessionToken = params.sessionToken;
this.userAgent = `${libraryAgent}`; // Default path style is true
if (params.pathStyle === undefined) {
this.pathStyle = true;
} else {
this.pathStyle = params.pathStyle;
}
if (!this.accessKey) this.accessKey = '';
if (!this.secretKey) this.secretKey = '';
this.anonymous = !this.accessKey || !this.secretKey;
if (params.credentialsProvider) {
this.credentialsProvider = params.credentialsProvider;
this.checkAndRefreshCreds();
}
this.regionMap = {};
if (params.region) {
this.region = params.region;
}
this.partSize = 64 * 1024 * 1024;
if (params.partSize) {
this.partSize = params.partSize;
this.overRidePartSize = true;
}
if (this.partSize < 5 * 1024 * 1024) {
throw new errors.InvalidArgumentError(`Part size should be greater than 5MB`);
}
if (this.partSize > 5 * 1024 * 1024 * 1024) {
throw new errors.InvalidArgumentError(`Part size should be less than 5GB`);
}
this.maximumPartSize = 5 * 1024 * 1024 * 1024;
this.maxObjectSize = 5 * 1024 * 1024 * 1024 * 1024; // SHA256 is enabled only for authenticated http requests. If the request is authenticated
// and the connection is https we use x-amz-content-sha256=UNSIGNED-PAYLOAD
// header for signature calculation.
this.enableSHA256 = !this.anonymous && !params.useSSL;
this.s3AccelerateEndpoint = params.s3AccelerateEndpoint || null;
this.reqOptions = {};
} // This is s3 Specific and does not hold validity in any other Object storage.
getAccelerateEndPointIfSet(bucketName, objectName) {
if (!_lodash.default.isEmpty(this.s3AccelerateEndpoint) && !_lodash.default.isEmpty(bucketName) && !_lodash.default.isEmpty(objectName)) {
// http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html
// Disable transfer acceleration for non-compliant bucket names.
if (bucketName.indexOf(".") !== -1) {
throw new Error(`Transfer Acceleration is not supported for non compliant bucket:${bucketName}`);
} // If transfer acceleration is requested set new host.
// For more details about enabling transfer acceleration read here.
// http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html
return this.s3AccelerateEndpoint;
}
return false;
}
/**
* @param endPoint _string_ valid S3 acceleration end point
*/
setS3TransferAccelerate(endPoint) {
this.s3AccelerateEndpoint = endPoint;
} // Sets the supported request options.
setRequestOptions(options) {
if (!(0, _helpers.isObject)(options)) {
throw new TypeError('request options should be of type "object"');
}
this.reqOptions = _lodash.default.pick(options, ['agent', 'ca', 'cert', 'ciphers', 'clientCertEngine', 'crl', 'dhparam', 'ecdhCurve', 'family', 'honorCipherOrder', 'key', 'passphrase', 'pfx', 'rejectUnauthorized', 'secureOptions', 'secureProtocol', 'servername', 'sessionIdContext']);
} // returns *options* object that can be used with http.request()
// Takes care of constructing virtual-host-style or path-style hostname
getRequestOptions(opts) {
var method = opts.method;
var region = opts.region;
var bucketName = opts.bucketName;
var objectName = opts.objectName;
var headers = opts.headers;
var query = opts.query;
var reqOptions = {
method
};
reqOptions.headers = {}; // Verify if virtual host supported.
var virtualHostStyle;
if (bucketName) {
virtualHostStyle = (0, _helpers.isVirtualHostStyle)(this.host, this.protocol, bucketName, this.pathStyle);
}
if (this.port) reqOptions.port = this.port;
reqOptions.protocol = this.protocol;
if (objectName) {
objectName = `${(0, _helpers.uriResourceEscape)(objectName)}`;
}
reqOptions.path = '/'; // Save host.
reqOptions.host = this.host; // For Amazon S3 endpoint, get endpoint based on region.
if ((0, _helpers.isAmazonEndpoint)(reqOptions.host)) {
const accelerateEndPoint = this.getAccelerateEndPointIfSet(bucketName, objectName);
if (accelerateEndPoint) {
reqOptions.host = `${accelerateEndPoint}`;
} else {
reqOptions.host = (0, _s3Endpoints.getS3Endpoint)(region);
}
}
if (virtualHostStyle && !opts.pathStyle) {
// For all hosts which support virtual host style, `bucketName`
// is part of the hostname in the following format:
//
// var host = 'bucketName.example.com'
//
if (bucketName) reqOptions.host = `${bucketName}.${reqOptions.host}`;
if (objectName) reqOptions.path = `/${objectName}`;
} else {
// For all S3 compatible storage services we will fallback to
// path style requests, where `bucketName` is part of the URI
// path.
if (bucketName) reqOptions.path = `/${bucketName}`;
if (objectName) reqOptions.path = `/${bucketName}/${objectName}`;
}
if (query) reqOptions.path += `?${query}`;
reqOptions.headers.host = reqOptions.host;
if (reqOptions.protocol === 'http:' && reqOptions.port !== 80 || reqOptions.protocol === 'https:' && reqOptions.port !== 443) {
reqOptions.headers.host = `${reqOptions.host}:${reqOptions.port}`;
}
reqOptions.headers['user-agent'] = this.userAgent;
if (headers) {
// have all header keys in lower case - to make signing easy
_lodash.default.map(headers, (v, k) => reqOptions.headers[k.toLowerCase()] = v);
} // Use any request option specified in minioClient.setRequestOptions()
reqOptions = Object.assign({}, this.reqOptions, reqOptions);
return reqOptions;
} // Set application specific information.
//
// Generates User-Agent in the following style.
//
// MinIO (OS; ARCH) LIB/VER APP/VER
//
// __Arguments__
// * `appName` _string_ - Application name.
// * `appVersion` _string_ - Application version.
setAppInfo(appName, appVersion) {
if (!(0, _helpers.isString)(appName)) {
throw new TypeError(`Invalid appName: ${appName}`);
}
if (appName.trim() === '') {
throw new errors.InvalidArgumentError('Input appName cannot be empty.');
}
if (!(0, _helpers.isString)(appVersion)) {
throw new TypeError(`Invalid appVersion: ${appVersion}`);
}
if (appVersion.trim() === '') {
throw new errors.InvalidArgumentError('Input appVersion cannot be empty.');
}
this.userAgent = `${this.userAgent} ${appName}/${appVersion}`;
} // Calculate part size given the object size. Part size will be atleast this.partSize
calculatePartSize(size) {
if (!(0, _helpers.isNumber)(size)) {
throw new TypeError('size should be of type "number"');
}
if (size > this.maxObjectSize) {
throw new TypeError(`size should not be more than ${this.maxObjectSize}`);
}
if (this.overRidePartSize) {
return this.partSize;
}
var partSize = this.partSize;
for (;;) {
// while(true) {...} throws linting error.
// If partSize is big enough to accomodate the object size, then use it.
if (partSize * 10000 > size) {
return partSize;
} // Try part sizes as 64MB, 80MB, 96MB etc.
partSize += 16 * 1024 * 1024;
}
} // log the request, response, error
logHTTP(reqOptions, response, err) {
// if no logstreamer available return.
if (!this.logStream) return;
if (!(0, _helpers.isObject)(reqOptions)) {
throw new TypeError('reqOptions should be of type "object"');
}
if (response && !(0, _helpers.isReadableStream)(response)) {
throw new TypeError('response should be of type "Stream"');
}
if (err && !(err instanceof Error)) {
throw new TypeError('err should be of type "Error"');
}
var logHeaders = headers => {
_lodash.default.forEach(headers, (v, k) => {
if (k == 'authorization') {
var redacter = new RegExp('Signature=([0-9a-f]+)');
v = v.replace(redacter, 'Signature=**REDACTED**');
}
this.logStream.write(`${k}: ${v}\n`);
});
this.logStream.write('\n');
};
this.logStream.write(`REQUEST: ${reqOptions.method} ${reqOptions.path}\n`);
logHeaders(reqOptions.headers);
if (response) {
this.logStream.write(`RESPONSE: ${response.statusCode}\n`);
logHeaders(response.headers);
}
if (err) {
this.logStream.write('ERROR BODY:\n');
var errJSON = JSON.stringify(err, null, '\t');
this.logStream.write(`${errJSON}\n`);
}
} // Enable tracing
traceOn(stream) {
if (!stream) stream = process.stdout;
this.logStream = stream;
} // Disable tracing
traceOff() {
this.logStream = null;
} // makeRequest is the primitive used by the apis for making S3 requests.
// payload can be empty string in case of no payload.
// statusCode is the expected statusCode. If response.statusCode does not match
// we parse the XML error and call the callback with the error message.
// A valid region is passed by the calls - listBuckets, makeBucket and
// getBucketRegion.
makeRequest(options, payload, statusCodes, region, returnResponse, cb) {
if (!(0, _helpers.isObject)(options)) {
throw new TypeError('options should be of type "object"');
}
if (!(0, _helpers.isString)(payload) && !(0, _helpers.isObject)(payload)) {
// Buffer is of type 'object'
throw new TypeError('payload should be of type "string" or "Buffer"');
}
statusCodes.forEach(statusCode => {
if (!(0, _helpers.isNumber)(statusCode)) {
throw new TypeError('statusCode should be of type "number"');
}
});
if (!(0, _helpers.isString)(region)) {
throw new TypeError('region should be of type "string"');
}
if (!(0, _helpers.isBoolean)(returnResponse)) {
throw new TypeError('returnResponse should be of type "boolean"');
}
if (!(0, _helpers.isFunction)(cb)) {
throw new TypeError('callback should be of type "function"');
}
if (!options.headers) options.headers = {};
if (options.method === 'POST' || options.method === 'PUT' || options.method === 'DELETE') {
options.headers['content-length'] = payload.length;
}
var sha256sum = '';
if (this.enableSHA256) sha256sum = (0, _helpers.toSha256)(payload);
var stream = (0, _helpers.readableStream)(payload);
this.makeRequestStream(options, stream, sha256sum, statusCodes, region, returnResponse, cb);
} // makeRequestStream will be used directly instead of makeRequest in case the payload
// is available as a stream. for ex. putObject
makeRequestStream(options, stream, sha256sum, statusCodes, region, returnResponse, cb) {
if (!(0, _helpers.isObject)(options)) {
throw new TypeError('options should be of type "object"');
}
if (!(0, _helpers.isReadableStream)(stream)) {
throw new errors.InvalidArgumentError('stream should be a readable Stream');
}
if (!(0, _helpers.isString)(sha256sum)) {
throw new TypeError('sha256sum should be of type "string"');
}
statusCodes.forEach(statusCode => {
if (!(0, _helpers.isNumber)(statusCode)) {
throw new TypeError('statusCode should be of type "number"');
}
});
if (!(0, _helpers.isString)(region)) {
throw new TypeError('region should be of type "string"');
}
if (!(0, _helpers.isBoolean)(returnResponse)) {
throw new TypeError('returnResponse should be of type "boolean"');
}
if (!(0, _helpers.isFunction)(cb)) {
throw new TypeError('callback should be of type "function"');
} // sha256sum will be empty for anonymous or https requests
if (!this.enableSHA256 && sha256sum.length !== 0) {
throw new errors.InvalidArgumentError(`sha256sum expected to be empty for anonymous or https requests`);
} // sha256sum should be valid for non-anonymous http requests.
if (this.enableSHA256 && sha256sum.length !== 64) {
throw new errors.InvalidArgumentError(`Invalid sha256sum : ${sha256sum}`);
}
var _makeRequest = (e, region) => {
if (e) return cb(e);
options.region = region;
var reqOptions = this.getRequestOptions(options);
if (!this.anonymous) {
// For non-anonymous https requests sha256sum is 'UNSIGNED-PAYLOAD' for signature calculation.
if (!this.enableSHA256) sha256sum = 'UNSIGNED-PAYLOAD';
let date = new Date();
reqOptions.headers['x-amz-date'] = (0, _helpers.makeDateLong)(date);
reqOptions.headers['x-amz-content-sha256'] = sha256sum;
if (this.sessionToken) {
reqOptions.headers['x-amz-security-token'] = this.sessionToken;
}
this.checkAndRefreshCreds();
var authorization = (0, _signing.signV4)(reqOptions, this.accessKey, this.secretKey, region, date);
reqOptions.headers.authorization = authorization;
}
var req = this.transport.request(reqOptions, response => {
if (!statusCodes.includes(response.statusCode)) {
// For an incorrect region, S3 server always sends back 400.
// But we will do cache invalidation for all errors so that,
// in future, if AWS S3 decides to send a different status code or
// XML error code we will still work fine.
delete this.regionMap[options.bucketName];
var errorTransformer = transformers.getErrorTransformer(response);
(0, _helpers.pipesetup)(response, errorTransformer).on('error', e => {
this.logHTTP(reqOptions, response, e);
cb(e);
});
return;
}
this.logHTTP(reqOptions, response);
if (returnResponse) return cb(null, response); // We drain the socket so that the connection gets closed. Note that this
// is not expensive as the socket will not have any data.
response.on('data', () => {});
cb(null);
});
let pipe = (0, _helpers.pipesetup)(stream, req);
pipe.on('error', e => {
this.logHTTP(reqOptions, null, e);
cb(e);
});
};
if (region) return _makeRequest(null, region);
this.getBucketRegion(options.bucketName, _makeRequest);
} // gets the region of the bucket
getBucketRegion(bucketName, cb) {
if (!(0, _helpers.isValidBucketName)(bucketName)) {
throw new errors.InvalidBucketNameError(`Invalid bucket name : ${bucketName}`);
}
if (!(0, _helpers.isFunction)(cb)) {
throw new TypeError('cb should be of type "function"');
} // Region is set with constructor, return the region right here.
if (this.region) return cb(null, this.region);
if (this.regionMap[bucketName]) return cb(null, this.regionMap[bucketName]);
var extractRegion = response => {
var transformer = transformers.getBucketRegionTransformer();
var region = _helpers.DEFAULT_REGION;
(0, _helpers.pipesetup)(response, transformer).on('error', cb).on('data', data => {
if (data) region = data;
}).on('end', () => {
this.regionMap[bucketName] = region;
cb(null, region);
});
};
var method = 'GET';
var query = 'location'; // `getBucketLocation` behaves differently in following ways for
// different environments.
//
// - For nodejs env we default to path style requests.
// - For browser env path style requests on buckets yields CORS
// error. To circumvent this problem we make a virtual host
// style request signed with 'us-east-1'. This request fails
// with an error 'AuthorizationHeaderMalformed', additionally
// the error XML also provides Region of the bucket. To validate
// this region is proper we retry the same request with the newly
// obtained region.
var pathStyle = this.pathStyle && typeof window === 'undefined';
this.makeRequest({
method,
bucketName,
query,
pathStyle
}, '', [200], _helpers.DEFAULT_REGION, true, (e, response) => {
if (e) {
if (e.name === 'AuthorizationHeaderMalformed') {
var region = e.Region;
if (!region) return cb(e);
this.makeRequest({
method,
bucketName,
query
}, '', [200], region, true, (e, response) => {
if (e) return cb(e);
extractRegion(response);
});
return;
}
return cb(e);
}
extractRegion(response);
});
} // Creates the bucket `bucketName`.
//
// __Arguments__
// * `bucketName` _string_ - Name of the bucket
// * `region` _string_ - region valid values are _us-west-1_, _us-west-2_, _eu-west-1_, _eu-central-1_, _ap-southeast-1_, _ap-northeast-1_, _ap-southeast-2_, _sa-east-1_.
// * `makeOpts` _object_ - Options to create a bucket. e.g {ObjectLocking:true} (Optional)
// * `callback(err)` _function_ - callback function with `err` as the error argument. `err` is null if the bucket is successfully created.
makeBucket(bucketName, region, makeOpts = {}, cb) {
if (!(0, _helpers.isValidBucketName)(bucketName)) {
throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucketName);
} // Backward Compatibility
if ((0, _helpers.isObject)(region)) {
cb = makeOpts;
makeOpts = region;
region = '';
}
if ((0, _helpers.isFunction)(region)) {
cb = region;
region = '';
makeOpts = {};
}
if ((0, _helpers.isFunction)(makeOpts)) {
cb = makeOpts;
makeOpts = {};
}
if (!(0, _helpers.isString)(region)) {
throw new TypeError('region should be of type "string"');
}
if (!(0, _helpers.isObject)(makeOpts)) {
throw new TypeError('makeOpts should be of type "object"');
}
if (!(0, _helpers.isFunction)(cb)) {
throw new TypeError('callback should be of type "function"');
}
var payload = ''; // Region already set in constructor, validate if
// caller requested bucket location is same.
if (region && this.region) {
if (region !== this.region) {
throw new errors.InvalidArgumentError(`Configured region ${this.region}, requested ${region}`);
}
} // sending makeBucket request with XML containing 'us-east-1' fails. For
// default region server expects the request without body
if (region && region !== _helpers.DEFAULT_REGION) {
var createBucketConfiguration = [];
createBucketConfiguration.push({
_attr: {
xmlns: 'http://s3.amazonaws.com/doc/2006-03-01/'
}
});
createBucketConfiguration.push({
LocationConstraint: region
});
var payloadObject = {
CreateBucketConfiguration: createBucketConfiguration
};
payload = (0, _xml.default)(payloadObject);
}
var method = 'PUT';
var headers = {};
if (makeOpts.ObjectLocking) {
headers["x-amz-bucket-object-lock-enabled"] = true;
}
if (!region) region = _helpers.DEFAULT_REGION;
const processWithRetry = err => {
if (err && (region === "" || region === _helpers.DEFAULT_REGION)) {
if (err.code === "AuthorizationHeaderMalformed" && err.region !== "") {
// Retry with region returned as part of error
this.makeRequest({
method,
bucketName,
headers
}, payload, [200], err.region, false, cb);
} else {
return cb && cb(err);
}
}
return cb && cb(err);
};
this.makeRequest({
method,
bucketName,
headers
}, payload, [200], region, false, processWithRetry);
} // List of buckets created.
//
// __Arguments__
// * `callback(err, buckets)` _function_ - callback function with error as the first argument. `buckets` is an array of bucket information
//
// `buckets` array element:
// * `bucket.name` _string_ : bucket name
// * `bucket.creationDate` _Date_: date when bucket was created
listBuckets(cb) {
if (!(0, _helpers.isFunction)(cb)) {
throw new TypeError('callback should be of type "function"');
}
var method = 'GET';
this.makeRequest({
method
}, '', [200], _helpers.DEFAULT_REGION, true, (e, response) => {
if (e) return cb(e);
var transformer = transformers.getListBucketTransformer();
var buckets;
(0, _helpers.pipesetup)(response, transformer).on('data', result => buckets = result).on('error', e => cb(e)).on('end', () => cb(null, buckets));
});
} // Returns a stream that emits objects that are partially uploaded.
//
// __Arguments__
// * `bucketName` _string_: name of the bucket
// * `prefix` _string_: prefix of the object names that are partially uploaded (optional, default `''`)
// * `recursive` _bool_: directory style listing when false, recursive listing when true (optional, default `false`)
//
// __Return Value__
// * `stream` _Stream_ : emits objects of the format:
// * `object.key` _string_: name of the object
// * `object.uploadId` _string_: upload ID of the object
// * `object.size` _Integer_: size of the partially uploaded object
listIncompleteUploads(bucket, prefix, recursive) {
if (prefix === undefined) prefix = '';
if (recursive === undefined) recursive = false;
if (!(0, _helpers.isValidBucketName)(bucket)) {
throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucket);
}
if (!(0, _helpers.isValidPrefix)(prefix)) {
throw new errors.InvalidPrefixError(`Invalid prefix : ${prefix}`);
}
if (!(0, _helpers.isBoolean)(recursive)) {
throw new TypeError('recursive should be of type "boolean"');
}
var delimiter = recursive ? '' : '/';
var keyMarker = '';
var uploadIdMarker = '';
var uploads = [];
var ended = false;
var readStream = _stream.default.Readable({
objectMode: true
});
readStream._read = () => {
// push one upload info per _read()
if (uploads.length) {
return readStream.push(uploads.shift());
}
if (ended) return readStream.push(null);
this.listIncompleteUploadsQuery(bucket, prefix, keyMarker, uploadIdMarker, delimiter).on('error', e => readStream.emit('error', e)).on('data', result => {
result.prefixes.forEach(prefix => uploads.push(prefix));
_async.default.eachSeries(result.uploads, (upload, cb) => {
// for each incomplete upload add the sizes of its uploaded parts
this.listParts(bucket, upload.key, upload.uploadId, (err, parts) => {
if (err) return cb(err);
upload.size = parts.reduce((acc, item) => acc + item.size, 0);
uploads.push(upload);
cb();
});
}, err => {
if (err) {
readStream.emit('error', err);
return;
}
if (result.isTruncated) {
keyMarker = result.nextKeyMarker;
uploadIdMarker = result.nextUploadIdMarker;
} else {
ended = true;
}
readStream._read();
});
});
};
return readStream;
} // To check if a bucket already exists.
//
// __Arguments__
// * `bucketName` _string_ : name of the bucket
// * `callback(err)` _function_ : `err` is `null` if the bucket exists
bucketExists(bucketName, cb) {
if (!(0, _helpers.isValidBucketName)(bucketName)) {
throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucketName);
}
if (!(0, _helpers.isFunction)(cb)) {
throw new TypeError('callback should be of type "function"');
}
var method = 'HEAD';
this.makeRequest({
method,
bucketName
}, '', [200], '', false, err => {
if (err) {
if (err.code == 'NoSuchBucket' || err.code == 'NotFound') return cb(null, false);
return cb(err);
}
cb(null, true);
});
} // Remove a bucket.
//
// __Arguments__
// * `bucketName` _string_ : name of the bucket
// * `callback(err)` _function_ : `err` is `null` if the bucket is removed successfully.
removeBucket(bucketName, cb) {
if (!(0, _helpers.isValidBucketName)(bucketName)) {
throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucketName);
}
if (!(0, _helpers.isFunction)(cb)) {
throw new TypeError('callback should be of type "function"');
}
var method = 'DELETE';
this.makeRequest({
method,
bucketName
}, '', [204], '', false, e => {
// If the bucket was successfully removed, remove the region map entry.
if (!e) delete this.regionMap[bucketName];
cb(e);
});
} // Remove the partially uploaded object.
//
// __Arguments__
// * `bucketName` _string_: name of the bucket
// * `objectName` _string_: name of the object
// * `callback(err)` _function_: callback function is called with non `null` value in case of error
removeIncompleteUpload(bucketName, objectName, cb) {
if (!(0, _helpers.isValidBucketName)(bucketName)) {
throw new errors.isValidBucketNameError('Invalid bucket name: ' + bucketName);
}
if (!(0, _helpers.isValidObjectName)(objectName)) {
throw new errors.InvalidObjectNameError(`Invalid object name: ${objectName}`);
}
if (!(0, _helpers.isFunction)(cb)) {
throw new TypeError('callback should be of type "function"');
}
var removeUploadId;
_async.default.during(cb => {
this.findUploadId(bucketName, objectName, (e, uploadId) => {
if (e) return cb(e);
removeUploadId = uploadId;
cb(null, uploadId);
});
}, cb => {
var method = 'DELETE';
var query = `uploadId=${removeUploadId}`;
this.makeRequest({
method,
bucketName,
objectName,
query
}, '', [204], '', false, e => cb(e));
}, cb);
} // Callback is called with `error` in case of error or `null` in case of success
//
// __Arguments__
// * `bucketName` _string_: name of the bucket
// * `objectName` _string_: name of the object
// * `filePath` _string_: path to which the object data will be written to
// * `getOpts` _object_: Version of the object in the form `{versionId:'my-uuid'}`. Default is `{}`. (optional)
// * `callback(err)` _function_: callback is called with `err` in case of error.
fGetObject(bucketName, objectName, filePath, getOpts = {}, cb) {
// Input validation.
if (!(0, _helpers.isValidBucketName)(bucketName)) {
throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucketName);
}
if (!(0, _helpers.isValidObjectName)(objectName)) {
throw new errors.InvalidObjectNameError(`Invalid object name: ${objectName}`);
}
if (!(0, _helpers.isString)(filePath)) {
throw new TypeError('filePath should be of type "string"');
} // Backward Compatibility
if ((0, _helpers.isFunction)(getOpts)) {
cb = getOpts;
getOpts = {};
}
if (!(0, _helpers.isFunction)(cb)) {
throw new TypeError('callback should be of type "function"');
} // Internal data.
var partFile;
var partFileStream;
var objStat; // Rename wrapper.
var rename = err => {
if (err) return cb(err);
_fs.default.rename(partFile, filePath, cb);
};
_async.default.waterfall([cb => this.statObject(bucketName, objectName, getOpts, cb), (result, cb) => {
objStat = result; // Create any missing top level directories.
(0, _mkdirp.default)(_path.default.dirname(filePath), cb);
}, (ignore, cb) => {
partFile = `${filePath}.${objStat.etag}.part.minio`;
_fs.default.stat(partFile, (e, stats) => {
var offset = 0;
if (e) {
partFileStream = _fs.default.createWriteStream(partFile, {
flags: 'w'
});
} else {
if (objStat.size === stats.size) return rename();
offset = stats.size;
partFileStream = _fs.default.createWriteStream(partFile, {
flags: 'a'
});
}
this.getPartialObject(bucketName, objectName, offset, 0, getOpts, cb);
});
}, (downloadStream, cb) => {
(0, _helpers.pipesetup)(downloadStream, partFileStream).on('error', e => cb(e)).on('finish', cb);
}, cb => _fs.default.stat(partFile, cb), (stats, cb) => {
if (stats.size === objStat.size) return cb();
cb(new Error('Size mismatch between downloaded file and the object'));
}], rename);
} // Callback is called with readable stream of the object content.
//
// __Arguments__
// * `bucketName` _string_: name of the bucket
// * `objectName` _string_: name of the object
// * `getOpts` _object_: Version of the object in the form `{versionId:'my-uuid'}`. Default is `{}`. (optional)
// * `callback(err, stream)` _function_: callback is called with `err` in case of error. `stream` is the object content stream
getObject(bucketName, objectName, getOpts = {}, cb) {
if (!(0, _helpers.isValidBucketName)(bucketName)) {
throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucketName);
}
if (!(0, _helpers.isValidObjectName)(objectName)) {
throw new errors.InvalidObjectNameError(`Invalid object name: ${objectName}`);
} // Backward Compatibility
if ((0, _helpers.isFunction)(getOpts)) {
cb = getOpts;
getOpts = {};
}
if (!(0, _helpers.isFunction)(cb)) {
throw new TypeError('callback should be of type "function"');
}
this.getPartialObject(bucketName, objectName, 0, 0, getOpts, cb);
} // Callback is called with readable stream of the partial object content.
//
// __Arguments__
// * `bucketName` _string_: name of the bucket
// * `objectName` _string_: name of the object
// * `offset` _number_: offset of the object from where the stream will start
// * `length` _number_: length of the object that will be read in the stream (optional, if not specified we read the rest of the file from the offset)
// * `getOpts` _object_: Version of the object in the form `{versionId:'my-uuid'}`. Default is `{}`. (optional)
// * `callback(err, stream)` _function_: callback is called with `err` in case of error. `stream` is the object content stream
getPartialObject(bucketName, objectName, offset, length, getOpts = {}, cb) {
if ((0, _helpers.isFunction)(length)) {
cb = length;
length = 0;
}
if (!(0, _helpers.isValidBucketName)(bucketName)) {
throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucketName);
}
if (!(0, _helpers.isValidObjectName)(objectName)) {
throw new errors.InvalidObjectNameError(`Invalid object name: ${objectName}`);
}
if (!(0, _helpers.isNumber)(offset)) {
throw new TypeError('offset should be of type "number"');
}
if (!(0, _helpers.isNumber)(length)) {
throw new TypeError('length should be of type "number"');
} // Backward Compatibility
if ((0, _helpers.isFunction)(getOpts)) {
cb = getOpts;
getOpts = {};
}
if (!(0, _helpers.isFunction)(cb)) {
throw new TypeError('callback should be of type "function"');
}
var range = '';
if (offset || length) {
if (offset) {
range = `bytes=${+offset}-`;
} else {
range = 'bytes=0-';
offset = 0;
}
if (length) {
range += `${+length + offset - 1}`;
}
}
var headers = {};
if (range !== '') {
headers.range = range;
}
var expectedStatusCodes = [200];
if (range) {
expectedStatusCodes.push(206);
}
var method = 'GET';
var query = _queryString.default.stringify(getOpts);
this.makeRequest({
method,
bucketName,
objectName,
headers,
query
}, '', expectedStatusCodes, '', true, cb);
} // Uploads the object using contents from a file
//
// __Arguments__
// * `bucketName` _string_: name of the bucket
// * `objectName` _string_: name of the object
// * `filePath` _string_: file path of the file to be uploaded
// * `metaData` _Javascript Object_: metaData assosciated with the object
// * `callback(err, objInfo)` _function_: non null `err` indicates error, `objInfo` _object_ which contains versionId and etag.
fPutObject(bucketName, objectName, filePath, metaData, callback) {
if (!(0, _helpers.isValidBucketName)(bucketName)) {
throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucketName);
}
if (!(0, _helpers.isValidObjectName)(objectName)) {
throw new errors.InvalidObjectNameError(`Invalid object name: ${objectName}`);
}
if (!(0, _helpers.isString)(filePath)) {
throw new TypeError('filePath should be of type "string"');
}
if ((0, _helpers.isFunction)(metaData)) {
callback = metaData;
metaData = {}; // Set metaData empty if no metaData provided.
}
if (!(0, _helpers.isObject)(metaData)) {
throw new TypeError('metaData should be of type "object"');
} // Inserts correct `content-type` attribute based on metaData and filePath
metaData = (0, _helpers.insertContentType)(metaData, filePath); // Updates metaData to have the correct prefix if needed
metaData = (0, _helpers.prependXAMZMeta)(metaData);
var size;
var partSize;
_async.default.waterfall([cb => _fs.default.stat(filePath, cb), (stats, cb) => {
size = stats.size;
var cbTriggered = false;
var origCb = cb;
cb = function () {
if (cbTriggered) {
return;
}
cbTriggered = true;
return origCb.apply(this, arguments);
};
if (size > this.maxObjectSize) {
return cb(new Error(`${filePath} size : ${stats.size}, max allowed size : 5TB`));
}
if (size <= this.partSize) {
// simple PUT request, no multipart
var multipart = false;
var uploader = this.getUploader(bucketName, objectName, metaData, multipart);
var hash = transformers.getHashSummer(this.enableSHA256);
var start = 0;
var end = size - 1;
var autoClose = true;
if (size === 0) end = 0;
var options = {
start,
end,
autoClose
};
(0, _helpers.pipesetup)(_fs.default.createReadStream(filePath, options), hash).on('data', data => {
var md5sum = data.md5sum;
var sha256sum = data.sha256sum;
var stream = _fs.default.createReadStream(filePath, options);
uploader(stream, size, sha256sum, md5sum, (err, objInfo) => {
callback(err, objInfo);
cb(true);
});
}).on('error', e => cb(e));
return;
}
this.findUploadId(bucketName, objectName, cb);
}, (uploadId, cb) => {
// if there was a previous incomplete upload, fetch all its uploaded parts info
if (uploadId) return this.listParts(bucketName, objectName, uploadId, (e, etags) => cb(e, uploadId, etags)); // there was no previous upload, initiate a new one
this.initiateNewMultipartUpload(bucketName, objectName, metaData, (e, uploadId) => cb(e, uploadId, []));
}, (uploadId, etags, cb) => {
partSize = this.calculatePartSize(size);
var multipart = true;
var uploader = this.getUploader(bucketName, objectName, metaData, multipart); // convert array to object to make things easy
var parts = etags.reduce(function (acc, item) {
if (!acc[item.part]) {
acc[item.part] = item;
}
return acc;
}, {});
var partsDone = [];
var partNumber = 1;
var uploadedSize = 0;
_async.default.whilst(cb => {
cb(null, uploadedSize < size);
}, cb => {
var cbTriggered = false;
var origCb = cb;
cb = function () {
if (cbTriggered) {
return;
}
cbTriggered = true;
return origCb.apply(this, arguments);
};
var part = parts[partNumber];
var hash = transformers.getHashSummer(this.enableSHA256);
var length = partSize;
if (length > size - uploadedSize) {
length = size - uploadedSize;
}
var start = uploadedSize;
var end = uploadedSize + length - 1;
var autoClose = true;
var options = {
autoClose,
start,
end
}; // verify md5sum of each part
(0, _helpers.pipesetup)(_fs.default.createReadStream(filePath, options), hash).on('data', data => {
var md5sumHex = Buffer.from(data.md5sum, 'base64').toString('hex');
if (part && md5sumHex === part.etag) {
// md5 matches, chunk already uploaded
partsDone.push({
part: partNumber,
etag: part.etag
});
partNumber++;
uploadedSize += length;
return cb();
} // part is not uploaded yet, or md5 mismatch
var stream = _fs.default.createReadStream(filePath, options);
uploader(uploadId, partNumber, stream, length, data.sha256sum, data.md5sum, (e, objInfo) => {
if (e) return cb(e);
partsDone.push({
part: partNumber,
etag: objInfo.etag
});
partNumber++;
uploadedSize += length;
return cb();
});
}).on('error', e => cb(e));
}, e => {
if (e) return cb(e);
cb(null, partsDone, uploadId);
});
}, // all parts uploaded, complete the multipart upload
(etags, uploadId, cb) => this.completeMultipartUpload(bucketName, objectName, uploadId, etags, cb)], (err, ...rest) => {
if (err === true) return;
callback(err, ...rest);
});
} // Uploads the object.
//
// Uploading a stream
// __Arguments__
// * `bucketName` _string_: name of the bucket
// * `objectName` _string_: name of the object
// * `stream` _Stream_: Readable stream
// * `size` _number_: size of the object (optional)
// * `callback(err, etag)` _function_: non null `err` indicates error, `etag` _string_ is the etag of the object uploaded.
//
// Uploading "Buffer" or "string"
// __Arguments__
// * `bucketName` _string_: name of the bucket
// * `objectName` _string_: name of the object
// * `string or Buffer` _string_ or _Buffer_: string or buffer
// * `callback(err, objInfo)` _function_: `err` is `null` in case of success and `info` will have the following object details:
// * `etag` _string_: etag of the object
// * `versionId` _string_: versionId of the object
putObject(bucketName, objectName, stream, size, metaData, callback) {
if (!(0, _helpers.isValidBucketName)(bucketName)) {
throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucketName);
}
if (!(0, _helpers.isValidObjectName)(objectName)) {
throw new errors.InvalidObjectNameError(`Invalid object name: ${objectName}`);
} // We'll need to shift arguments to the left because of size and metaData.
if ((0, _helpers.isFunction)(size)) {
callback = size;
metaData = {};
} else if ((0, _helpers.isFunction)(metaData)) {
callback = metaData;
metaData = {};
} // We'll need to shift arguments to the left because of metaData
// and size being optional.
if ((0, _helpers.isObject)(size)) {
metaData = size;
} // Ensures Metadata has appropriate prefix for A3 API
metaData = (0, _helpers.prependXAMZMeta)(metaData);
if (typeof stream === 'string' || stream instanceof Buffer) {
// Adapts the non-stream interface into a stream.
size = stream.length;
stream = (0, _helpers.readableStream)(stream);
} else if (!(0, _helpers.isReadableStream)(stream)) {
throw new TypeError('third argument should be of type "stream.Readable" or "Buffer" or "string"');
}
if (!(0, _helpers.isFunction)(callback)) {
throw new TypeError('callback should be of type "function"');
}
if ((0, _helpers.isNumber)(size) && size < 0) {
throw new errors.InvalidArgumentError(`size cannot be negative, given size: ${size}`);
} // Get the part size and forward that to the BlockStream. Default to the
// largest block size possible if necessary.
if (!(0, _helpers.isNumber)(size)) size = this.maxObjectSize;
size = this.calculatePartSize(size); // s3 requires that all non-end chunks be at least `this.partSize`,
// so we chunk the stream until we hit either that size or the end before
// we flush it to s3.
let chunker = new _blockStream.default({
size,
zeroPadding: false
}); // This is a Writable stream that can be written to in order to upload
// to the specified bucket and object automatically.
let uploader = new _objectUploader.default(this, bucketName, objectName, size, metaData, callback); // stream => chunker => uploader
stream.pipe(chunker).pipe(uploader);
} // Copy the object.
//
// __Arguments__
// * `bucketName` _string_: name of the bucket
// * `objectName` _string_: name of the object
// * `srcObject` _string_: path of the source object to be copied
// * `conditions` _CopyConditions_: copy conditions that needs to be satisfied (optional, default `null`)
// * `callback(err, {etag, lastModified})` _function_: non null `err` indicates error, `etag` _string_ and `listModifed` _Date_ are respectively the etag and the last modified date of the newly copied object
copyObjectV1(arg1, arg2, arg3, arg4, arg5) {
var bucketName = arg1;
var objectName = arg2;
var srcObject = arg3;
var conditions, cb;
if (typeof arg4 == 'function' && arg5 === undefined) {
conditions = null;
cb = arg4;
} else {
conditions = arg4;
cb = arg5;
}
if (!(0, _helpers.isValidBucketName)(bucketName)) {
throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucketName);
}
if (!(0, _helpers.isValidObjectName)(objectName)) {
throw new errors.InvalidObjectNameError(`Invalid object name: ${objectName}`);
}
if (!(0, _helpers.isString)(srcObject)) {
throw new TypeError('srcObject should be of type "string"');
}
if (srcObject === "") {
throw new errors.InvalidPrefixError(`Empty source prefix`);
}
if (conditions !== null && !(conditions instanceof CopyConditions)) {
throw new TypeError('conditions should be of type "CopyConditions"');
}
var headers = {};
headers['x-amz-copy-source'] = (0, _helpers.uriResourceEscape)(srcObject);
if (conditions !== null) {
if (conditions.modified !== "") {
headers['x-amz-copy-source-if-modified-since'] = conditions.modified;
}
if (conditions.unmodified !== "") {
headers['x-amz-copy-source-if-unmodified-since'] = conditions.unmodified;
}
if (conditions.matchETag !== "") {
headers['x-amz-copy-source-if-match'] = conditions.matchETag;
}
if (conditions.matchEtagExcept !== "") {
headers['x-amz-copy-source-if-none-match'] = conditions.matchETagExcept;
}
}
var method = 'PUT';
this.makeRequest({
method,
bucketName,
objectName,
headers
}, '', [200], '', true, (e, response) => {
if (e) return cb(e);
var transformer = transformers.getCopyObjectTransformer();
(0, _helpers.pipesetup)(response, transformer).on('error', e => cb(e)).on('data', data => cb(null, data));
});
}
/**
* Internal Method to perform copy of an object.
*