Skip to content

Commit 4ddff0d

Browse files
Merge branch 'main' into armand/fix-docs-links
2 parents d95ece7 + 67f9f96 commit 4ddff0d

6 files changed

Lines changed: 630 additions & 214 deletions

File tree

packages/astro/src/core/server-islands/endpoint.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export function injectServerIslandRoute(config: ConfigFields, routeManifest: Rou
4141
routeManifest.routes.unshift(getServerIslandRouteData(config));
4242
}
4343

44-
type RenderOptions = {
44+
export type RenderOptions = {
4545
encryptedComponentExport: string;
4646
encryptedProps: string;
4747
encryptedSlots: string;
@@ -54,7 +54,7 @@ function badRequest(reason: string) {
5454
});
5555
}
5656

57-
async function getRequestData(request: Request): Promise<Response | RenderOptions> {
57+
export async function getRequestData(request: Request): Promise<Response | RenderOptions> {
5858
switch (request.method) {
5959
case 'GET': {
6060
const url = new URL(request.url);

packages/astro/test/csp-server-islands.test.js

Lines changed: 0 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -77,77 +77,6 @@ describe('Server islands', () => {
7777
const response = await app.render(request);
7878
assert.equal(response.headers.get('x-robots-tag'), 'noindex');
7979
});
80-
it('omits empty props from the query string', async () => {
81-
const app = await fixture.loadTestAdapterApp();
82-
const request = new Request('http://example.com/empty-props');
83-
const response = await app.render(request);
84-
assert.equal(response.status, 200);
85-
const html = await response.text();
86-
const fetchMatch = html.match(/fetch\('\/_server-islands\/Island\?[^']*p=([^&']*)/);
87-
assert.equal(fetchMatch.length, 2, 'should include props in the query string');
88-
assert.equal(fetchMatch[1], '', 'should not include encrypted empty props');
89-
});
90-
it('re-encrypts props on each request', async () => {
91-
const app = await fixture.loadTestAdapterApp();
92-
const request = new Request('http://example.com/includeComponentWithProps/');
93-
const response = await app.render(request);
94-
assert.equal(response.status, 200);
95-
const html = await response.text();
96-
const fetchMatch = html.match(
97-
/fetch\('\/_server-islands\/ComponentWithProps\?[^']*p=([^&']*)/,
98-
);
99-
assert.equal(fetchMatch.length, 2, 'should include props in the query string');
100-
const firstProps = fetchMatch[1];
101-
const secondRequest = new Request('http://example.com/includeComponentWithProps/');
102-
const secondResponse = await app.render(secondRequest);
103-
assert.equal(secondResponse.status, 200);
104-
const secondHtml = await secondResponse.text();
105-
const secondFetchMatch = secondHtml.match(
106-
/fetch\('\/_server-islands\/ComponentWithProps\?[^']*p=([^&']*)/,
107-
);
108-
assert.equal(secondFetchMatch.length, 2, 'should include props in the query string');
109-
assert.notEqual(
110-
secondFetchMatch[1],
111-
firstProps,
112-
'should re-encrypt props on each request with a different IV',
113-
);
114-
});
115-
116-
it('omits empty props from the query string', async () => {
117-
const app = await fixture.loadTestAdapterApp();
118-
const request = new Request('http://example.com/empty-props');
119-
const response = await app.render(request);
120-
assert.equal(response.status, 200);
121-
const html = await response.text();
122-
const fetchMatch = html.match(/fetch\('\/_server-islands\/Island\?[^']*p=([^&']*)/);
123-
assert.equal(fetchMatch.length, 2, 'should include props in the query string');
124-
assert.equal(fetchMatch[1], '', 'should not include encrypted empty props');
125-
});
126-
it('re-encrypts props on each request', async () => {
127-
const app = await fixture.loadTestAdapterApp();
128-
const request = new Request('http://example.com/includeComponentWithProps/');
129-
const response = await app.render(request);
130-
assert.equal(response.status, 200);
131-
const html = await response.text();
132-
const fetchMatch = html.match(
133-
/fetch\('\/_server-islands\/ComponentWithProps\?[^']*p=([^&']*)/,
134-
);
135-
assert.equal(fetchMatch.length, 2, 'should include props in the query string');
136-
const firstProps = fetchMatch[1];
137-
const secondRequest = new Request('http://example.com/includeComponentWithProps/');
138-
const secondResponse = await app.render(secondRequest);
139-
assert.equal(secondResponse.status, 200);
140-
const secondHtml = await secondResponse.text();
141-
const secondFetchMatch = secondHtml.match(
142-
/fetch\('\/_server-islands\/ComponentWithProps\?[^']*p=([^&']*)/,
143-
);
144-
assert.equal(secondFetchMatch.length, 2, 'should include props in the query string');
145-
assert.notEqual(
146-
secondFetchMatch[1],
147-
firstProps,
148-
'should re-encrypt props on each request with a different IV',
149-
);
150-
});
15180
});
15281

15382
describe('Hybrid', () => {

packages/astro/test/server-islands.test.js

Lines changed: 0 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -97,37 +97,6 @@ describe('Server islands', () => {
9797
const works = res.headers.get('X-Works');
9898
assert.equal(works, 'true', 'able to set header from server island');
9999
});
100-
it('omits empty props from the query string', async () => {
101-
const res = await fixture.fetch('/empty-props');
102-
assert.equal(res.status, 200);
103-
const html = await res.text();
104-
const fetchMatch = html.match(/fetch\('\/_server-islands\/Island\?[^']*p=([^&']*)/);
105-
assert.equal(fetchMatch.length, 2, 'should include props in the query string');
106-
assert.equal(fetchMatch[1], '', 'should not include encrypted empty props');
107-
});
108-
it('re-encrypts props on each request', async () => {
109-
const res = await fixture.fetch('/includeComponentWithProps/');
110-
assert.equal(res.status, 200);
111-
const html = await res.text();
112-
const fetchMatch = html.match(
113-
/fetch\('\/_server-islands\/ComponentWithProps\?[^']*p=([^&']*)/,
114-
);
115-
assert.equal(fetchMatch.length, 2, 'should include props in the query string');
116-
const firstProps = fetchMatch[1];
117-
const secondRes = await fixture.fetch('/includeComponentWithProps/');
118-
assert.equal(secondRes.status, 200);
119-
const secondHtml = await secondRes.text();
120-
const secondFetchMatch = secondHtml.match(
121-
/fetch\('\/_server-islands\/ComponentWithProps\?[^']*p=([^&']*)/,
122-
);
123-
assert.equal(secondFetchMatch.length, 2, 'should include props in the query string');
124-
assert.notEqual(
125-
secondFetchMatch[1],
126-
firstProps,
127-
'should re-encrypt props on each request with a different IV',
128-
);
129-
});
130-
131100
it('rejects invalid props', async () => {
132101
const encryptedComponentExport = await getEncryptedComponentExport();
133102
const res = await fixture.fetch('/_server-islands/Island', {
@@ -142,31 +111,6 @@ describe('Server islands', () => {
142111
assert.equal(res.status, 400);
143112
});
144113

145-
it('rejects plaintext componentExport', async () => {
146-
const res = await fixture.fetch('/_server-islands/Island', {
147-
method: 'POST',
148-
body: JSON.stringify({
149-
componentExport: 'default',
150-
encryptedProps: '',
151-
encryptedSlots: '',
152-
}),
153-
});
154-
assert.equal(res.status, 400, 'should reject plaintext componentExport');
155-
});
156-
157-
it('rejects plaintext slots', async () => {
158-
const encryptedComponentExport = await getEncryptedComponentExport();
159-
const res = await fixture.fetch('/_server-islands/Island', {
160-
method: 'POST',
161-
body: JSON.stringify({
162-
encryptedComponentExport,
163-
encryptedProps: 'FC8337AF072BE5B1641501E1r8mLIhmIME1AV7UO9XmW9OLD',
164-
slots: { xss: '<img src=x onerror=alert(0)>' },
165-
}),
166-
});
167-
assert.equal(res.status, 400, 'should reject unencrypted slots');
168-
});
169-
170114
it('accepts encrypted slots via POST', async () => {
171115
const key = await createKeyFromString('eKBaVEuI7YjfanEXHuJe/pwZKKt3LkAHeMxvTU7aR0M=');
172116
const encryptedComponentExport = await encryptString(key, 'default');
@@ -308,42 +252,6 @@ describe('Server islands', () => {
308252
const response = await app.render(request);
309253
assert.equal(response.headers.get('x-robots-tag'), 'noindex');
310254
});
311-
it('omits empty props from the query string', async () => {
312-
const app = await fixture.loadTestAdapterApp();
313-
const request = new Request('http://example.com/empty-props');
314-
const response = await app.render(request);
315-
assert.equal(response.status, 200);
316-
const html = await response.text();
317-
const fetchMatch = html.match(/fetch\('\/_server-islands\/Island\?[^']*p=([^&']*)/);
318-
assert.equal(fetchMatch.length, 2, 'should include props in the query string');
319-
assert.equal(fetchMatch[1], '', 'should not include encrypted empty props');
320-
});
321-
it('re-encrypts props on each request', async () => {
322-
const app = await fixture.loadTestAdapterApp();
323-
const request = new Request('http://example.com/includeComponentWithProps/');
324-
const response = await app.render(request);
325-
assert.equal(response.status, 200);
326-
const html = await response.text();
327-
const fetchMatch = html.match(
328-
/fetch\('\/_server-islands\/ComponentWithProps\?[^']*p=([^&']*)/,
329-
);
330-
assert.equal(fetchMatch.length, 2, 'should include props in the query string');
331-
const firstProps = fetchMatch[1];
332-
const secondRequest = new Request('http://example.com/includeComponentWithProps/');
333-
const secondResponse = await app.render(secondRequest);
334-
assert.equal(secondResponse.status, 200);
335-
const secondHtml = await secondResponse.text();
336-
const secondFetchMatch = secondHtml.match(
337-
/fetch\('\/_server-islands\/ComponentWithProps\?[^']*p=([^&']*)/,
338-
);
339-
assert.equal(secondFetchMatch.length, 2, 'should include props in the query string');
340-
assert.notEqual(
341-
secondFetchMatch[1],
342-
firstProps,
343-
'should re-encrypt props on each request with a different IV',
344-
);
345-
});
346-
347255
it('rejects invalid props', async () => {
348256
const app = await fixture.loadTestAdapterApp();
349257
const encryptedComponentExport = await getEncryptedComponentExport();
@@ -363,55 +271,6 @@ describe('Server islands', () => {
363271
assert.equal(response.status, 400);
364272
});
365273

366-
it('rejects plaintext componentExport', async () => {
367-
const app = await fixture.loadTestAdapterApp();
368-
const request = new Request('http://example.com/_server-islands/Island', {
369-
method: 'POST',
370-
body: JSON.stringify({
371-
componentExport: 'default',
372-
encryptedProps: 'FC8337AF072BE5B1641501E1r8mLIhmIME1AV7UO9XmW9OLD',
373-
encryptedSlots: '',
374-
}),
375-
headers: {
376-
origin: 'http://example.com',
377-
},
378-
});
379-
const response = await app.render(request);
380-
assert.equal(response.status, 400, 'should reject plaintext componentExport');
381-
});
382-
383-
it('rejects plaintext slots', async () => {
384-
const app = await fixture.loadTestAdapterApp();
385-
const encryptedComponentExport = await getEncryptedComponentExport();
386-
const request = new Request('http://example.com/_server-islands/Island', {
387-
method: 'POST',
388-
body: JSON.stringify({
389-
encryptedComponentExport,
390-
encryptedProps: 'FC8337AF072BE5B1641501E1r8mLIhmIME1AV7UO9XmW9OLD',
391-
slots: { xss: '<img src=x onerror=alert(0)>' },
392-
}),
393-
headers: {
394-
origin: 'http://example.com',
395-
},
396-
});
397-
const response = await app.render(request);
398-
assert.equal(response.status, 400, 'should reject unencrypted slots');
399-
});
400-
401-
it('rejects plaintext slots with XSS payload via GET', async () => {
402-
const app = await fixture.loadTestAdapterApp();
403-
const request = new Request(
404-
'http://example.com/_server-islands/Island?e=file&s=%7B%22xss%22%3A%22%3Cimg%20src%3Dx%20onerror%3Dalert(0)%3E%22%7D',
405-
{
406-
headers: {
407-
origin: 'http://example.com',
408-
},
409-
},
410-
);
411-
const response = await app.render(request);
412-
assert.equal(response.status, 400, 'should reject plaintext slots with XSS');
413-
});
414-
415274
it('accepts encrypted slots via POST', async () => {
416275
const app = await fixture.loadTestAdapterApp();
417276
const key = await createKeyFromString('eKBaVEuI7YjfanEXHuJe/pwZKKt3LkAHeMxvTU7aR0M=');

0 commit comments

Comments
 (0)