diff --git a/app/server/lib/AppSettings.ts b/app/server/lib/AppSettings.ts index aff55934..dbb7ef17 100644 --- a/app/server/lib/AppSettings.ts +++ b/app/server/lib/AppSettings.ts @@ -1,4 +1,4 @@ -import { isAffirmative } from 'app/common/gutil'; +import { isAffirmative, isNumber } from 'app/common/gutil'; /** * A bundle of settings for the application. May contain @@ -23,6 +23,22 @@ export class AppSettings { return (this._value !== undefined) ? isAffirmative(this._value) : undefined; } + /** + * Access the setting as an integer using parseInt. Undefined if not set. + * Throws an error if not numberlike. + */ + public getAsInt(): number|undefined { + if (this._value === undefined) { return undefined; } + const datum = this._value?.valueOf(); + if (typeof datum === 'number') { + return datum; + } + if (isNumber(String(datum))) { + return parseInt(String(datum), 10); + } + throw new Error(`${datum} does not look like a number`); + } + /** * Try to read the setting from the environment. Even if * we fail, we record information about how we tried to diff --git a/app/server/lib/ICreate.ts b/app/server/lib/ICreate.ts index f2d027e6..1ca70ca7 100644 --- a/app/server/lib/ICreate.ts +++ b/app/server/lib/ICreate.ts @@ -57,6 +57,9 @@ export function makeSimpleCreator(opts: { storage?: ICreateStorageOptions[], billing?: ICreateBillingOptions, notifier?: ICreateNotifierOptions, + sandboxFlavor?: string, + shell?: IShell, + getExtraHeadHtml?: () => string, }): ICreate { const {sessionSecret, storage, notifier, billing} = opts; return { @@ -84,7 +87,7 @@ export function makeSimpleCreator(opts: { return undefined; }, NSandbox(options) { - return createSandbox('unsandboxed', options); + return createSandbox(opts.sandboxFlavor || 'unsandboxed', options); }, sessionSecret() { const secret = process.env.GRIST_SESSION_SECRET || sessionSecret; @@ -98,7 +101,15 @@ export function makeSimpleCreator(opts: { if (s.check()) { break; } } }, + ...(opts.shell && { + Shell() { + return opts.shell as IShell; + }, + }), getExtraHeadHtml() { + if (opts.getExtraHeadHtml) { + return opts.getExtraHeadHtml(); + } const elements: string[] = []; if (process.env.APP_STATIC_INCLUDE_CUSTOM_CSS === 'true') { elements.push(''); diff --git a/app/server/lib/MinIOExternalStorage.ts b/app/server/lib/MinIOExternalStorage.ts new file mode 100644 index 00000000..d2dad332 --- /dev/null +++ b/app/server/lib/MinIOExternalStorage.ts @@ -0,0 +1,164 @@ +import {ApiError} from 'app/common/ApiError'; +import {ObjMetadata, ObjSnapshotWithMetadata, toExternalMetadata, toGristMetadata} from 'app/common/DocSnapshot'; +import {ExternalStorage} from 'app/server/lib/ExternalStorage'; +import {IncomingMessage} from 'http'; +import * as fse from 'fs-extra'; +import * as minio from 'minio'; + +// The minio typings appear to be quite stale. Extend them here to avoid +// lots of casts to any. +type MinIOClient = minio.Client & { + statObject(bucket: string, key: string, options: {versionId?: string}): Promise; + getObject(bucket: string, key: string, options: {versionId?: string}): Promise; + listObjects(bucket: string, key: string, recursive: boolean, + options: {IncludeVersion?: boolean}): minio.BucketStream; + removeObjects(bucket: string, versions: Array<{name: string, versionId: string}>): Promise; +}; + +type MinIOBucketItemStat = minio.BucketItemStat & { + versionId?: string; + metaData?: Record; +}; + +/** + * An external store implemented using the MinIO client, which + * will work with MinIO and other S3-compatible storage. + */ +export class MinIOExternalStorage implements ExternalStorage { + private _s3: MinIOClient; + + // Specify bucket to use, and optionally the max number of keys to request + // in any call to listObjectVersions (used for testing) + constructor(public bucket: string, public options: { + endPoint: string, + port?: number, + useSSL?: boolean, + accessKey: string, + secretKey: string, + }, private _batchSize?: number) { + this._s3 = new minio.Client(options) as MinIOClient; + } + + public async exists(key: string, snapshotId?: string) { + return Boolean(await this.head(key, snapshotId)); + } + + public async head(key: string, snapshotId?: string): Promise { + try { + const head = await this._s3.statObject( + this.bucket, key, + snapshotId ? {versionId: snapshotId} : {}, + ); + if (!head.lastModified || !head.versionId) { + // AWS documentation says these fields will be present. + throw new Error('MinIOExternalStorage.head did not get expected fields'); + } + return { + lastModified: head.lastModified.toISOString(), + snapshotId: head.versionId, + ...head.metaData && { metadata: toGristMetadata(head.metaData) }, + }; + } catch (err) { + if (!this.isFatalError(err)) { return null; } + throw err; + } + } + + public async upload(key: string, fname: string, metadata?: ObjMetadata) { + const stream = fse.createReadStream(fname); + const result = await this._s3.putObject( + this.bucket, key, stream, + metadata ? {Metadata: toExternalMetadata(metadata)} : undefined + ); + // Empirically VersionId is available in result for buckets with versioning enabled. + return result.versionId || null; + } + + public async download(key: string, fname: string, snapshotId?: string) { + const stream = fse.createWriteStream(fname); + const request = await this._s3.getObject( + this.bucket, key, + snapshotId ? {versionId: snapshotId} : {} + ); + const statusCode = request.statusCode || 500; + if (statusCode >= 300) { + throw new ApiError('download error', statusCode); + } + // See https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/requests-using-stream-objects.html + // for an example of streaming data. + const headers = request.headers; + // For a versioned bucket, the header 'x-amz-version-id' contains a version id. + const downloadedSnapshotId = String(headers['x-amz-version-id'] || ''); + return new Promise((resolve, reject) => { + request + .on('error', reject) // handle errors on the read stream + .pipe(stream) + .on('error', reject) // handle errors on the write stream + .on('finish', () => resolve(downloadedSnapshotId)); + }); + } + + public async remove(key: string, snapshotIds?: string[]) { + if (snapshotIds) { + await this._deleteBatch(key, snapshotIds); + } else { + await this._deleteAllVersions(key); + } + } + + public async versions(key: string, options?: { includeDeleteMarkers?: boolean }) { + const results: minio.BucketItem[] = []; + await new Promise((resolve, reject) => { + const stream = this._s3.listObjects(this.bucket, key, false, {IncludeVersion: true}); + stream + .on('error', reject) + .on('end', () => { + resolve(results); + }) + .on('data', data => { + results.push(data); + }); + }); + return results + .filter(v => v.name === key && + v.lastModified && (v as any).versionId && + (options?.includeDeleteMarkers || !(v as any).isDeleteMarker)) + .map(v => ({ + lastModified: v.lastModified.toISOString(), + snapshotId: (v as any).versionId!, + })); + } + + public url(key: string) { + return `minio://${this.bucket}/${key}`; + } + + public isFatalError(err: any) { + return err.code !== 'NotFound' && err.code !== 'NoSuchKey'; + } + + public async close() { + // nothing to do + } + + // Delete all versions of an object. + public async _deleteAllVersions(key: string) { + const vs = await this.versions(key, {includeDeleteMarkers: true}); + await this._deleteBatch(key, vs.map(v => v.snapshotId)); + } + + // Delete a batch of versions for an object. + private async _deleteBatch(key: string, versions: Array) { + // Max number of keys per request for AWS S3 is 1000, see: + // https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html + // Stick to this maximum in case we are using this client to talk to AWS. + const N = this._batchSize || 1000; + for (let i = 0; i < versions.length; i += N) { + const iVersions = versions.slice(i, i + N).filter(v => v) as string[]; + if (iVersions.length === 0) { continue; } + await this._s3.removeObjects(this.bucket, iVersions.map(versionId => { + return { name: key, versionId }; + })); + } + } +} diff --git a/app/server/lib/configureMinIOExternalStorage.ts b/app/server/lib/configureMinIOExternalStorage.ts new file mode 100644 index 00000000..e76f1a18 --- /dev/null +++ b/app/server/lib/configureMinIOExternalStorage.ts @@ -0,0 +1,54 @@ +import {wrapWithKeyMappedStorage} from 'app/server/lib/ExternalStorage'; +import {appSettings} from 'app/server/lib/AppSettings'; +import {MinIOExternalStorage} from 'app/server/lib/MinIOExternalStorage'; + +export function configureMinIOExternalStorage(purpose: 'doc'|'meta', extraPrefix: string) { + const options = checkMinIOExternalStorage(); + if (!options?.bucket) { return undefined; } + return wrapWithKeyMappedStorage(new MinIOExternalStorage(options.bucket, options), { + basePrefix: options.prefix, + extraPrefix, + purpose, + }); +} + +export function checkMinIOExternalStorage() { + const settings = appSettings.section('externalStorage').section('minio'); + const bucket = settings.flag('bucket').readString({ + envVar: ['GRIST_DOCS_MINIO_BUCKET', 'TEST_MINIO_BUCKET'], + preferredEnvVar: 'GRIST_DOCS_MINIO_BUCKET', + }); + if (!bucket) { return undefined; } + const prefix = settings.flag('prefix').requireString({ + envVar: ['GRIST_DOCS_MINIO_PREFIX'], + preferredEnvVar: 'GRIST_DOCS_MINIO_PREFIX', + defaultValue: 'docs/', + }); + const endPoint = settings.flag('endpoint').requireString({ + envVar: ['GRIST_DOCS_MINIO_ENDPOINT'], + preferredEnvVar: 'GRIST_DOCS_MINIO_ENDPOINT', + }); + const port = settings.flag('port').read({ + envVar: ['GRIST_DOCS_MINIO_PORT'], + preferredEnvVar: 'GRIST_DOCS_MINIO_PORT', + }).getAsInt(); + const useSSL = settings.flag('useSsl').read({ + envVar: ['GRIST_DOCS_MINIO_USE_SSL'], + }).getAsBool(); + const accessKey = settings.flag('accessKey').requireString({ + envVar: ['GRIST_DOCS_MINIO_ACCESS_KEY'], + }); + const secretKey = settings.flag('secretKey').requireString({ + envVar: ['GRIST_DOCS_MINIO_SECRET_KEY'], + }); + settings.flag('url').set(`minio://${bucket}/${prefix}`); + settings.flag('active').set(true); + return { + endPoint, + port, + bucket, prefix, + useSSL, + accessKey, + secretKey, + }; +} diff --git a/package.json b/package.json index cb72581e..1da970db 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "@types/lodash": "4.14.117", "@types/lru-cache": "5.1.1", "@types/mime-types": "2.1.0", + "@types/minio": "7.0.15", "@types/mocha": "5.2.5", "@types/moment-timezone": "0.5.9", "@types/node": "^14", @@ -142,6 +143,7 @@ "knockout": "3.5.0", "locale-currency": "0.0.2", "lodash": "4.17.21", + "minio": "7.0.32", "moment": "2.29.4", "moment-timezone": "0.5.35", "morgan": "1.9.1", diff --git a/stubs/app/server/lib/create.ts b/stubs/app/server/lib/create.ts index d0c32887..adea51a4 100644 --- a/stubs/app/server/lib/create.ts +++ b/stubs/app/server/lib/create.ts @@ -1,5 +1,15 @@ +import { checkMinIOExternalStorage, + configureMinIOExternalStorage } from 'app/server/lib/configureMinIOExternalStorage'; import { makeSimpleCreator } from 'app/server/lib/ICreate'; export const create = makeSimpleCreator({ - sessionSecret: 'Phoo2ag1jaiz6Moo2Iese2xoaphahbai3oNg7diemohlah0ohtae9iengafieS2Hae7quungoCi9iaPh' + // This can and should be overridden by GRIST_SESSION_SECRET + // (or generated randomly per install, like grist-omnibus does). + sessionSecret: 'Phoo2ag1jaiz6Moo2Iese2xoaphahbai3oNg7diemohlah0ohtae9iengafieS2Hae7quungoCi9iaPh', + storage: [ + { + check: () => checkMinIOExternalStorage() !== undefined, + create: configureMinIOExternalStorage, + }, + ], }); diff --git a/yarn.lock b/yarn.lock index e07792cf..00bc9a9f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -469,6 +469,13 @@ resolved "https://registry.npmjs.org/@types/mime/-/mime-2.0.2.tgz" integrity sha512-4kPlzbljFcsttWEq6aBW0OZe6BDajAmyvr2xknBG92tejQnvdGtT9+kXSZ580DqpxY9qG2xeQVF9Dq0ymUTo5Q== +"@types/minio@7.0.15": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/minio/-/minio-7.0.15.tgz#6fbf2e17aeae172cbf181ea52b1faa05a601ce42" + integrity sha512-1VR05lWJDuxkn/C7d87MPAJs0p+onKnkUN3nyQ0xrrtaziZQmONy/nxXRaAVWheEyIb6sl0TTi77I/GAQDN5Lw== + dependencies: + "@types/node" "*" + "@types/mocha@5.2.5": version "5.2.5" resolved "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.5.tgz" @@ -770,6 +777,11 @@ resolved "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +"@zxing/text-encoding@0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@zxing/text-encoding/-/text-encoding-0.9.0.tgz#fb50ffabc6c7c66a0c96b4c03e3d9be74864b70b" + integrity sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA== + JSONStream@^1.0.3: version "1.3.5" resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" @@ -1123,6 +1135,11 @@ async@^2.1.5, async@^2.5.0: dependencies: lodash "^4.17.14" +async@^3.1.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" + integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== + async@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/async/-/async-3.2.0.tgz" @@ -1138,6 +1155,11 @@ asynckit@^0.4.0: resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= +available-typed-arrays@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" + integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz" @@ -1223,6 +1245,13 @@ bl@^4.0.3: inherits "^2.0.4" readable-stream "^3.4.0" +block-stream2@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/block-stream2/-/block-stream2-2.1.0.tgz#ac0c5ef4298b3857796e05be8ebed72196fa054b" + integrity sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg== + dependencies: + readable-stream "^3.4.0" + bluebird@3.7.2, bluebird@^3.3.3, bluebird@^3.5.0: version "3.7.2" resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz" @@ -1325,6 +1354,11 @@ brorand@^1.0.1, brorand@^1.1.0: resolved "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz" integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== +browser-or-node@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/browser-or-node/-/browser-or-node-1.3.0.tgz#f2a4e8568f60263050a6714b2cc236bb976647a7" + integrity sha512-0F2z/VSnLbmEeBcUrSuDH5l0HxTXdQQzLjkmBR4cYfvg1zJrKSlmIZFqyFR8oX0NrwPhy3c3HQ6i3OxMbew4Tg== + browser-pack@^6.0.1: version "6.1.0" resolved "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz" @@ -2094,7 +2128,7 @@ cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -crypto-browserify@^3.0.0: +crypto-browserify@^3.0.0, crypto-browserify@^3.12.0: version "3.12.0" resolved "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz" integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== @@ -2241,6 +2275,11 @@ decimal.js@^10.2.1: resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz" integrity sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg== +decode-uri-component@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== + decompress-response@^3.3.0: version "3.3.0" resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz" @@ -2962,6 +3001,13 @@ fast-text-encoding@^1.0.0: resolved "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz" integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== +fast-xml-parser@^3.17.5: + version "3.21.1" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-3.21.1.tgz#152a1d51d445380f7046b304672dd55d15c9e736" + integrity sha512-FTFVjYoBOZTJekiUsawGsSYV9QL0A+zDYCRj7y34IO6Jg+2IMYEtQa+bbictpdpV8dHxXywqU7C0gRDEOFtBFg== + dependencies: + strnum "^1.0.4" + fastest-levenshtein@^1.0.12: version "1.0.12" resolved "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz" @@ -2990,6 +3036,11 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +filter-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" + integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== + finalhandler@1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz" @@ -3035,6 +3086,13 @@ follow-redirects@^1.14.0: resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz" integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== +for-each@^0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" + integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== + dependencies: + is-callable "^1.1.3" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" @@ -3207,6 +3265,15 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" +get-intrinsic@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" + integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + get-stream@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz" @@ -3387,6 +3454,13 @@ googleapis-common@^5.0.1: url-template "^2.0.8" uuid "^8.0.0" +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + got@5.6.0: version "5.6.0" resolved "https://registry.npmjs.org/got/-/got-5.6.0.tgz" @@ -3513,6 +3587,18 @@ has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2: resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz" integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" @@ -3816,6 +3902,19 @@ ipaddr.js@1.9.1: resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +ipaddr.js@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" + integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== + +is-arguments@^1.0.4: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" @@ -3850,6 +3949,11 @@ is-buffer@~2.0.3: resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== +is-callable@^1.1.3: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + is-callable@^1.1.4, is-callable@^1.2.3: version "1.2.3" resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz" @@ -3903,6 +4007,13 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-generator-function@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" + integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== + dependencies: + has-tostringtag "^1.0.0" + is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz" @@ -4024,6 +4135,17 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.1" +is-typed-array@^1.1.10, is-typed-array@^1.1.3: + version "1.1.10" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f" + integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" @@ -4191,6 +4313,11 @@ json-stable-stringify@~0.0.0: dependencies: jsonify "~0.0.0" +json-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-stream/-/json-stream-1.0.0.tgz#1a3854e28d2bbeeab31cc7ddf683d2ddc5652708" + integrity sha512-H/ZGY0nIAg3QcOwE1QN/rK/Fa7gJn7Ii5obwp6zyPO4xiPNwpIMjqy2gwjBEGqzkF/vSWEIBQCBuN19hYiL6Qg== + json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" @@ -4506,7 +4633,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.21, lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.7.0: +lodash@4.17.21, lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -4625,9 +4752,9 @@ mime-types@^2.1.12, mime-types@~2.1.19: dependencies: mime-db "1.46.0" -mime-types@^2.1.27: +mime-types@^2.1.14, mime-types@^2.1.27: version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: mime-db "1.52.0" @@ -4701,6 +4828,29 @@ minimist@~1.1.1: resolved "https://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz" integrity sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag= +minio@7.0.32: + version "7.0.32" + resolved "https://registry.yarnpkg.com/minio/-/minio-7.0.32.tgz#fed6a4679c5954d3efc6df47f73f7e7124446e2c" + integrity sha512-txa7Vr0N24MKzeAybP/wY1jxbLnfGHXwZYyfFXuMW55HX2+HOcKEIgH4hU6Qj/kiMgyXs/ozHjAuLIDrR8nwLg== + dependencies: + async "^3.1.0" + block-stream2 "^2.0.0" + browser-or-node "^1.3.0" + buffer-crc32 "^0.2.13" + crypto-browserify "^3.12.0" + es6-error "^4.1.1" + fast-xml-parser "^3.17.5" + ipaddr.js "^2.0.1" + json-stream "^1.0.0" + lodash "^4.17.21" + mime-types "^2.1.14" + mkdirp "^0.5.1" + query-string "^7.1.1" + through2 "^3.0.1" + web-encoding "^1.1.5" + xml "^1.0.0" + xml2js "^0.4.15" + minipass@^2.6.0, minipass@^2.9.0: version "2.9.0" resolved "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz" @@ -5689,6 +5839,16 @@ qs@^6.7.0: dependencies: side-channel "^1.0.4" +query-string@^7.1.1: + version "7.1.3" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" + integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg== + dependencies: + decode-uri-component "^0.2.2" + filter-obj "^1.1.0" + split-on-first "^1.0.0" + strict-uri-encode "^2.0.0" + querystring-es3@~0.2.0: version "0.2.1" resolved "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz" @@ -5769,6 +5929,15 @@ read-only-stream@^2.0.0: dependencies: readable-stream "^2.0.2" +"readable-stream@2 || 3", readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" @@ -5782,15 +5951,6 @@ readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - readable-stream@~2.0.0: version "2.0.6" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz" @@ -6354,6 +6514,11 @@ source-map@~0.5.3: resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= +split-on-first@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" + integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== + split2@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz" @@ -6456,6 +6621,11 @@ stream-transform@^1.0.7: resolved "https://registry.npmjs.org/stream-transform/-/stream-transform-1.0.8.tgz" integrity sha512-1q+dL790Ps0NV33rISMq9OLtfDA9KMJZdo1PHZXE85orrWsM4FAh8CVyAOTHO0rhyeM138KNPngBPrx33bFsxw== +strict-uri-encode@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" + integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== + string-width@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz" @@ -6591,6 +6761,11 @@ strip-json-comments@2.0.1, strip-json-comments@~2.0.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= +strnum@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" + integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== + strtok3@^6.2.4: version "6.3.0" resolved "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz" @@ -6767,6 +6942,14 @@ through2@^2.0.0: readable-stream "~2.3.6" xtend "~4.0.1" +through2@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.2.tgz#99f88931cfc761ec7678b41d5d7336b5b6a07bf4" + integrity sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ== + dependencies: + inherits "^2.0.4" + readable-stream "2 || 3" + "through@>=2.2.7 <3", through@~2.3.4: version "2.3.8" resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" @@ -7186,6 +7369,17 @@ util@0.10.3: dependencies: inherits "2.0.1" +util@^0.12.3: + version "0.12.5" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" + integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== + dependencies: + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + which-typed-array "^1.1.2" + util@~0.10.1: version "0.10.4" resolved "https://registry.npmjs.org/util/-/util-0.10.4.tgz" @@ -7256,6 +7450,15 @@ watchpack@^2.3.1: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" +web-encoding@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/web-encoding/-/web-encoding-1.1.5.tgz#fc810cf7667364a6335c939913f5051d3e0c4864" + integrity sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA== + dependencies: + util "^0.12.3" + optionalDependencies: + "@zxing/text-encoding" "0.9.0" + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" @@ -7385,6 +7588,18 @@ which-module@^2.0.0: resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= +which-typed-array@^1.1.2: + version "1.1.9" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6" + integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + gopd "^1.0.1" + has-tostringtag "^1.0.0" + is-typed-array "^1.1.10" + which@1.3.1: version "1.3.1" resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" @@ -7535,14 +7750,19 @@ xml-name-validator@^3.0.0: resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== -xml2js@^0.4.0, xml2js@^0.4.17, xml2js@^0.4.23: +xml2js@^0.4.0, xml2js@^0.4.15, xml2js@^0.4.17, xml2js@^0.4.23: version "0.4.23" - resolved "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== dependencies: sax ">=0.6.0" xmlbuilder "~11.0.0" +xml@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw== + xmlbuilder@~11.0.0: version "11.0.1" resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz"