diff --git a/.changeset/chilly-glasses-occur.md b/.changeset/chilly-glasses-occur.md new file mode 100644 index 000000000..4b5a0a700 --- /dev/null +++ b/.changeset/chilly-glasses-occur.md @@ -0,0 +1,8 @@ +--- +'@verdaccio/api': patch +'@verdaccio/core': patch +'@verdaccio/server-fastify': patch +'@verdaccio/store': patch +--- + +fix: abbreviated headers handle quality values diff --git a/.changeset/config.json b/.changeset/config.json index a59a25c15..8dac55d6b 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -3,7 +3,14 @@ "changelog": "@changesets/cli/changelog", "commit": false, "fixed": [ - ["verdaccio", "@verdaccio/cli", "@verdaccio/core", "@verdaccio/config", "@verdaccio/node-api"] + [ + "verdaccio", + "@verdaccio/cli", + "@verdaccio/core", + "@verdaccio/config", + "@verdaccio/node-api", + "@verdaccio/ui-theme" + ] ], "access": "public", "baseBranch": "master", diff --git a/packages/api/src/package.ts b/packages/api/src/package.ts index fbfd0135c..51b15bda2 100644 --- a/packages/api/src/package.ts +++ b/packages/api/src/package.ts @@ -2,7 +2,7 @@ import buildDebug from 'debug'; import { Router } from 'express'; import { IAuth } from '@verdaccio/auth'; -import { HEADERS, HEADER_TYPE } from '@verdaccio/core'; +import { HEADERS, HEADER_TYPE, stringUtils } from '@verdaccio/core'; import { allow } from '@verdaccio/middleware'; import { Storage } from '@verdaccio/store'; @@ -25,7 +25,8 @@ export default function (route: Router, auth: IAuth, storage: Storage): void { const name = req.params.package; let version = req.params.version; const write = req.query.write === 'true'; - const abbreviated = req.get('Accept') === Storage.ABBREVIATED_HEADER; + const abbreviated = + stringUtils.getByQualityPriorityValue(req.get('Accept')) === Storage.ABBREVIATED_HEADER; const requestOptions = { protocol: req.protocol, headers: req.headers as any, @@ -43,6 +44,12 @@ export default function (route: Router, auth: IAuth, storage: Storage): void { version, requestOptions, }); + if (abbreviated) { + _res.setHeader(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_INSTALL_CHARSET); + } else { + _res.setHeader(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON); + } + next(manifest); } catch (err) { next(err); diff --git a/packages/api/test/integration/package.spec.ts b/packages/api/test/integration/package.spec.ts index b5cdfa023..a602dc2ae 100644 --- a/packages/api/test/integration/package.spec.ts +++ b/packages/api/test/integration/package.spec.ts @@ -90,7 +90,7 @@ describe('package', () => { .get(`/${pkg}`) .set(HEADERS.ACCEPT, HEADERS.JSON) .set(HEADERS.ACCEPT, Storage.ABBREVIATED_HEADER) - .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_CHARSET) + .expect(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_INSTALL_CHARSET) .expect(HTTP_STATUS.OK); expect(response.body.name).toEqual(pkg); expect(response.body.time).toBeDefined(); diff --git a/packages/core/core/src/constants.ts b/packages/core/core/src/constants.ts index a205d907b..9c73ed219 100644 --- a/packages/core/core/src/constants.ts +++ b/packages/core/core/src/constants.ts @@ -46,6 +46,7 @@ export const HEADERS = { NONE_MATCH: 'If-None-Match', ETAG: 'ETag', JSON_CHARSET: 'application/json; charset=utf-8', + JSON_INSTALL_CHARSET: 'application/vnd.npm.install-v1+json; charset=utf-8', OCTET_STREAM: 'application/octet-stream; charset=utf-8', TEXT_CHARSET: 'text/plain; charset=utf-8', WWW_AUTH: 'WWW-Authenticate', diff --git a/packages/core/core/src/index.ts b/packages/core/core/src/index.ts index 05402a053..60b68f87f 100644 --- a/packages/core/core/src/index.ts +++ b/packages/core/core/src/index.ts @@ -5,6 +5,7 @@ import * as pkgUtils from './pkg-utils'; import * as pluginUtils from './plugin-utils'; import * as searchUtils from './search-utils'; import * as streamUtils from './stream-utils'; +import * as stringUtils from './string-utils'; import * as validatioUtils from './validation-utils'; import * as warningUtils from './warning-utils'; @@ -33,6 +34,7 @@ export { // TODO: remove this typo validatioUtils, validationUtils, + stringUtils, constants, pluginUtils, warningUtils, diff --git a/packages/core/core/src/string-utils.ts b/packages/core/core/src/string-utils.ts new file mode 100644 index 000000000..b481691fc --- /dev/null +++ b/packages/core/core/src/string-utils.ts @@ -0,0 +1,36 @@ +/** + * Quality values, or q-values and q-factors, are used to describe the order + * of priority of values in a comma-separated list. + * It is a special syntax allowed in some HTTP headers and in HTML. + * https://developer.mozilla.org/en-US/docs/Glossary/Quality_values + * @param headerValue + */ +export function getByQualityPriorityValue(headerValue: string | undefined | null): string { + if (typeof headerValue !== 'string') { + return ''; + } + + const split = headerValue.split(','); + + if (split.length <= 1) { + const qList = split[0].split(';'); + return qList[0]; + } + + let [header] = split + .reduce((acc, item: string) => { + const qList = item.split(';'); + if (qList.length > 1) { + const [accept, q] = qList; + const [, query] = q.split('='); + acc.push([accept.trim(), query ? query : 0]); + } else { + acc.push([qList[0], 0]); + } + return acc; + }, [] as any) + .sort(function (a, b) { + return b[1] - a[1]; + }); + return header[0]; +} diff --git a/packages/core/core/test/string-utils.spec.ts b/packages/core/core/test/string-utils.spec.ts new file mode 100644 index 000000000..476bea5ae --- /dev/null +++ b/packages/core/core/test/string-utils.spec.ts @@ -0,0 +1,40 @@ +import { stringUtils } from '../src'; + +describe('string-utils', () => { + test('getByQualityPriorityValue', () => { + expect(stringUtils.getByQualityPriorityValue('')).toEqual(''); + expect(stringUtils.getByQualityPriorityValue(null)).toEqual(''); + expect(stringUtils.getByQualityPriorityValue(undefined)).toEqual(''); + expect(stringUtils.getByQualityPriorityValue('something')).toEqual('something'); + expect(stringUtils.getByQualityPriorityValue('something,')).toEqual('something'); + expect(stringUtils.getByQualityPriorityValue('0,')).toEqual('0'); + expect(stringUtils.getByQualityPriorityValue('application/json')).toEqual('application/json'); + expect(stringUtils.getByQualityPriorityValue('application/json; q=1')).toEqual( + 'application/json' + ); + expect(stringUtils.getByQualityPriorityValue('application/json; q=')).toEqual( + 'application/json' + ); + expect(stringUtils.getByQualityPriorityValue('application/json;')).toEqual('application/json'); + expect( + stringUtils.getByQualityPriorityValue( + 'application/json; q=1.0, application/vnd.npm.install-v1+json; q=0.9, */*' + ) + ).toEqual('application/json'); + expect( + stringUtils.getByQualityPriorityValue( + 'application/json; q=1.0, application/vnd.npm.install-v1+json; q=, */*' + ) + ).toEqual('application/json'); + expect( + stringUtils.getByQualityPriorityValue( + 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.9, */*' + ) + ).toEqual('application/vnd.npm.install-v1+json'); + expect( + stringUtils.getByQualityPriorityValue( + 'application/vnd.npm.install-v1+json; q=, application/json; q=0.9, */*' + ) + ).toEqual('application/json'); + }); +}); diff --git a/packages/server/fastify/src/endpoints/manifest.ts b/packages/server/fastify/src/endpoints/manifest.ts index c55faa29f..ec28d524b 100644 --- a/packages/server/fastify/src/endpoints/manifest.ts +++ b/packages/server/fastify/src/endpoints/manifest.ts @@ -1,6 +1,8 @@ import buildDebug from 'debug'; import { FastifyInstance } from 'fastify'; +import { stringUtils } from '@verdaccio/core'; +import { Storage } from '@verdaccio/store'; import { Package, Version } from '@verdaccio/types'; const debug = buildDebug('verdaccio:fastify:api:sidebar'); @@ -17,7 +19,9 @@ async function manifestRoute(fastify: FastifyInstance) { const storage = fastify.storage; debug('pkg name %s ', name); // @ts-ignore - const abbreviated = request.headers['accept'] === Storage.ABBREVIATED_HEADER; + const abbreviated = + stringUtils.getByQualityPriorityValue(request.headers['accept']) === + Storage.ABBREVIATED_HEADER; const data = await storage?.getPackageByOptions({ name, // @ts-ignore diff --git a/packages/store/src/storage.ts b/packages/store/src/storage.ts index 9afef6da1..98bfea187 100644 --- a/packages/store/src/storage.ts +++ b/packages/store/src/storage.ts @@ -94,8 +94,7 @@ class Storage { debug('uplinks available %o', Object.keys(this.uplinks)); } - static ABBREVIATED_HEADER = - 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*'; + static ABBREVIATED_HEADER = 'application/vnd.npm.install-v1+json'; /** * Change an existing package (i.e. unpublish one version)