Skip to content

Conneg: .meta and .acl files are not recognized as RDF, break Turtle-native clients #294

Description

@melvincarvalho

Symptom

A Turtle-native Solid client (e.g. umai) fetching `//.meta` from a JSS pod (conneg enabled) gets JSON-LD back instead of Turtle, and errors with `MalformedSolidDocumentError: Malformed Turtle document ... Expected entity but got { on line 2`.

Reproduced today against ewingson's `test.solidweb.app` (JSS 0.0.144, `conneg: true`).

Root cause

`getContentType(filePath)` in `src/utils/url.js:216` maps extensions to MIME types. The map is extension-based and has no entry for `.meta` or `.acl`:

```js
const types = {
'.jsonld': 'application/ld+json',
'.json': 'application/json',
'.ttl': 'text/turtle',
// ... no '.meta' or '.acl'
};
return types[ext] || 'application/octet-stream';
```

`path.extname('/a/.meta')` returns `''` (leading-dot files have no extension per node:path), so the fallback hits. `.meta` and `.acl` both end up tagged `application/octet-stream`.

Downstream: `handleGet`'s conneg branch is gated on `isRdfContentType(storedContentType)` (`src/handlers/resource.js:411`). `application/octet-stream` isn't an RDF type, so conneg never kicks in for these files — they're served verbatim as stored, regardless of `Accept`.

Mirror bug in .acl

Same issue applies to `.acl`. It hasn't shown up visibly because:

  1. JSS's own `parseAcl()` (`src/wac/parser.js:38-49`) is forgiving — tries JSON first, falls back to Turtle. So server-side WAC checks work on either format.
  2. Most clients don't GET `.acl` via HTTP — they read `wac-allow` on the protected resource. Only ACL-editing tools fetch `.acl`, and the few that do mostly accept JSON-LD.

But the latent bug is real: JSS writes `.acl` as JSON-LD via `serializeAcl()` → `JSON.stringify()`. A strict-Turtle ACL editor pointed at any existing JSS pod today would hit the exact same parser error.

Fix

Two-line change to `getContentType`:

```diff
const types = {
'.jsonld': 'application/ld+json',
'.json': 'application/json',
...

  • '.meta': 'application/ld+json', // Solid container metadata
  • '.acl': 'application/ld+json', // WAC access control
    };
    ```

We tag them as `application/ld+json` (not `text/turtle`) because:

  1. JSS's stance is JSON-LD native (see createPodStructure should write .jsonld (not .ttl) for prefs and TypeIndexes #282 / Use .jsonld for all generated pod files (#282) #283). That's the format we actually write these files in (`serializeAcl()`, `createPodStructure()`).
  2. Conneg handles the translation for Turtle-native clients automatically — `handleGet` already has a working `fromJsonLd(jsonLd, 'text/turtle', ...)` path for JSON-LD-stored files.
  3. The PUT side already handles the inverse: `handlePut:639` converts `text/turtle` bodies to JSON-LD before storing when `--conneg` is enabled.

So the round-trip lands for both client families with no further code change:

Client request Today After fix
umai: `PUT text/turtle .meta` Stored as Turtle bytes (silent) Converted to JSON-LD, stored
umai: `GET .meta Accept: text/turtle` Gets JSON-LD, parser errors Gets Turtle, parser happy
pilot: `GET .meta Accept: application/ld+json` Served raw (happens to be JSON-LD) Served raw (explicit)

Back-compat

Existing pods have JSON-LD `.acl` files on disk (written by `createPodStructure` via `serializeAcl`). Zero migration: the fix recognises them, conneg reads them correctly, Turtle clients start working immediately.

Scope

Two lines. Tests around `conneg.test.js` / `ldp.test.js` can add a case per file type (PUT Turtle, GET Turtle, both succeed; PUT JSON-LD, GET Turtle, convert) — straightforward to bolt onto existing fixtures.

Happy to PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions