mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Create MetadataParser that detects JSON with Context link and throws an error
* feat: add PlainJsonLdFilter to reject JSON with context link * refactor: abstract parseLinkHeader into HeaderUtils * docs: typo in comment field Co-authored-by: Ruben Verborgh <ruben@verborgh.org> * refactor: Replace BadRequestHttpError with NotImplementedError Co-authored-by: Ruben Verborgh <ruben@verborgh.org> * refactor: incorporate requested changes * refactor: requested changes incorporated * refactor: remove obsolete code lines Co-authored-by: Ruben Verborgh <ruben@verborgh.org>
This commit is contained in:
parent
3685b7c659
commit
48efc6fae1
@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^3.0.0/components/context.jsonld",
|
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^3.0.0/components/context.jsonld",
|
||||||
"import": [
|
"import": [
|
||||||
"files-scs:config/ldp/metadata-parser/parsers/content-type.json",
|
|
||||||
"files-scs:config/ldp/metadata-parser/parsers/content-length.json",
|
"files-scs:config/ldp/metadata-parser/parsers/content-length.json",
|
||||||
"files-scs:config/ldp/metadata-parser/parsers/slug.json",
|
"files-scs:config/ldp/metadata-parser/parsers/content-type.json",
|
||||||
"files-scs:config/ldp/metadata-parser/parsers/link.json"
|
"files-scs:config/ldp/metadata-parser/parsers/link.json",
|
||||||
|
"files-scs:config/ldp/metadata-parser/parsers/plain-json-ld-filter.json",
|
||||||
|
"files-scs:config/ldp/metadata-parser/parsers/slug.json"
|
||||||
],
|
],
|
||||||
"@graph": [
|
"@graph": [
|
||||||
{
|
{
|
||||||
@ -12,10 +13,11 @@
|
|||||||
"@id": "urn:solid-server:default:MetadataParser",
|
"@id": "urn:solid-server:default:MetadataParser",
|
||||||
"@type": "ParallelHandler",
|
"@type": "ParallelHandler",
|
||||||
"handlers": [
|
"handlers": [
|
||||||
{ "@id": "urn:solid-server:default:ContentTypeParser" },
|
|
||||||
{ "@id": "urn:solid-server:default:ContentLengthParser" },
|
{ "@id": "urn:solid-server:default:ContentLengthParser" },
|
||||||
{ "@id": "urn:solid-server:default:SlugParser" },
|
{ "@id": "urn:solid-server:default:ContentTypeParser" },
|
||||||
{ "@id": "urn:solid-server:default:LinkRelParser" }
|
{ "@id": "urn:solid-server:default:LinkRelParser" },
|
||||||
|
{ "@id": "urn:solid-server:default:PlainJsonLdFilter" },
|
||||||
|
{ "@id": "urn:solid-server:default:SlugParser" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
10
config/ldp/metadata-parser/parsers/plain-json-ld-filter.json
Normal file
10
config/ldp/metadata-parser/parsers/plain-json-ld-filter.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^3.0.0/components/context.jsonld",
|
||||||
|
"@graph": [
|
||||||
|
{
|
||||||
|
"comment": "Checks for JSON-LD posted with plain application/json as content type and errors if so.",
|
||||||
|
"@id": "urn:solid-server:default:PlainJsonLdFilter",
|
||||||
|
"@type": "PlainJsonLdFilter"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -2,7 +2,7 @@ import type { NamedNode } from '@rdfjs/types';
|
|||||||
import { DataFactory } from 'n3';
|
import { DataFactory } from 'n3';
|
||||||
import { getLoggerFor } from '../../../logging/LogUtil';
|
import { getLoggerFor } from '../../../logging/LogUtil';
|
||||||
import type { HttpRequest } from '../../../server/HttpRequest';
|
import type { HttpRequest } from '../../../server/HttpRequest';
|
||||||
import { parseParameters, splitAndClean, transformQuotedStrings } from '../../../util/HeaderUtil';
|
import { parseLinkHeader } from '../../../util/HeaderUtil';
|
||||||
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
|
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
|
||||||
import { MetadataParser } from './MetadataParser';
|
import { MetadataParser } from './MetadataParser';
|
||||||
import namedNode = DataFactory.namedNode;
|
import namedNode = DataFactory.namedNode;
|
||||||
@ -23,25 +23,9 @@ export class LinkRelParser extends MetadataParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async handle(input: { request: HttpRequest; metadata: RepresentationMetadata }): Promise<void> {
|
public async handle(input: { request: HttpRequest; metadata: RepresentationMetadata }): Promise<void> {
|
||||||
const link = input.request.headers.link ?? [];
|
for (const { target, parameters } of parseLinkHeader(input.request.headers.link)) {
|
||||||
const entries: string[] = Array.isArray(link) ? link : [ link ];
|
if (this.linkRelMap[parameters.rel]) {
|
||||||
for (const entry of entries) {
|
input.metadata.add(this.linkRelMap[parameters.rel], namedNode(target));
|
||||||
this.parseLink(entry, input.metadata);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected parseLink(linkEntry: string, metadata: RepresentationMetadata): void {
|
|
||||||
const { result, replacements } = transformQuotedStrings(linkEntry);
|
|
||||||
for (const part of splitAndClean(result)) {
|
|
||||||
const [ link, ...parameters ] = part.split(/\s*;\s*/u);
|
|
||||||
if (/^[^<]|[^>]$/u.test(link)) {
|
|
||||||
this.logger.warn(`Invalid link header ${part}.`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (const { name, value } of parseParameters(parameters, replacements)) {
|
|
||||||
if (name === 'rel' && this.linkRelMap[value]) {
|
|
||||||
metadata.add(this.linkRelMap[value], namedNode(link.slice(1, -1)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
45
src/http/input/metadata/PlainJsonLdFilter.ts
Normal file
45
src/http/input/metadata/PlainJsonLdFilter.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { getLoggerFor } from '../../../logging/LogUtil';
|
||||||
|
import type { HttpRequest } from '../../../server/HttpRequest';
|
||||||
|
import { NotImplementedHttpError } from '../../../util/errors/NotImplementedHttpError';
|
||||||
|
import { parseContentType, parseLinkHeader } from '../../../util/HeaderUtil';
|
||||||
|
import { JSON_LD } from '../../../util/Vocabularies';
|
||||||
|
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
|
||||||
|
import { MetadataParser } from './MetadataParser';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter that errors on JSON-LD with a plain application/json content-type.
|
||||||
|
* This will not store metadata, only throw errors if necessary.
|
||||||
|
*/
|
||||||
|
export class PlainJsonLdFilter extends MetadataParser {
|
||||||
|
protected readonly logger = getLoggerFor(this);
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handle(input: {
|
||||||
|
request: HttpRequest;
|
||||||
|
metadata: RepresentationMetadata;
|
||||||
|
}): Promise<void> {
|
||||||
|
const contentTypeHeader = input.request.headers['content-type'];
|
||||||
|
if (!contentTypeHeader) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { value: contentType } = parseContentType(contentTypeHeader);
|
||||||
|
// Throw error on content-type application/json AND a link header that refers to a JSON-LD context.
|
||||||
|
if (
|
||||||
|
contentType === 'application/json' &&
|
||||||
|
this.linkHasContextRelation(input.request.headers.link)
|
||||||
|
) {
|
||||||
|
throw new NotImplementedHttpError(
|
||||||
|
'JSON-LD is only supported with the application/ld+json content type.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private linkHasContextRelation(link: string | string[] = []): boolean {
|
||||||
|
return parseLinkHeader(link).some(
|
||||||
|
({ parameters }): boolean => parameters.rel === JSON_LD.context,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -63,6 +63,7 @@ export * from './http/input/metadata/ContentLengthParser';
|
|||||||
export * from './http/input/metadata/ContentTypeParser';
|
export * from './http/input/metadata/ContentTypeParser';
|
||||||
export * from './http/input/metadata/LinkRelParser';
|
export * from './http/input/metadata/LinkRelParser';
|
||||||
export * from './http/input/metadata/MetadataParser';
|
export * from './http/input/metadata/MetadataParser';
|
||||||
|
export * from './http/input/metadata/PlainJsonLdFilter';
|
||||||
export * from './http/input/metadata/SlugParser';
|
export * from './http/input/metadata/SlugParser';
|
||||||
|
|
||||||
// HTTP/Input/Preferences
|
// HTTP/Input/Preferences
|
||||||
|
@ -108,6 +108,16 @@ export interface ContentType {
|
|||||||
parameters: Record<string, string>;
|
parameters: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LinkEntryParameters extends Record<string, string> {
|
||||||
|
/** Required rel properties of Link entry */
|
||||||
|
rel: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LinkEntry {
|
||||||
|
target: string;
|
||||||
|
parameters: LinkEntryParameters;
|
||||||
|
}
|
||||||
|
|
||||||
// REUSED REGEXES
|
// REUSED REGEXES
|
||||||
const tchar = /[a-zA-Z0-9!#$%&'*+-.^_`|~]/u;
|
const tchar = /[a-zA-Z0-9!#$%&'*+-.^_`|~]/u;
|
||||||
const token = new RegExp(`^${tchar.source}+$`, 'u');
|
const token = new RegExp(`^${tchar.source}+$`, 'u');
|
||||||
@ -495,3 +505,45 @@ export function parseForwarded(headers: IncomingHttpHeaders): Forwarded {
|
|||||||
}
|
}
|
||||||
return forwarded;
|
return forwarded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the link header(s) and returns an array of LinkEntry objects.
|
||||||
|
* @param link - A single link header or an array of link headers
|
||||||
|
* @returns A LinkEntry array, LinkEntry contains a link and a params Record<string,string>
|
||||||
|
*/
|
||||||
|
export function parseLinkHeader(link: string | string[] = []): LinkEntry[] {
|
||||||
|
const linkHeaders = Array.isArray(link) ? link : [ link ];
|
||||||
|
const links: LinkEntry[] = [];
|
||||||
|
for (const entry of linkHeaders) {
|
||||||
|
const { result, replacements } = transformQuotedStrings(entry);
|
||||||
|
for (const part of splitAndClean(result)) {
|
||||||
|
const [ target, ...parameters ] = part.split(/\s*;\s*/u);
|
||||||
|
if (/^[^<]|[^>]$/u.test(target)) {
|
||||||
|
logger.warn(`Invalid link header ${part}.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC 8288 - Web Linking (https://datatracker.ietf.org/doc/html/rfc8288)
|
||||||
|
//
|
||||||
|
// The rel parameter MUST be
|
||||||
|
// present but MUST NOT appear more than once in a given link-value;
|
||||||
|
// occurrences after the first MUST be ignored by parsers.
|
||||||
|
//
|
||||||
|
const params: any = {};
|
||||||
|
for (const { name, value } of parseParameters(parameters, replacements)) {
|
||||||
|
if (name === 'rel' && 'rel' in params) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
params[name] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!('rel' in params)) {
|
||||||
|
logger.warn(`Invalid link header ${part} contains no 'rel' parameter.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
links.push({ target: target.slice(1, -1), parameters: params });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
@ -96,6 +96,10 @@ export const HTTP = createUriAndTermNamespace('http://www.w3.org/2011/http#',
|
|||||||
|
|
||||||
export const IANA = createUriAndTermNamespace('http://www.w3.org/ns/iana/media-types/');
|
export const IANA = createUriAndTermNamespace('http://www.w3.org/ns/iana/media-types/');
|
||||||
|
|
||||||
|
export const JSON_LD = createUriAndTermNamespace('http://www.w3.org/ns/json-ld#',
|
||||||
|
'context',
|
||||||
|
);
|
||||||
|
|
||||||
export const LDP = createUriAndTermNamespace('http://www.w3.org/ns/ldp#',
|
export const LDP = createUriAndTermNamespace('http://www.w3.org/ns/ldp#',
|
||||||
'contains',
|
'contains',
|
||||||
|
|
||||||
|
63
test/unit/http/input/metadata/PlainJsonLdFilter.test.ts
Normal file
63
test/unit/http/input/metadata/PlainJsonLdFilter.test.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { NotImplementedHttpError } from '../../../../../src';
|
||||||
|
import { PlainJsonLdFilter } from '../../../../../src/http/input/metadata/PlainJsonLdFilter';
|
||||||
|
import { RepresentationMetadata } from '../../../../../src/http/representation/RepresentationMetadata';
|
||||||
|
import type { HttpRequest } from '../../../../../src/server/HttpRequest';
|
||||||
|
|
||||||
|
describe('A PlainJsonLdFilter', (): void => {
|
||||||
|
const parser = new PlainJsonLdFilter();
|
||||||
|
let request: HttpRequest;
|
||||||
|
let metadata: RepresentationMetadata;
|
||||||
|
|
||||||
|
beforeEach(async(): Promise<void> => {
|
||||||
|
request = { headers: {}} as HttpRequest;
|
||||||
|
metadata = new RepresentationMetadata();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does nothing if there are no type headers.', async(): Promise<void> => {
|
||||||
|
await expect(parser.handle({ request, metadata })).resolves.toBeUndefined();
|
||||||
|
expect(metadata.quads()).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does allow content-type application/json on its own.', async(): Promise<void> => {
|
||||||
|
request.headers['content-type'] = 'application/json';
|
||||||
|
await expect(parser.handle({ request, metadata })).resolves.toBeUndefined();
|
||||||
|
expect(metadata.quads()).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does allow a correct content-type and link headers combination.', async(): Promise<void> => {
|
||||||
|
request.headers['content-type'] = 'application/json+ld';
|
||||||
|
request.headers.link = '<https://json-ld.org/contexts/person.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"';
|
||||||
|
await expect(parser.handle({ request, metadata })).resolves.toBeUndefined();
|
||||||
|
expect(metadata.quads()).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws error when content-type and link header are in conflict.', async(): Promise<void> => {
|
||||||
|
request.headers['content-type'] = 'application/json';
|
||||||
|
request.headers.link = '<https://json-ld.org/contexts/person.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"';
|
||||||
|
await expect(parser.handle({ request, metadata })).rejects.toThrow(NotImplementedHttpError);
|
||||||
|
expect(metadata.quads()).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws error when at least 1 content-type and link header are in conflict.', async(): Promise<void> => {
|
||||||
|
request.headers['content-type'] = 'application/json';
|
||||||
|
request.headers.link = [
|
||||||
|
'<http://test.com/type>; rel="type"',
|
||||||
|
'<https://json-ld.org/contexts/person.jsonld>; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"',
|
||||||
|
];
|
||||||
|
await expect(parser.handle({ request, metadata })).rejects.toThrow(NotImplementedHttpError);
|
||||||
|
expect(metadata.quads()).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores invalid link headers.', async(): Promise<void> => {
|
||||||
|
request.headers['content-type'] = 'application/json';
|
||||||
|
request.headers.link = 'http://test.com/type;rel="type"';
|
||||||
|
await expect(parser.handle({ request, metadata })).resolves.toBeUndefined();
|
||||||
|
expect(metadata.quads()).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores empty content-type headers.', async(): Promise<void> => {
|
||||||
|
request.headers.link = '<http://test.com/type>;rel="type"';
|
||||||
|
await expect(parser.handle({ request, metadata })).resolves.toBeUndefined();
|
||||||
|
expect(metadata.quads()).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
@ -9,6 +9,7 @@ import {
|
|||||||
parseAcceptLanguage,
|
parseAcceptLanguage,
|
||||||
parseContentType,
|
parseContentType,
|
||||||
parseForwarded,
|
parseForwarded,
|
||||||
|
parseLinkHeader,
|
||||||
} from '../../../src/util/HeaderUtil';
|
} from '../../../src/util/HeaderUtil';
|
||||||
|
|
||||||
describe('HeaderUtil', (): void => {
|
describe('HeaderUtil', (): void => {
|
||||||
@ -43,9 +44,11 @@ describe('HeaderUtil', (): void => {
|
|||||||
|
|
||||||
it('parses Accept headers with double quoted values.', async(): Promise<void> => {
|
it('parses Accept headers with double quoted values.', async(): Promise<void> => {
|
||||||
expect(parseAccept('audio/basic; param1="val" ; q=0.5 ;param2="\\\\\\"valid"')).toEqual([
|
expect(parseAccept('audio/basic; param1="val" ; q=0.5 ;param2="\\\\\\"valid"')).toEqual([
|
||||||
{ range: 'audio/basic',
|
{
|
||||||
|
range: 'audio/basic',
|
||||||
weight: 0.5,
|
weight: 0.5,
|
||||||
parameters: { mediaType: { param1: 'val' }, extension: { param2: '\\\\\\"valid' }}},
|
parameters: { mediaType: { param1: 'val' }, extension: { param2: '\\\\\\"valid' }},
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -285,4 +288,135 @@ describe('HeaderUtil', (): void => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#parseLinkHeader', (): void => {
|
||||||
|
it('handles an empty set of headers.', (): void => {
|
||||||
|
expect(parseLinkHeader([])).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles empty string values.', (): void => {
|
||||||
|
expect(parseLinkHeader([ '' ])).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses a Link header value as array.', (): void => {
|
||||||
|
const link = [ '<http://test.com>; rel="myRel"; test="value1"' ];
|
||||||
|
expect(parseLinkHeader(link)).toEqual([
|
||||||
|
{
|
||||||
|
target: 'http://test.com',
|
||||||
|
parameters: {
|
||||||
|
rel: 'myRel',
|
||||||
|
test: 'value1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses a Link header value as string.', (): void => {
|
||||||
|
const link = '<http://test.com>; rel="myRel"; test="value1"';
|
||||||
|
expect(parseLinkHeader(link)).toEqual([
|
||||||
|
{
|
||||||
|
target: 'http://test.com',
|
||||||
|
parameters: {
|
||||||
|
rel: 'myRel',
|
||||||
|
test: 'value1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses multiple Link header values delimited by a comma.', (): void => {
|
||||||
|
const link = [ `<http://test.com>; rel="myRel"; test="value1",
|
||||||
|
<http://test2.com>; rel="myRel2"; test="value2"` ];
|
||||||
|
expect(parseLinkHeader(link)).toEqual([
|
||||||
|
{
|
||||||
|
target: 'http://test.com',
|
||||||
|
parameters: {
|
||||||
|
rel: 'myRel',
|
||||||
|
test: 'value1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: 'http://test2.com',
|
||||||
|
parameters: {
|
||||||
|
rel: 'myRel2',
|
||||||
|
test: 'value2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses multiple Link header values as array elements.', (): void => {
|
||||||
|
const link = [
|
||||||
|
'<http://test.com>; rel="myRel"; test="value1"',
|
||||||
|
'<http://test2.com>; rel="myRel2"; test="value2"',
|
||||||
|
];
|
||||||
|
expect(parseLinkHeader(link)).toEqual([
|
||||||
|
{
|
||||||
|
target: 'http://test.com',
|
||||||
|
parameters: {
|
||||||
|
rel: 'myRel',
|
||||||
|
test: 'value1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: 'http://test2.com',
|
||||||
|
parameters: {
|
||||||
|
rel: 'myRel2',
|
||||||
|
test: 'value2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores invalid syntax links.', (): void => {
|
||||||
|
const link = [
|
||||||
|
'http://test.com; rel="myRel"; test="value1"',
|
||||||
|
'<http://test2.com>; rel="myRel2"; test="value2"',
|
||||||
|
];
|
||||||
|
expect(parseLinkHeader(link)).toEqual([
|
||||||
|
{
|
||||||
|
target: 'http://test2.com',
|
||||||
|
parameters: {
|
||||||
|
rel: 'myRel2',
|
||||||
|
test: 'value2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores invalid links (no rel parameter).', (): void => {
|
||||||
|
const link = [
|
||||||
|
'<http://test.com>; att="myAtt"; test="value1"',
|
||||||
|
'<http://test2.com>; rel="myRel2"; test="value2"',
|
||||||
|
];
|
||||||
|
expect(parseLinkHeader(link)).toEqual([
|
||||||
|
{
|
||||||
|
target: 'http://test2.com',
|
||||||
|
parameters: {
|
||||||
|
rel: 'myRel2',
|
||||||
|
test: 'value2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores extra rel parameters.', (): void => {
|
||||||
|
const link = [
|
||||||
|
'<http://test.com>; rel="myRel1"; rel="myRel2"; test="value1"',
|
||||||
|
];
|
||||||
|
expect(parseLinkHeader(link)).toEqual([
|
||||||
|
{
|
||||||
|
target: 'http://test.com',
|
||||||
|
parameters: {
|
||||||
|
rel: 'myRel1',
|
||||||
|
test: 'value1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works with an empty argument.', (): void => {
|
||||||
|
expect(parseLinkHeader()).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user