Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions src/http/output/metadata/AllowAcceptHeaderWriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { NotFoundHttpError } from '../../../util/errors/NotFoundHttpError';
import { UnsupportedMediaTypeHttpError } from '../../../util/errors/UnsupportedMediaTypeHttpError';
import { addHeader } from '../../../util/HeaderUtil';
import { isContainerPath } from '../../../util/PathUtil';
import { LDP, PIM, RDF, SOLID_ERROR } from '../../../util/Vocabularies';
import { LDP, PIM, RDF, SOLID_ERROR, SOLID_META } from '../../../util/Vocabularies';
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
import { MetadataWriter } from './MetadataWriter';

Expand Down Expand Up @@ -102,10 +102,11 @@ export class AllowAcceptHeaderWriter extends MetadataWriter {
}

/**
* PUT is not allowed on existing containers.
* PUT is not allowed on description resources or existing containers.
*/
private isPutAllowed(metadata: RepresentationMetadata, resourceType: ResourceType): boolean {
return resourceType !== ResourceType.container || !metadata.has(RDF.terms.type, LDP.terms.Resource);
return !metadata.has(RDF.terms.type, SOLID_META.terms.DescriptionResource) &&
(resourceType !== ResourceType.container || !metadata.has(RDF.terms.type, LDP.terms.Resource));
}

/**
Expand All @@ -117,12 +118,15 @@ export class AllowAcceptHeaderWriter extends MetadataWriter {

/**
* DELETE is allowed if the target exists,
* is not a container,
* is not a container or description resource,
* or is an empty container that isn't a storage.
*
* Note that the identifier value check only works if the metadata is not about an error.
*/
private isDeleteAllowed(metadata: RepresentationMetadata, resourceType: ResourceType): boolean {
if (metadata.has(RDF.terms.type, SOLID_META.terms.DescriptionResource)) {
return false;
}
if (resourceType !== ResourceType.container) {
return true;
}
Expand Down
17 changes: 7 additions & 10 deletions src/storage/DataAccessorBasedStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,25 +140,22 @@ export class DataAccessorBasedStore implements ResourceStore {
}
}
data = metadata.quads();
}

if (isMetadata) {
metadata = new RepresentationMetadata(this.metadataStrategy.getAuxiliaryIdentifier(identifier));
}
if (isMetadata) {
metadata = new RepresentationMetadata(this.metadataStrategy.getAuxiliaryIdentifier(identifier));
addResourceMetadata(metadata, false);
metadata.add(RDF.terms.type, SOLID_META.terms.DescriptionResource);
}

metadata.addQuad(DC.terms.namespace, PREFERRED_PREFIX_TERM, 'dc', SOLID_META.terms.ResponseMetadata);
metadata.addQuad(LDP.terms.namespace, PREFERRED_PREFIX_TERM, 'ldp', SOLID_META.terms.ResponseMetadata);
metadata.addQuad(POSIX.terms.namespace, PREFERRED_PREFIX_TERM, 'posix', SOLID_META.terms.ResponseMetadata);
metadata.addQuad(XSD.terms.namespace, PREFERRED_PREFIX_TERM, 'xsd', SOLID_META.terms.ResponseMetadata);
}

if (isContainer) {
if (isContainer || isMetadata) {
representation = new BasicRepresentation(data, metadata, INTERNAL_QUADS);
} else if (isMetadata) {
representation = new BasicRepresentation(
metadata.quads(),
this.metadataStrategy.getAuxiliaryIdentifier(identifier),
INTERNAL_QUADS,
);
} else {
representation = new BasicRepresentation(await this.accessor.getData(identifier), metadata);
}
Expand Down
2 changes: 2 additions & 0 deletions src/util/Vocabularies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,8 @@ export const SOLID_META = createVocabulary(
'urn:npm:solid:community-server:meta:',
// This identifier is used as graph for all metadata that is generated on the fly and should not be stored
'ResponseMetadata',
// Used to identify description resources, which are a specific kind of resource
'DescriptionResource',
// This is used to identify templates that can be used for the representation of a resource
'template',
// This is used to store Content-Type Parameters
Expand Down
5 changes: 4 additions & 1 deletion test/integration/LdpHandlerWithoutAuth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createReadStream } from 'node:fs';
import fetch from 'cross-fetch';
import type { Quad } from 'n3';
import { DataFactory, Parser, Store } from 'n3';
import { joinFilePath, joinUrl, PIM, RDF } from '../../src/';
import { joinFilePath, joinUrl, PIM, RDF, splitCommaSeparated } from '../../src/';
import type { App } from '../../src/';
import { LDP } from '../../src/util/Vocabularies';
import {
Expand Down Expand Up @@ -699,6 +699,9 @@ describe.each(stores)('An LDP handler allowing all requests %s', (name, { storeC
it('can read metadata.', async(): Promise<void> => {
const response = await fetch(baseUrl + metaSuffix);
expect(response.status).toBe(200);
const allows = splitCommaSeparated(response.headers.get('allow') ?? '');
expect(allows.sort()).toEqual([ 'GET', 'HEAD', 'OPTIONS', 'PATCH' ]);
expect(response.headers.get('accept-patch')).toBe('text/n3, application/sparql-update');
await expectQuads(response, [ quad(namedNode(baseUrl), namedNode(RDF.type), namedNode(PIM.Storage)) ]);
});

Expand Down
17 changes: 16 additions & 1 deletion test/unit/http/output/metadata/AllowAcceptHeaderWriter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import type { HttpResponse } from '../../../../../src/server/HttpResponse';
import { MethodNotAllowedHttpError } from '../../../../../src/util/errors/MethodNotAllowedHttpError';
import { NotFoundHttpError } from '../../../../../src/util/errors/NotFoundHttpError';
import { UnsupportedMediaTypeHttpError } from '../../../../../src/util/errors/UnsupportedMediaTypeHttpError';
import { LDP, PIM, RDF, SOLID_ERROR } from '../../../../../src/util/Vocabularies';
import { LDP, PIM, RDF, SOLID_ERROR, SOLID_META } from '../../../../../src/util/Vocabularies';

describe('An AllowAcceptHeaderWriter', (): void => {
const document = new RepresentationMetadata(
{ path: 'http://example.com/foo/bar' },
{ [RDF.type]: LDP.terms.Resource },
);
const descriptionResource = new RepresentationMetadata(
{ path: 'http://example.com/foo/bar.meta' },
{ [RDF.type]: [ LDP.terms.Resource, SOLID_META.terms.DescriptionResource ]},
);
const emptyContainer = new RepresentationMetadata(
{ path: 'http://example.com/foo/' },
{ [RDF.type]: [ LDP.terms.Resource, LDP.terms.Container ]},
Expand Down Expand Up @@ -65,6 +69,17 @@ describe('An AllowAcceptHeaderWriter', (): void => {
expect(headers['accept-post']).toBeUndefined();
});

it('returns all methods except POST, PUT, and DELETE for a description resource.', async(): Promise<void> => {
await expect(writer.handleSafe({ response, metadata: descriptionResource })).resolves.toBeUndefined();
const headers = response.getHeaders();
expect(typeof headers.allow).toBe('string');
expect(new Set(headers.allow!.split(', ')))
.toEqual(new Set([ 'OPTIONS', 'GET', 'HEAD', 'PATCH' ]));
expect(headers['accept-patch']).toBe('text/n3, application/sparql-update');
expect(headers['accept-put']).toBeUndefined();
expect(headers['accept-post']).toBeUndefined();
});

it('returns all methods except PUT/PATCH for an empty container.', async(): Promise<void> => {
await expect(writer.handleSafe({ response, metadata: emptyContainer })).resolves.toBeUndefined();
const headers = response.getHeaders();
Expand Down
4 changes: 4 additions & 0 deletions test/unit/storage/DataAccessorBasedStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ describe('A DataAccessorBasedStore', (): void => {
quad(namedNode(resourceID.path), CONTENT_TYPE_TERM, literal('text/plain')),
);
expect(result.metadata.contentType).toBe(INTERNAL_QUADS);
expect(result.metadata.getAll(RDF.terms.type)).toEqualRdfTermArray([
LDP.terms.Resource,
SOLID_META.terms.DescriptionResource,
]);
});

it('will return the generated representation for container metadata resources.', async(): Promise<void> => {
Expand Down
Loading