From 08dcd8eb749699a223e21233a268c19f0c10a1ac Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Fri, 3 Feb 2023 22:11:47 +0800 Subject: [PATCH] fix: allow publish 10mb tarball package by default closes https://github.com/cnpm/cnpmcore/issues/388 --- .../package/SavePackageVersionController.ts | 7 +- config/config.default.ts | 3 + .../SavePackageVersionController.test.ts | 65 +++++++++++++++---- 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/app/port/controller/package/SavePackageVersionController.ts b/app/port/controller/package/SavePackageVersionController.ts index 17fba2d8..4e8aa3f5 100644 --- a/app/port/controller/package/SavePackageVersionController.ts +++ b/app/port/controller/package/SavePackageVersionController.ts @@ -61,7 +61,7 @@ type FullPackage = Omit, 'versions' | '_attachmen }}; // base64 regex https://stackoverflow.com/questions/475074/regex-to-parse-or-validate-base64-data/475217#475217 -const PACKAGE_ATTACH_DATA_RE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/; +const PACKAGE_ATTACH_DATA_RE = /^[A-Za-z0-9+/]{4}/; @HTTPController() export class SavePackageVersionController extends AbstractController { @@ -130,9 +130,12 @@ export class SavePackageVersionController extends AbstractController { } // check attachment data format and size - if (!attachment.data || typeof attachment.data !== 'string' || !PACKAGE_ATTACH_DATA_RE.test(attachment.data)) { + if (!attachment.data || typeof attachment.data !== 'string') { throw new UnprocessableEntityError('attachment.data format invalid'); } + if (!PACKAGE_ATTACH_DATA_RE.test(attachment.data)) { + throw new UnprocessableEntityError('attachment.data string format invalid'); + } const tarballBytes = Buffer.from(attachment.data, 'base64'); if (tarballBytes.length !== attachment.length) { throw new UnprocessableEntityError(`attachment size ${attachment.length} not match download size ${tarballBytes.length}`); diff --git a/config/config.default.ts b/config/config.default.ts index 5526e5a7..4232ac7e 100644 --- a/config/config.default.ts +++ b/config/config.default.ts @@ -151,6 +151,7 @@ export default (appInfo: EggAppConfig) => { config.logger = { enablePerformanceTimer: true, + enableFastContextLogger: true, }; config.logrotator = { @@ -161,6 +162,8 @@ export default (appInfo: EggAppConfig) => { config.bodyParser = { // saveTag will send version string in JSON format strict: false, + // set default limit to 10mb, see https://github.com/npm/npm/issues/12750 + jsonLimit: '10mb', }; // https://github.com/xiekw2010/egg-typebox-validate#%E5%A6%82%E4%BD%95%E5%86%99%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A0%A1%E9%AA%8C%E8%A7%84%E5%88%99 diff --git a/test/port/controller/package/SavePackageVersionController.test.ts b/test/port/controller/package/SavePackageVersionController.test.ts index 7ef2c8b4..5735bdff 100644 --- a/test/port/controller/package/SavePackageVersionController.test.ts +++ b/test/port/controller/package/SavePackageVersionController.test.ts @@ -2,6 +2,7 @@ import assert from 'assert'; import { app } from 'egg-mock/bootstrap'; import { TestUtil } from 'test/TestUtil'; import { UserRepository } from 'app/repository/UserRepository'; +import { calculateIntegrity } from 'app/common/PackageUtil'; describe('test/port/controller/package/SavePackageVersionController.test.ts', () => { let userRepository: UserRepository; @@ -165,6 +166,48 @@ describe('test/port/controller/package/SavePackageVersionController.test.ts', () assert.equal(res.body.versions[version].version, version); }); + it('should publish with 4mb tarball', async () => { + const tarball = Buffer.alloc(4 * 1024 * 1024); + const { integrity } = await calculateIntegrity(tarball); + const pkg = await TestUtil.getFullPackage({ + attachment: { + data: tarball.toString('base64'), + length: tarball.length, + }, + dist: { + integrity, + }, + }); + await app.httpRequest() + .put(`/${pkg.name}`) + .set('authorization', publisher.authorization) + .set('user-agent', publisher.ua) + .send(pkg) + .expect(201); + }); + + // unstable on supertest, will random fail with Error: write ECONNRESET + it.skip('should publish fail and response status 413 when tarball >= 10mb', async () => { + const tarball = Buffer.alloc(10 * 1024 * 1024); + const { integrity } = await calculateIntegrity(tarball); + const pkg = await TestUtil.getFullPackage({ + attachment: { + data: tarball.toString('base64'), + length: tarball.length, + }, + dist: { + integrity, + }, + }); + const res = await app.httpRequest() + .put(`/${pkg.name}`) + .set('authorization', publisher.authorization) + .set('user-agent', publisher.ua) + .send(pkg) + .expect(413); + console.log(res.body, res.text, res.headers); + }); + it('should add new version success', async () => { const pkg = await TestUtil.getFullPackage({ version: '0.0.0' }); let res = await app.httpRequest() @@ -600,7 +643,7 @@ describe('test/port/controller/package/SavePackageVersionController.test.ts', () .set('user-agent', publisher.ua) .send(pkg) .expect(422); - assert.equal(res.body.error, '[UNPROCESSABLE_ENTITY] attachment.data format invalid'); + assert.equal(res.body.error, '[UNPROCESSABLE_ENTITY] attachment.data string format invalid'); pkg = await TestUtil.getFullPackage({ attachment: { @@ -614,34 +657,34 @@ describe('test/port/controller/package/SavePackageVersionController.test.ts', () .send(pkg) .expect(422); assert.equal(res.body.error, '[UNPROCESSABLE_ENTITY] attachment.data format invalid'); + }); - pkg = await TestUtil.getFullPackage({ + it('should 422 when attachment size not match', async () => { + let pkg = await TestUtil.getFullPackage({ attachment: { - data: 'H4sIAAAAAAAAA+2SsWrDMBCGPfspDg2Zine123OyEgeylg6Zau2YR8rVRHEtGkkOg5N0jWaFdujVQAv6W4/7/', + length: 3, }, }); - res = await app.httpRequest() + let res = await app.httpRequest() .put(`/${pkg.name}`) .set('authorization', publisher.authorization) .set('user-agent', publisher.ua) .send(pkg) .expect(422); - assert.equal(res.body.error, '[UNPROCESSABLE_ENTITY] attachment.data format invalid'); - }); + assert.equal(res.body.error, '[UNPROCESSABLE_ENTITY] attachment size 3 not match download size 251'); - it('should 422 when attachment size not match', async () => { - const pkg = await TestUtil.getFullPackage({ + pkg = await TestUtil.getFullPackage({ attachment: { - length: 3, + data: 'H4sIAAAAAAAAA+2SsWrDMBCGPfspDg2Zine123OyEgeylg6Zau2YR8rVRHEtGkkOg5N0jWaFdujVQAv6W4/7/', }, }); - const res = await app.httpRequest() + res = await app.httpRequest() .put(`/${pkg.name}`) .set('authorization', publisher.authorization) .set('user-agent', publisher.ua) .send(pkg) .expect(422); - assert.equal(res.body.error, '[UNPROCESSABLE_ENTITY] attachment size 3 not match download size 251'); + assert.equal(res.body.error, '[UNPROCESSABLE_ENTITY] attachment size 251 not match download size 63'); }); it('should 422 dist.integrity invalid', async () => {