Repository: solid/community-server (ts)

Issue:
Server currently gives UnsupportedHttpError: Only string or binary results are supported
```
$ npm start

> @solid/community-server@0.0.1 start /Users/ruben/Documents/UGent/Solid/solid-community-server
> node ./bin/server.js -p 3000

Running at http://localhost:3000/
UnsupportedHttpError: Only string or binary results are supported.
UnsupportedHttpError: Only string or binary results are supported.
    at SimpleResponseWriter.canHandle (/Users/ruben/Documents/UGent/Solid/solid-community-server/src/ldp/http/SimpleResponseWriter.js:17:23)
    at SimpleResponseWriter.handleSafe (/Users/ruben/Documents/UGent/Solid/solid-community-server/src/util/AsyncHandler.js:17:20)
    at AuthenticatedLdpHandler.handle (/Users/ruben/Documents/UGent/Solid/solid-community-server/src/ldp/AuthenticatedLdpHandler.js:50:35)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
    at async /Users/ruben/Documents/UGent/Solid/solid-community-server/src/server/ExpressHttpServer.js:23:17
```

The above error occurs when sending a `curl localhost:3000`.

There are two issues here:
- the error itself
- the fact that the error is returned with `Content-Type: text/html`, opening up the possibility for injecting malicious strings into the body; should be `text/plain`.

Pull request description:
Use text/plain for errors and return text/turtle by default
Resolves #33 

Patch:
diff --git a/src/ldp/http/SimpleResponseWriter.ts b/src/ldp/http/SimpleResponseWriter.ts
index ba2187cba..83b5da2c4 100644
--- a/src/ldp/http/SimpleResponseWriter.ts
+++ b/src/ldp/http/SimpleResponseWriter.ts
@@ -23,9 +23,8 @@ export class SimpleResponseWriter extends ResponseWriter {
     if (input.description) {
       input.response.setHeader('location', input.description.identifier.path);
       if (input.description.body) {
-        if (input.description.body.metadata.contentType) {
-          input.response.setHeader('content-type', input.description.body.metadata.contentType);
-        }
+        const contentType = input.description.body.metadata.contentType || 'text/plain';
+        input.response.setHeader('content-type', contentType);
         input.description.body.data.pipe(input.response);
       }
 
@@ -40,6 +39,7 @@ export class SimpleResponseWriter extends ResponseWriter {
       if (input.error instanceof HttpError) {
         code = input.error.statusCode;
       }
+      input.response.setHeader('content-type', 'text/plain');
       input.response.writeHead(code);
       input.response.end(`${input.error.name}: ${input.error.message}\n${input.error.stack}`);
     }
diff --git a/src/server/ExpressHttpServer.ts b/src/server/ExpressHttpServer.ts
index 996c58e8c..d7167b7ba 100644
--- a/src/server/ExpressHttpServer.ts
+++ b/src/server/ExpressHttpServer.ts
@@ -26,7 +26,7 @@ export class ExpressHttpServer {
       } catch (error) {
         const errMsg = `${error.name}: ${error.message}\n${error.stack}`;
         process.stderr.write(errMsg);
-        response.status(500).send(errMsg);
+        response.status(500).contentType('text/plain').send(errMsg);
       }
     });
     return app.listen(port);
diff --git a/src/storage/SimpleResourceStore.ts b/src/storage/SimpleResourceStore.ts
index f29228530..a57ad95f7 100644
--- a/src/storage/SimpleResourceStore.ts
+++ b/src/storage/SimpleResourceStore.ts
@@ -122,16 +122,21 @@ export class SimpleResourceStore implements ResourceStore {
    * Converts an array of Quads to a Representation.
    * If preferences.type contains 'text/turtle' the result will be a stream of turtle strings,
    * otherwise a stream of Quads.
+   *
+   * Note that in general this should be done by resource store specifically made for converting to turtle,
+   * this is just here to make this simple resource store work.
+   *
    * @param data - Quads to transform.
    * @param preferences - Requested preferences.
    *
    * @returns The resulting Representation.
    */
   private generateRepresentation(data: Quad[], preferences: RepresentationPreferences): Representation {
-    if (preferences.type && preferences.type.some((preference): boolean => preference.value.includes('text/turtle'))) {
-      return this.generateBinaryRepresentation(data);
+    // Always return turtle unless explicitly asked for quads
+    if (preferences.type?.some((preference): boolean => preference.value.includes('internal/quads'))) {
+      return this.generateQuadRepresentation(data);
     }
-    return this.generateQuadRepresentation(data);
+    return this.generateBinaryRepresentation(data);
   }
 
   /**


Test patch:
diff --git a/test/unit/storage/SimpleResourceStore.test.ts b/test/unit/storage/SimpleResourceStore.test.ts
index e7ac21852..53f1c5943 100644
--- a/test/unit/storage/SimpleResourceStore.test.ts
+++ b/test/unit/storage/SimpleResourceStore.test.ts
@@ -47,7 +47,7 @@ describe('A SimpleResourceStore', (): void => {
   it('can write and read data.', async(): Promise<void> => {
     const identifier = await store.addResource({ path: base }, representation);
     expect(identifier.path.startsWith(base)).toBeTruthy();
-    const result = await store.getRepresentation(identifier, {});
+    const result = await store.getRepresentation(identifier, { type: [{ value: 'internal/quads', weight: 1 }]});
     expect(result).toEqual({
       dataType: 'quad',
       data: expect.any(Readable),
@@ -84,9 +84,27 @@ describe('A SimpleResourceStore', (): void => {
     );
   });
 
+  it('returns turtle data if no preference was set.', async(): Promise<void> => {
+    const identifier = await store.addResource({ path: base }, representation);
+    expect(identifier.path.startsWith(base)).toBeTruthy();
+    const result = await store.getRepresentation(identifier, { });
+    expect(result).toEqual({
+      dataType: 'binary',
+      data: expect.any(Readable),
+      metadata: {
+        profiles: [],
+        raw: [],
+        contentType: 'text/turtle',
+      },
+    });
+    await expect(arrayifyStream(result.data)).resolves.toContain(
+      `<${quad.subject.value}> <${quad.predicate.value}> <${quad.object.value}>`,
+    );
+  });
+
   it('can set data.', async(): Promise<void> => {
     await store.setRepresentation({ path: base }, representation);
-    const result = await store.getRepresentation({ path: base }, {});
+    const result = await store.getRepresentation({ path: base }, { type: [{ value: 'internal/quads', weight: 1 }]});
     expect(result).toEqual({
       dataType: 'quad',
       data: expect.any(Readable),