javascript-solid-server 0.0.180 → 0.0.181
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/handlers/resource.js +19 -2
- package/test/conneg.test.js +82 -0
package/package.json
CHANGED
package/src/handlers/resource.js
CHANGED
|
@@ -28,6 +28,14 @@ const LIVE_RELOAD_SCRIPT = `<script>(function(){var ws=new WebSocket((location.p
|
|
|
28
28
|
// where a cached data variant was served on top-level navigation (#315).
|
|
29
29
|
const RDF_CACHE_CONTROL = 'private, no-cache, must-revalidate';
|
|
30
30
|
|
|
31
|
+
// Detects when the request's Accept header explicitly names a JSON
|
|
32
|
+
// media type. Used by the container/index.html branches of GET and HEAD
|
|
33
|
+
// to decide whether to surface the embedded JSON-LD data island —
|
|
34
|
+
// without this guard, selectContentType's `*/*` arm would divert plain
|
|
35
|
+
// browser requests into the RDF branch (#409). Hoisted so GET and HEAD
|
|
36
|
+
// can't drift apart silently.
|
|
37
|
+
const EXPLICIT_JSON_RE = /\b(application\/ld\+json|application\/json)\b/i;
|
|
38
|
+
|
|
31
39
|
/**
|
|
32
40
|
* Inject live reload script into HTML content
|
|
33
41
|
*/
|
|
@@ -161,7 +169,16 @@ export async function handleGet(request, reply) {
|
|
|
161
169
|
const wantsTurtle = negotiated === RDF_TYPES.TURTLE
|
|
162
170
|
|| negotiated === RDF_TYPES.N3
|
|
163
171
|
|| negotiated === 'application/n-triples';
|
|
164
|
-
|
|
172
|
+
// Only treat as JSON-LD when Accept *explicitly* asks for JSON.
|
|
173
|
+
// selectContentType doesn't recognize text/html or
|
|
174
|
+
// application/xhtml+xml, so for a browser Accept like
|
|
175
|
+
// `text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8`
|
|
176
|
+
// it walks past those unsupported types and lands on `*/*`, which
|
|
177
|
+
// returns JSON-LD — diverting plain browser GETs into the RDF
|
|
178
|
+
// branch and serving the embedded data island instead of the
|
|
179
|
+
// index.html body. Mirrors the HEAD-handler logic below (#409).
|
|
180
|
+
const explicitJson = EXPLICIT_JSON_RE.test(acceptHeader);
|
|
181
|
+
const wantsJsonLd = negotiated === RDF_TYPES.JSON_LD && explicitJson;
|
|
165
182
|
|
|
166
183
|
if (wantsTurtle || wantsJsonLd) {
|
|
167
184
|
// Extract JSON-LD from HTML data island
|
|
@@ -601,7 +618,7 @@ export async function handleHead(request, reply) {
|
|
|
601
618
|
// For an index.html container, only override to JSON-LD if the
|
|
602
619
|
// Accept header explicitly asked for JSON; otherwise fall back
|
|
603
620
|
// to text/html so HEAD matches the index.html that GET serves.
|
|
604
|
-
const explicitJson =
|
|
621
|
+
const explicitJson = EXPLICIT_JSON_RE.test(acceptHeader);
|
|
605
622
|
contentType = (indexExists && !explicitJson) ? 'text/html' : 'application/ld+json';
|
|
606
623
|
} else {
|
|
607
624
|
contentType = indexExists ? 'text/html' : 'application/ld+json';
|
package/test/conneg.test.js
CHANGED
|
@@ -677,4 +677,86 @@ describe('Content Negotiation — q-weights and HEAD/GET parity (#325)', () => {
|
|
|
677
677
|
assert.strictEqual(ct(authed), ct(anon));
|
|
678
678
|
});
|
|
679
679
|
});
|
|
680
|
+
|
|
681
|
+
describe('container with index.html — browser Accept (#409)', () => {
|
|
682
|
+
// Regression: a container that has an index.html with a *valid*
|
|
683
|
+
// <script type="application/ld+json"> data island used to return that
|
|
684
|
+
// data island as application/ld+json to plain browser GETs.
|
|
685
|
+
// selectContentType iterates the Accept list — for a browser sending
|
|
686
|
+
// `Accept: text/html, ..., */*;q=0.8` it sees text/html (and other
|
|
687
|
+
// HTML-ish types) but doesn't recognize any of them, then hits the
|
|
688
|
+
// `*/*` arm and returns JSON-LD, so the user-visible page silently
|
|
689
|
+
// flipped to JSON.
|
|
690
|
+
const HTML_WITH_JSONLD = '<!DOCTYPE html><html><head><title>Home</title>'
|
|
691
|
+
+ '<script type="application/ld+json">'
|
|
692
|
+
+ JSON.stringify({ '@context': { foaf: 'http://xmlns.com/foaf/0.1/' }, '@id': '#me', 'foaf:name': 'Carol' })
|
|
693
|
+
+ '</script></head><body><h1>hello</h1></body></html>';
|
|
694
|
+
|
|
695
|
+
before(async () => {
|
|
696
|
+
// Container with an index.html containing a parseable JSON-LD island.
|
|
697
|
+
await request('/qwtest/public/page/', { method: 'PUT', auth: 'qwtest' });
|
|
698
|
+
await request('/qwtest/public/page/index.html', {
|
|
699
|
+
method: 'PUT',
|
|
700
|
+
headers: { 'Content-Type': 'text/html' },
|
|
701
|
+
body: HTML_WITH_JSONLD,
|
|
702
|
+
auth: 'qwtest'
|
|
703
|
+
});
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
it('browser Accept (text/html with */*;q=0.8) → text/html, not JSON-LD', async () => {
|
|
707
|
+
const res = await request('/qwtest/public/page/', {
|
|
708
|
+
headers: { Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' }
|
|
709
|
+
});
|
|
710
|
+
assertStatus(res, 200);
|
|
711
|
+
assert.strictEqual(ct(res), 'text/html',
|
|
712
|
+
'browser GET on a container with index.html must return the HTML body, not the embedded data island');
|
|
713
|
+
const body = await res.text();
|
|
714
|
+
assert.ok(body.includes('<h1>hello</h1>'), 'response should be the index.html body');
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
it('plain Accept: text/html → text/html', async () => {
|
|
718
|
+
const res = await request('/qwtest/public/page/', { headers: { Accept: 'text/html' } });
|
|
719
|
+
assertStatus(res, 200);
|
|
720
|
+
assert.strictEqual(ct(res), 'text/html');
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
it('explicit Accept: application/ld+json → JSON-LD from data island still works', async () => {
|
|
724
|
+
const res = await request('/qwtest/public/page/', {
|
|
725
|
+
headers: { Accept: 'application/ld+json' }
|
|
726
|
+
});
|
|
727
|
+
assertStatus(res, 200);
|
|
728
|
+
assert.strictEqual(ct(res), 'application/ld+json');
|
|
729
|
+
const body = await res.json();
|
|
730
|
+
assert.strictEqual(body['foaf:name'], 'Carol',
|
|
731
|
+
'should still extract the data island when JSON-LD is explicitly asked for');
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
it('explicit Accept: text/turtle → Turtle from data island still works', async () => {
|
|
735
|
+
const res = await request('/qwtest/public/page/', { headers: { Accept: 'text/turtle' } });
|
|
736
|
+
assertStatus(res, 200);
|
|
737
|
+
assert.strictEqual(ct(res), 'text/turtle');
|
|
738
|
+
const body = await res.text();
|
|
739
|
+
assert.ok(body.includes('Carol'), 'turtle output should contain the data island content');
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
// The original bug was specifically GET vs HEAD divergence — the HEAD
|
|
743
|
+
// handler already had the explicitJson guard, GET didn't. Pin the
|
|
744
|
+
// parity here so any future drift between the two branches fails.
|
|
745
|
+
const parityCases = [
|
|
746
|
+
['browser Accept', { Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' }],
|
|
747
|
+
['Accept: text/html', { Accept: 'text/html' }],
|
|
748
|
+
['Accept: application/ld+json', { Accept: 'application/ld+json' }],
|
|
749
|
+
['Accept: text/turtle', { Accept: 'text/turtle' }]
|
|
750
|
+
];
|
|
751
|
+
for (const [label, headers] of parityCases) {
|
|
752
|
+
it(`HEAD === GET content-type — ${label}`, async () => {
|
|
753
|
+
const get = await request('/qwtest/public/page/', { headers });
|
|
754
|
+
const head = await request('/qwtest/public/page/', { method: 'HEAD', headers });
|
|
755
|
+
assert.strictEqual(get.status, 200);
|
|
756
|
+
assert.strictEqual(head.status, 200);
|
|
757
|
+
assert.strictEqual(ct(head), ct(get),
|
|
758
|
+
`HEAD ct (${ct(head)}) must equal GET ct (${ct(get)}) for ${label}`);
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
});
|
|
680
762
|
});
|