mirror of
https://github.com/verdaccio/verdaccio.git
synced 2025-01-13 22:48:31 -05:00
fix: abbreviated headers handle quality values (#3361)
This commit is contained in:
parent
97078c9084
commit
43f32687cd
10 changed files with 111 additions and 7 deletions
8
.changeset/chilly-glasses-occur.md
Normal file
8
.changeset/chilly-glasses-occur.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
'@verdaccio/api': patch
|
||||||
|
'@verdaccio/core': patch
|
||||||
|
'@verdaccio/server-fastify': patch
|
||||||
|
'@verdaccio/store': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix: abbreviated headers handle quality values
|
|
@ -3,7 +3,14 @@
|
||||||
"changelog": "@changesets/cli/changelog",
|
"changelog": "@changesets/cli/changelog",
|
||||||
"commit": false,
|
"commit": false,
|
||||||
"fixed": [
|
"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",
|
"access": "public",
|
||||||
"baseBranch": "master",
|
"baseBranch": "master",
|
||||||
|
|
|
@ -2,7 +2,7 @@ import buildDebug from 'debug';
|
||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
|
|
||||||
import { IAuth } from '@verdaccio/auth';
|
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 { allow } from '@verdaccio/middleware';
|
||||||
import { Storage } from '@verdaccio/store';
|
import { Storage } from '@verdaccio/store';
|
||||||
|
|
||||||
|
@ -25,7 +25,8 @@ export default function (route: Router, auth: IAuth, storage: Storage): void {
|
||||||
const name = req.params.package;
|
const name = req.params.package;
|
||||||
let version = req.params.version;
|
let version = req.params.version;
|
||||||
const write = req.query.write === 'true';
|
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 = {
|
const requestOptions = {
|
||||||
protocol: req.protocol,
|
protocol: req.protocol,
|
||||||
headers: req.headers as any,
|
headers: req.headers as any,
|
||||||
|
@ -43,6 +44,12 @@ export default function (route: Router, auth: IAuth, storage: Storage): void {
|
||||||
version,
|
version,
|
||||||
requestOptions,
|
requestOptions,
|
||||||
});
|
});
|
||||||
|
if (abbreviated) {
|
||||||
|
_res.setHeader(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON_INSTALL_CHARSET);
|
||||||
|
} else {
|
||||||
|
_res.setHeader(HEADER_TYPE.CONTENT_TYPE, HEADERS.JSON);
|
||||||
|
}
|
||||||
|
|
||||||
next(manifest);
|
next(manifest);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
|
|
|
@ -90,7 +90,7 @@ describe('package', () => {
|
||||||
.get(`/${pkg}`)
|
.get(`/${pkg}`)
|
||||||
.set(HEADERS.ACCEPT, HEADERS.JSON)
|
.set(HEADERS.ACCEPT, HEADERS.JSON)
|
||||||
.set(HEADERS.ACCEPT, Storage.ABBREVIATED_HEADER)
|
.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(HTTP_STATUS.OK);
|
||||||
expect(response.body.name).toEqual(pkg);
|
expect(response.body.name).toEqual(pkg);
|
||||||
expect(response.body.time).toBeDefined();
|
expect(response.body.time).toBeDefined();
|
||||||
|
|
|
@ -46,6 +46,7 @@ export const HEADERS = {
|
||||||
NONE_MATCH: 'If-None-Match',
|
NONE_MATCH: 'If-None-Match',
|
||||||
ETAG: 'ETag',
|
ETAG: 'ETag',
|
||||||
JSON_CHARSET: 'application/json; charset=utf-8',
|
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',
|
OCTET_STREAM: 'application/octet-stream; charset=utf-8',
|
||||||
TEXT_CHARSET: 'text/plain; charset=utf-8',
|
TEXT_CHARSET: 'text/plain; charset=utf-8',
|
||||||
WWW_AUTH: 'WWW-Authenticate',
|
WWW_AUTH: 'WWW-Authenticate',
|
||||||
|
|
|
@ -5,6 +5,7 @@ import * as pkgUtils from './pkg-utils';
|
||||||
import * as pluginUtils from './plugin-utils';
|
import * as pluginUtils from './plugin-utils';
|
||||||
import * as searchUtils from './search-utils';
|
import * as searchUtils from './search-utils';
|
||||||
import * as streamUtils from './stream-utils';
|
import * as streamUtils from './stream-utils';
|
||||||
|
import * as stringUtils from './string-utils';
|
||||||
import * as validatioUtils from './validation-utils';
|
import * as validatioUtils from './validation-utils';
|
||||||
import * as warningUtils from './warning-utils';
|
import * as warningUtils from './warning-utils';
|
||||||
|
|
||||||
|
@ -33,6 +34,7 @@ export {
|
||||||
// TODO: remove this typo
|
// TODO: remove this typo
|
||||||
validatioUtils,
|
validatioUtils,
|
||||||
validationUtils,
|
validationUtils,
|
||||||
|
stringUtils,
|
||||||
constants,
|
constants,
|
||||||
pluginUtils,
|
pluginUtils,
|
||||||
warningUtils,
|
warningUtils,
|
||||||
|
|
36
packages/core/core/src/string-utils.ts
Normal file
36
packages/core/core/src/string-utils.ts
Normal file
|
@ -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];
|
||||||
|
}
|
40
packages/core/core/test/string-utils.spec.ts
Normal file
40
packages/core/core/test/string-utils.spec.ts
Normal file
|
@ -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');
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,6 +1,8 @@
|
||||||
import buildDebug from 'debug';
|
import buildDebug from 'debug';
|
||||||
import { FastifyInstance } from 'fastify';
|
import { FastifyInstance } from 'fastify';
|
||||||
|
|
||||||
|
import { stringUtils } from '@verdaccio/core';
|
||||||
|
import { Storage } from '@verdaccio/store';
|
||||||
import { Package, Version } from '@verdaccio/types';
|
import { Package, Version } from '@verdaccio/types';
|
||||||
|
|
||||||
const debug = buildDebug('verdaccio:fastify:api:sidebar');
|
const debug = buildDebug('verdaccio:fastify:api:sidebar');
|
||||||
|
@ -17,7 +19,9 @@ async function manifestRoute(fastify: FastifyInstance) {
|
||||||
const storage = fastify.storage;
|
const storage = fastify.storage;
|
||||||
debug('pkg name %s ', name);
|
debug('pkg name %s ', name);
|
||||||
// @ts-ignore
|
// @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({
|
const data = await storage?.getPackageByOptions({
|
||||||
name,
|
name,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
|
@ -94,8 +94,7 @@ class Storage {
|
||||||
debug('uplinks available %o', Object.keys(this.uplinks));
|
debug('uplinks available %o', Object.keys(this.uplinks));
|
||||||
}
|
}
|
||||||
|
|
||||||
static ABBREVIATED_HEADER =
|
static ABBREVIATED_HEADER = 'application/vnd.npm.install-v1+json';
|
||||||
'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change an existing package (i.e. unpublish one version)
|
* Change an existing package (i.e. unpublish one version)
|
||||||
|
|
Loading…
Add table
Reference in a new issue