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:
Thomas Dupont
2022-04-01 14:25:09 +02:00
committed by GitHub
parent 3685b7c659
commit 48efc6fae1
9 changed files with 323 additions and 28 deletions

View File

@@ -2,7 +2,7 @@ import type { NamedNode } from '@rdfjs/types';
import { DataFactory } from 'n3';
import { getLoggerFor } from '../../../logging/LogUtil';
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 { MetadataParser } from './MetadataParser';
import namedNode = DataFactory.namedNode;
@@ -23,25 +23,9 @@ export class LinkRelParser extends MetadataParser {
}
public async handle(input: { request: HttpRequest; metadata: RepresentationMetadata }): Promise<void> {
const link = input.request.headers.link ?? [];
const entries: string[] = Array.isArray(link) ? link : [ link ];
for (const entry of entries) {
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)));
}
for (const { target, parameters } of parseLinkHeader(input.request.headers.link)) {
if (this.linkRelMap[parameters.rel]) {
input.metadata.add(this.linkRelMap[parameters.rel], namedNode(target));
}
}
}

View 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,
);
}
}