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
7 changes: 4 additions & 3 deletions controllers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,11 @@ const URI_DOUBLED_FILTER_KEYS = new Set(["__rerum.generatedBy", "creator"])
const TARGET_KEYS = ["target", "target.@id", "target.id", "target.source", "target.source.@id", "target.source.id"]

/**
* Identity, system, and processing properties an Annotation body must never overwrite when its
* assertions are merged onto an entity.
* Identity, classification, system, and processing properties an Annotation body must never overwrite when its
* assertions are merged onto an entity. The entity stays the authority on its own '@context' and
* on the class it declares under 'type' or '@type'.
*/
const PROTECTED_EXPANSION_KEYS = new Set(["@id", "id", "_id", "__rerum", "__deleted", "__proto__", "@context"])
const PROTECTED_EXPANSION_KEYS = new Set(["@id", "id", "_id", "__rerum", "__deleted", "__proto__", "@context", "type", "@type"])

/**
* Escape the RegExp metacharacters in a literal so it can be embedded in a pattern and match only
Expand Down
26 changes: 12 additions & 14 deletions public/API.html
Original file line number Diff line number Diff line change
Expand Up @@ -1714,23 +1714,21 @@ <h3 id="entity-expansion">Entity Expansion</h3>
record said and what each Annotation added.
</p>
<p>
A record's identity, system, and processing properties are never overwritten by an Annotation. An
Annotation body asserting <code class="language-plaintext highlighter-rouge">@id</code>,
A record's identity, classification, system, and processing properties
are never overwritten by an Annotation. An Annotation body asserting
<code class="language-plaintext highlighter-rouge">@id</code>,
<code class="language-plaintext highlighter-rouge">id</code>,
<code class="language-plaintext highlighter-rouge">_id</code>,
<code class="language-plaintext highlighter-rouge">@context</code>,
<code class="language-plaintext highlighter-rouge">@type</code>,
<code class="language-plaintext highlighter-rouge">type</code>,
<code class="language-plaintext highlighter-rouge">__rerum</code>,
<code class="language-plaintext highlighter-rouge">__deleted</code>,
<code class="language-plaintext highlighter-rouge">__proto__</code>, or
<code class="language-plaintext highlighter-rouge">@context</code> contributes nothing. The expanded record
answers to the same URI as the record you asked for, and reads under the same
<code class="language-plaintext highlighter-rouge">@context</code>.
</p>
<p>
Linked Data keywords that describe the record rather than identify it are not held back. An Annotation
asserting <code class="language-plaintext highlighter-rouge">type</code> or
<code class="language-plaintext highlighter-rouge">@type</code>
contributes it like any other property, which collects the record's own value and the asserted one
into an Array.
<code class="language-plaintext highlighter-rouge">__deleted</code>, or
<code class="language-plaintext highlighter-rouge">__proto__</code>
contributes nothing. The expanded record answers to the same URI as the
record you asked for, and reads under the same
<code class="language-plaintext highlighter-rouge">@context</code>
as the same class of thing it declared itself to be.
</p>
<p class="alert">
A deleted record is never expanded. Whatever it used to assert is inside its
Expand Down
4 changes: 4 additions & 0 deletions routes/__tests__/id.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ describe('GET /id/:id/expanded', () => {
anno({ body: { __rerum: { evil: true } } }),
anno({ body: { __deleted: { time: "2025-01-01T00:00:00.000" } } }),
anno({ body: { "@context": "https://evil.example.org/context.json" } }),
anno({ body: { "@type": "Hijacked" } }),
anno({ body: { type: "Hijacked" } }),
// An object literal with a __proto__ key sets the prototype instead of creating an own
// property. JSON.parse creates the own property, which is what a MongoDB document has.
anno({ body: JSON.parse('{"__proto__":{"polluted":"yes"}}') })
Expand All @@ -291,6 +293,8 @@ describe('GET /id/:id/expanded', () => {
assert.strictEqual(response.body.__rerum.generatedBy, MOCK_AGENT)
assert.strictEqual(response.body.__deleted, undefined)
assert.strictEqual(response.body["@context"], "http://www.loc.gov/mods")
assert.strictEqual(response.body["@type"], "named-gloss", 'the record keeps the class it declared')
assert.strictEqual(response.body.type, undefined, 'an Annotation cannot add the other spelling either')
assert.strictEqual(Object.hasOwn(response.body, '__proto__'), false)
assert.strictEqual({}.polluted, undefined, 'Object.prototype must not be polluted')
assert.strictEqual(response.headers['annotations-merged'], '0')
Expand Down
Loading