fix: Allow Content-Type: 0 on GET.

Fixes https://github.com/solid/community-server/issues/498
This commit is contained in:
Ruben Verborgh
2021-01-12 15:40:17 +01:00
committed by Joachim Van Herwegen
parent 41899ca9cd
commit 16ef86acef
3 changed files with 67 additions and 13 deletions

View File

@@ -14,18 +14,26 @@ export class RawBodyParser extends BodyParser {
// Note that the only reason this is a union is in case the body is empty.
// If this check gets moved away from the BodyParsers this union could be removed
public async handle({ request, metadata }: BodyParserArgs): Promise<Representation | undefined> {
const {
'content-type': contentType,
'content-length': contentLength,
'transfer-encoding': transferEncoding,
} = request.headers;
// RFC7230, §3.3: The presence of a message body in a request
// is signaled by a Content-Length or Transfer-Encoding header field.
if (!request.headers['content-length'] && !request.headers['transfer-encoding']) {
this.logger.debug('HTTP request appears to not have a body, so nothing to parse');
// While clients SHOULD NOT use use a Content-Length header on GET,
// some still provide a Content-Length of 0 (but without Content-Type).
if ((!contentLength || (/^0+$/u.test(contentLength) && !contentType)) && !transferEncoding) {
this.logger.debug('HTTP request does not have a body, or its empty body is missing a Content-Type header');
return;
}
// 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 (!request.headers['content-type']) {
this.logger.warn('A body was passed, but the content length was not specified');
throw new BadRequestHttpError('HTTP request body was passed without Content-Type header');
if (!contentType) {
this.logger.warn('HTTP request has a body, but no Content-Type header');
throw new BadRequestHttpError('HTTP request body was passed without a Content-Type header');
}
return new BasicRepresentation(request, metadata);

View File

@@ -86,4 +86,31 @@ describe('A server with authorization', (): void => {
);
expect(response.statusCode).toBe(401);
});
// https://github.com/solid/community-server/issues/498
it('accepts a GET with Content-Length: 0.', async(): Promise<void> => {
await aclHelper.setSimpleAcl({ read: true, write: true, append: true }, 'agent');
// PUT
let requestUrl = new URL('http://test.com/foo/bar');
let response: MockResponse<any> = await performRequest(
handler,
requestUrl,
'PUT',
{ 'content-length': '0', 'content-type': 'text/turtle' },
[],
);
expect(response.statusCode).toBe(205);
// GET
requestUrl = new URL('http://test.com/foo/bar');
response = await performRequest(
handler,
requestUrl,
'GET',
{ 'content-length': '0' },
[],
);
expect(response.statusCode).toBe(200);
});
});

View File

@@ -18,27 +18,46 @@ describe('A RawBodyparser', (): void => {
await expect(bodyParser.canHandle({} as any)).resolves.toBeUndefined();
});
it('returns empty output if there was no content length or transfer encoding.', async(): Promise<void> => {
it('returns empty output if there is no content length or transfer encoding.', async(): Promise<void> => {
input.request = streamifyArray([ '' ]) as HttpRequest;
input.request.headers = {};
await expect(bodyParser.handle(input)).resolves.toBeUndefined();
});
it('errors when a content length was specified without content type.', async(): Promise<void> => {
input.request = streamifyArray([ 'abc' ]) as HttpRequest;
// https://github.com/solid/community-server/issues/498
it('returns empty output if the content length is 0 and there is no content type.', async(): Promise<void> => {
input.request = streamifyArray([ '' ]) as HttpRequest;
input.request.headers = { 'content-length': '0' };
await expect(bodyParser.handle(input)).rejects
.toThrow('HTTP request body was passed without Content-Type header');
await expect(bodyParser.handle(input)).resolves.toBeUndefined();
});
it('errors when a transfer encoding was specified without content type.', async(): Promise<void> => {
it('errors when a content length is specified without content type.', async(): Promise<void> => {
input.request = streamifyArray([ 'abc' ]) as HttpRequest;
input.request.headers = { 'content-length': '1' };
await expect(bodyParser.handle(input)).rejects
.toThrow('HTTP request body was passed without a Content-Type header');
});
it('errors when a transfer encoding is specified without content type.', async(): Promise<void> => {
input.request = streamifyArray([ 'abc' ]) as HttpRequest;
input.request.headers = { 'transfer-encoding': 'chunked' };
await expect(bodyParser.handle(input)).rejects
.toThrow('HTTP request body was passed without Content-Type header');
.toThrow('HTTP request body was passed without a Content-Type header');
});
it('returns a Representation if there was data.', async(): Promise<void> => {
it('returns a Representation if there is empty data.', async(): Promise<void> => {
input.request = streamifyArray([]) as HttpRequest;
input.request.headers = { 'content-length': '0', 'content-type': 'text/turtle' };
const result = (await bodyParser.handle(input))!;
expect(result).toEqual({
binary: true,
data: input.request,
metadata: input.metadata,
});
await expect(arrayifyStream(result.data)).resolves.toEqual([]);
});
it('returns a Representation if there is non-empty data.', async(): Promise<void> => {
input.request = streamifyArray([ '<http://test.com/s> <http://test.com/p> <http://test.com/o>.' ]) as HttpRequest;
input.request.headers = { 'transfer-encoding': 'chunked', 'content-type': 'text/turtle' };
const result = (await bodyParser.handle(input))!;