Repository: solid/community-server (ts)

Issue:
POST succeeds on a non-existing URI target
**Version:** CSS 0.4.0

**Startup options:** default (npm start)

**Request:** 
_from clean store where /container1/ does not yet exist._
curl -v   -X POST -H "Content-Type: text/turtle" -H "Link:   <http://www.w3.org/ns/ldp#BasicContainer>; rel=\"type\""   -d "<#a> <#b> <#c> ."   http://localhost:3000/container1/

**Expected Result:**
`404` due to 2.4.3 of Solid Protocol Spec
> When a POST method request targets a resource without an existing representation, the server MUST respond with the 404 status code.

**Actual Result:**
```
HTTP/1.1 201 Created
location: http://localhost:3000/container1/c4db9502-4ca4-40ff-8250-94f72bb3101d/
```


Pull request:
fix: Prevent POST requests from creating intermediate containers
Fixes #476 

Patch:
diff --git a/src/storage/DataAccessorBasedStore.ts b/src/storage/DataAccessorBasedStore.ts
index b1eb8cfb4..baa0c7709 100644
--- a/src/storage/DataAccessorBasedStore.ts
+++ b/src/storage/DataAccessorBasedStore.ts
@@ -97,9 +97,9 @@ export class DataAccessorBasedStore implements ResourceStore {
     // Using the parent metadata as we can also use that later to check if the nested containers maybe need to be made
     const parentMetadata = await this.getSafeNormalizedMetadata(container);
 
-    // When a POST method request targets a non-container resource without an existing representation,
+    // When a POST method request targets a resource without an existing representation,
     // the server MUST respond with the 404 status code.
-    if (!parentMetadata && !isContainerIdentifier(container)) {
+    if (!parentMetadata) {
       throw new NotFoundHttpError();
     }
 
@@ -290,10 +290,10 @@ export class DataAccessorBasedStore implements ResourceStore {
    *
    * @param container - Identifier of the target container.
    * @param metadata - Metadata of the new resource.
-   * @param parentMetadata - Optional metadata of the parent container.
+   * @param parentMetadata - Metadata of the parent container.
    */
   protected createSafeUri(container: ResourceIdentifier, metadata: RepresentationMetadata,
-    parentMetadata?: RepresentationMetadata): ResourceIdentifier {
+    parentMetadata: RepresentationMetadata): ResourceIdentifier {
     // Get all values needed for naming the resource
     const isContainer = this.isNewContainer(metadata);
     const slug = metadata.get(HTTP.slug)?.value;
@@ -302,14 +302,12 @@ export class DataAccessorBasedStore implements ResourceStore {
     let newID: ResourceIdentifier = this.createURI(container, isContainer, slug);
 
     // Make sure we don't already have a resource with this exact name (or with differing trailing slash)
-    if (parentMetadata) {
-      const withSlash = ensureTrailingSlash(newID.path);
-      const withoutSlash = trimTrailingSlashes(newID.path);
-      const exists = parentMetadata.getAll(LDP.contains).some((term): boolean =>
-        term.value === withSlash || term.value === withoutSlash);
-      if (exists) {
-        newID = this.createURI(container, isContainer);
-      }
+    const withSlash = ensureTrailingSlash(newID.path);
+    const withoutSlash = trimTrailingSlashes(newID.path);
+    const exists = parentMetadata.getAll(LDP.contains).some((term): boolean =>
+      term.value === withSlash || term.value === withoutSlash);
+    if (exists) {
+      newID = this.createURI(container, isContainer);
     }
 
     return newID;


Test patch:
diff --git a/test/unit/storage/DataAccessorBasedStore.test.ts b/test/unit/storage/DataAccessorBasedStore.test.ts
index 5f3d2746f..f4848c313 100644
--- a/test/unit/storage/DataAccessorBasedStore.test.ts
+++ b/test/unit/storage/DataAccessorBasedStore.test.ts
@@ -137,8 +137,8 @@ describe('A DataAccessorBasedStore', (): void => {
       await expect(store.addResource(resourceID, representation)).rejects.toThrow(BadRequestHttpError);
     });
 
-    it('will 404 if the target does not exist and does not end in a slash.', async(): Promise<void> => {
-      const resourceID = { path: `${root}container` };
+    it('will 404 if the target does not exist.', async(): Promise<void> => {
+      const resourceID = { path: `${root}container/` };
       await expect(store.addResource(resourceID, representation)).rejects.toThrow(NotFoundHttpError);
     });
 
@@ -219,27 +219,6 @@ describe('A DataAccessorBasedStore', (): void => {
         path: expect.stringMatching(new RegExp(`^${root}[^/]+/$`, 'u')),
       });
     });
-
-    it('creates recursive containers when needed.', async(): Promise<void> => {
-      const resourceID = { path: `${root}a/b/` };
-      const result = await store.addResource(resourceID, representation);
-      expect(result).toEqual({
-        path: expect.stringMatching(new RegExp(`^${root}a/b/[^/]+$`, 'u')),
-      });
-      await expect(arrayifyStream(accessor.data[result.path].data)).resolves.toEqual([ resourceData ]);
-      expect(accessor.data[`${root}a/`].metadata.getAll(RDF.type).map((type): string => type.value))
-        .toContain(LDP.Container);
-      expect(accessor.data[`${root}a/b/`].metadata.getAll(RDF.type).map((type): string => type.value))
-        .toContain(LDP.Container);
-    });
-
-    it('errors when a recursive container overlaps with an existing resource.', async(): Promise<void> => {
-      const resourceID = { path: `${root}a/b/` };
-      accessor.data[`${root}a`] = representation;
-      await expect(store.addResource(resourceID, representation)).rejects.toThrow(
-        new ConflictHttpError(`Creating container ${root}a/ conflicts with an existing resource.`),
-      );
-    });
   });
 
   describe('setting a Representation', (): void => {