mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
fix: Have AsyncHandlers only check what is necessary
This commit is contained in:
parent
10723bb6b8
commit
4d34cdd12f
@ -28,20 +28,18 @@ export class BasicRequestParser extends RequestParser {
|
||||
Object.assign(this, args);
|
||||
}
|
||||
|
||||
public async canHandle(input: HttpRequest): Promise<void> {
|
||||
if (!input.url) {
|
||||
throw new Error('Missing URL.');
|
||||
}
|
||||
if (!input.method) {
|
||||
throw new Error('Missing method.');
|
||||
}
|
||||
public async canHandle(): Promise<void> {
|
||||
// Can handle all requests
|
||||
}
|
||||
|
||||
public async handle(input: HttpRequest): Promise<Operation> {
|
||||
if (!input.method) {
|
||||
throw new Error('Missing method.');
|
||||
}
|
||||
const target = await this.targetExtractor.handleSafe(input);
|
||||
const preferences = await this.preferenceParser.handleSafe(input);
|
||||
const body = await this.bodyParser.handleSafe(input);
|
||||
|
||||
return { method: input.method!, target, preferences, body };
|
||||
return { method: input.method, target, preferences, body };
|
||||
}
|
||||
}
|
||||
|
@ -10,10 +10,8 @@ import { ResponseWriter } from './ResponseWriter';
|
||||
*/
|
||||
export class BasicResponseWriter extends ResponseWriter {
|
||||
public async canHandle(input: { response: HttpResponse; result: ResponseDescription | Error }): Promise<void> {
|
||||
if (!(input.result instanceof Error)) {
|
||||
if (input.result.body && !input.result.body.binary) {
|
||||
throw new UnsupportedHttpError('Only binary results are supported.');
|
||||
}
|
||||
if (!(input.result instanceof Error) && input.result.body && !input.result.body.binary) {
|
||||
throw new UnsupportedHttpError('Only binary results and errors are supported.');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,16 +10,17 @@ import { TargetExtractor } from './TargetExtractor';
|
||||
* TODO: input requires more extensive cleaning/parsing based on headers (see #22).
|
||||
*/
|
||||
export class BasicTargetExtractor extends TargetExtractor {
|
||||
public async canHandle(input: HttpRequest): Promise<void> {
|
||||
public async canHandle(): Promise<void> {
|
||||
// Can handle all URLs
|
||||
}
|
||||
|
||||
public async handle(input: HttpRequest): Promise<ResourceIdentifier> {
|
||||
if (!input.url) {
|
||||
throw new Error('Missing URL.');
|
||||
}
|
||||
if (!input.headers.host) {
|
||||
throw new Error('Missing host.');
|
||||
}
|
||||
}
|
||||
|
||||
public async handle(input: HttpRequest): Promise<ResourceIdentifier> {
|
||||
const isHttps = input.connection && (input.connection as TLSSocket).encrypted;
|
||||
const url = format({
|
||||
protocol: `http${isHttps ? 's' : ''}`,
|
||||
|
@ -27,7 +27,7 @@ export class RawBodyParser extends BodyParser {
|
||||
// While RFC7231 allows treating a body without content type as an octet stream,
|
||||
// such an omission likely signals a mistake, so force clients to make this explicit.
|
||||
if (!input.headers['content-type']) {
|
||||
throw new Error('An HTTP request body was passed without Content-Type header');
|
||||
throw new UnsupportedHttpError('An HTTP request body was passed without Content-Type header');
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { PassThrough } from 'stream';
|
||||
import { translate } from 'sparqlalgebrajs';
|
||||
import type { HttpRequest } from '../../server/HttpRequest';
|
||||
import { APPLICATION_SPARQL_UPDATE } from '../../util/ContentTypes';
|
||||
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
|
||||
import { UnsupportedMediaTypeHttpError } from '../../util/errors/UnsupportedMediaTypeHttpError';
|
||||
import { CONTENT_TYPE } from '../../util/UriConstants';
|
||||
@ -16,9 +17,7 @@ import type { SparqlUpdatePatch } from './SparqlUpdatePatch';
|
||||
*/
|
||||
export class SparqlUpdateBodyParser extends BodyParser {
|
||||
public async canHandle(input: HttpRequest): Promise<void> {
|
||||
const contentType = input.headers['content-type'];
|
||||
|
||||
if (!contentType || contentType !== 'application/sparql-update') {
|
||||
if (input.headers['content-type'] !== APPLICATION_SPARQL_UPDATE) {
|
||||
throw new UnsupportedMediaTypeHttpError('This parser only supports SPARQL UPDATE data.');
|
||||
}
|
||||
}
|
||||
@ -35,7 +34,7 @@ export class SparqlUpdateBodyParser extends BodyParser {
|
||||
const sparql = await readableToString(toAlgebraStream);
|
||||
const algebra = translate(sparql, { quads: true });
|
||||
|
||||
const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'application/sparql-update' });
|
||||
const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: APPLICATION_SPARQL_UPDATE });
|
||||
|
||||
// Prevent body from being requested again
|
||||
return {
|
||||
|
@ -20,13 +20,13 @@ export class PostOperationHandler extends OperationHandler {
|
||||
if (input.method !== 'POST') {
|
||||
throw new UnsupportedHttpError('This handler only supports POST operations.');
|
||||
}
|
||||
if (!input.body) {
|
||||
throw new UnsupportedHttpError('POST operations require a body.');
|
||||
}
|
||||
}
|
||||
|
||||
public async handle(input: Operation): Promise<ResponseDescription> {
|
||||
const identifier = await this.store.addResource(input.target, input.body!);
|
||||
if (!input.body) {
|
||||
throw new UnsupportedHttpError('POST operations require a body.');
|
||||
}
|
||||
const identifier = await this.store.addResource(input.target, input.body);
|
||||
return { identifier };
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
import type { Representation } from '../../ldp/representation/Representation';
|
||||
import { RepresentationMetadata } from '../../ldp/representation/RepresentationMetadata';
|
||||
import type { RepresentationPreferences } from '../../ldp/representation/RepresentationPreferences';
|
||||
import { CONTENT_TYPE } from '../../util/UriConstants';
|
||||
import { matchingMediaType } from '../../util/Util';
|
||||
import { checkRequest } from './ConversionUtil';
|
||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||
|
||||
@ -44,18 +42,14 @@ export class ChainedConverter extends TypedRepresentationConverter {
|
||||
|
||||
public async canHandle(input: RepresentationConverterArgs): Promise<void> {
|
||||
// We assume a chain can be constructed, otherwise there would be a configuration issue
|
||||
// Check if the first converter can handle the input
|
||||
const firstChain = await this.getMatchingType(this.converters[0], this.converters[1]);
|
||||
const preferences: RepresentationPreferences = { type: [{ value: firstChain, weight: 1 }]};
|
||||
await this.first.canHandle({ ...input, preferences });
|
||||
// So we only check if the input can be parsed and the preferred type can be written
|
||||
const inTypes = this.filterTypes(await this.first.getInputTypes());
|
||||
const outTypes = this.filterTypes(await this.last.getOutputTypes());
|
||||
checkRequest(input, inTypes, outTypes);
|
||||
}
|
||||
|
||||
// Check if the last converter can produce the output
|
||||
const idx = this.converters.length - 1;
|
||||
const lastChain = await this.getMatchingType(this.converters[idx - 1], this.converters[idx]);
|
||||
const oldMeta = input.representation.metadata;
|
||||
const metadata = new RepresentationMetadata(oldMeta, { [CONTENT_TYPE]: lastChain });
|
||||
const representation: Representation = { ...input.representation, metadata };
|
||||
await this.last.canHandle({ ...input, representation });
|
||||
private filterTypes(typeVals: { [contentType: string]: number }): string[] {
|
||||
return Object.keys(typeVals).filter((name): boolean => typeVals[name] > 0);
|
||||
}
|
||||
|
||||
public async handle(input: RepresentationConverterArgs): Promise<Representation> {
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Well-known content types
|
||||
export const TEXT_TURTLE = 'text/turtle';
|
||||
export const APPLICATION_OCTET_STREAM = 'application/octet-stream';
|
||||
export const APPLICATION_SPARQL_UPDATE = 'application/sparql-update';
|
||||
|
||||
// Internal (non-exposed) content types
|
||||
export const INTERNAL_QUADS = 'internal/quads';
|
||||
|
@ -17,16 +17,12 @@ describe('A BasicRequestParser', (): void => {
|
||||
requestParser = new BasicRequestParser({ targetExtractor, bodyParser, preferenceParser });
|
||||
});
|
||||
|
||||
it('can handle input with both a URL and a method.', async(): Promise<void> => {
|
||||
await expect(requestParser.canHandle({ url: 'url', method: 'GET' } as any)).resolves.toBeUndefined();
|
||||
it('can handle any input.', async(): Promise<void> => {
|
||||
await expect(requestParser.canHandle()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('rejects input with no URL.', async(): Promise<void> => {
|
||||
await expect(requestParser.canHandle({ method: 'GET' } as any)).rejects.toThrow('Missing URL.');
|
||||
});
|
||||
|
||||
it('rejects input with no method.', async(): Promise<void> => {
|
||||
await expect(requestParser.canHandle({ url: 'url' } as any)).rejects.toThrow('Missing method.');
|
||||
it('errors if there is no input.', async(): Promise<void> => {
|
||||
await expect(requestParser.handle({ url: 'url' } as any)).rejects.toThrow('Missing method.');
|
||||
});
|
||||
|
||||
it('returns the output of all input parsers after calling handle.', async(): Promise<void> => {
|
||||
|
@ -3,16 +3,16 @@ import { BasicTargetExtractor } from '../../../../src/ldp/http/BasicTargetExtrac
|
||||
describe('A BasicTargetExtractor', (): void => {
|
||||
const extractor = new BasicTargetExtractor();
|
||||
|
||||
it('can handle input with an URL and host.', async(): Promise<void> => {
|
||||
await expect(extractor.canHandle({ url: 'url', headers: { host: 'test.com' }} as any)).resolves.toBeUndefined();
|
||||
it('can handle any input.', async(): Promise<void> => {
|
||||
await expect(extractor.canHandle()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('rejects input without URL.', async(): Promise<void> => {
|
||||
await expect(extractor.canHandle({ headers: { host: 'test.com' }} as any)).rejects.toThrow('Missing URL.');
|
||||
it('errors if there is no URL.', async(): Promise<void> => {
|
||||
await expect(extractor.handle({ headers: { host: 'test.com' }} as any)).rejects.toThrow('Missing URL.');
|
||||
});
|
||||
|
||||
it('rejects input without host.', async(): Promise<void> => {
|
||||
await expect(extractor.canHandle({ url: 'url', headers: {}} as any)).rejects.toThrow('Missing host.');
|
||||
it('errors if there is no host.', async(): Promise<void> => {
|
||||
await expect(extractor.handle({ url: 'url', headers: {}} as any)).rejects.toThrow('Missing host.');
|
||||
});
|
||||
|
||||
it('returns the input URL.', async(): Promise<void> => {
|
||||
|
@ -10,12 +10,15 @@ describe('A PostOperationHandler', (): void => {
|
||||
} as unknown as ResourceStore;
|
||||
const handler = new PostOperationHandler(store);
|
||||
|
||||
it('only supports POST operations with a body.', async(): Promise<void> => {
|
||||
it('only supports POST operations.', async(): Promise<void> => {
|
||||
await expect(handler.canHandle({ method: 'POST', body: { }} as Operation))
|
||||
.resolves.toBeUndefined();
|
||||
await expect(handler.canHandle({ method: 'GET', body: { }} as Operation))
|
||||
.rejects.toThrow(UnsupportedHttpError);
|
||||
await expect(handler.canHandle({ method: 'POST' } as Operation)).rejects.toThrow(UnsupportedHttpError);
|
||||
});
|
||||
|
||||
it('errors if there is no body.', async(): Promise<void> => {
|
||||
await expect(handler.handle({ method: 'POST' } as Operation)).rejects.toThrow(UnsupportedHttpError);
|
||||
});
|
||||
|
||||
it('adds the given representation to the store and returns the new identifier.', async(): Promise<void> => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user