Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: prevent overwriting of CloudFlare _headers file #8693

Merged
merged 4 commits into from
Jan 27, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Revert "add append flag to _headers file writing"
This reverts commit 4e27ca4.
  • Loading branch information
eltigerchino committed Jan 24, 2023
commit 8d89773799420c124c16051706d40f7cafda3fd1
5 changes: 0 additions & 5 deletions .changeset/fluffy-wombats-fetch.md

This file was deleted.

180 changes: 67 additions & 113 deletions packages/adapter-cloudflare/src/worker.js
Original file line number Diff line number Diff line change
@@ -1,119 +1,73 @@
import { writeFileSync } from 'fs';
import { posix } from 'path';
import { fileURLToPath } from 'url';
import * as esbuild from 'esbuild';

/** @type {import('.').default} */
export default function () {
return {
name: '@sveltejs/adapter-cloudflare',
async adapt(builder) {
const files = fileURLToPath(new URL('./files', import.meta.url).href);
const dest = builder.getBuildDirectory('cloudflare');
const tmp = builder.getBuildDirectory('cloudflare-tmp');

builder.rimraf(dest);
builder.rimraf(tmp);
builder.mkdirp(tmp);

const dest_dir = `${dest}${builder.config.kit.paths.base}`;
const written_files = builder.writeClient(dest_dir);
builder.writePrerendered(dest_dir);

const relativePath = posix.relative(tmp, builder.getServerDirectory());

writeFileSync(
`${tmp}/manifest.js`,
`export const manifest = ${builder.generateManifest({ relativePath })};\n\n` +
`export const prerendered = new Set(${JSON.stringify(builder.prerendered.paths)});\n`
);

writeFileSync(
`${dest}/_routes.json`,
JSON.stringify(get_routes_json(builder, written_files))
);

writeFileSync(`${dest}/_headers`, generate_headers(builder.config.kit.appDir), { flag: 'a' });

builder.copy(`${files}/worker.js`, `${tmp}/_worker.js`, {
replace: {
SERVER: `${relativePath}/index.js`,
MANIFEST: './manifest.js'
import { Server } from 'SERVER';
import { manifest, prerendered } from 'MANIFEST';
import * as Cache from 'worktop/cfw.cache';

const server = new Server(manifest);

const app_path = `/${manifest.appPath}/`;

/** @type {import('worktop/cfw').Module.Worker<{ ASSETS: import('worktop/cfw.durable').Durable.Object }>} */
const worker = {
async fetch(req, env, context) {
await server.init({ env });
// skip cache if "cache-control: no-cache" in request
let pragma = req.headers.get('cache-control') || '';
let res = !pragma.includes('no-cache') && (await Cache.lookup(req));
if (res) return res;

let { pathname } = new URL(req.url);

// static assets
if (pathname.startsWith(app_path)) {
res = await env.ASSETS.fetch(req);
if (!res.ok) return res;

const cache_control = pathname.startsWith(app_path + 'immutable/')
? 'public, immutable, max-age=31536000'
: 'no-cache';

res = new Response(res.body, {
headers: {
// include original headers, minus cache-control which
// is overridden, and etag which is no longer useful
'cache-control': cache_control,
'content-type': res.headers.get('content-type'),
'x-robots-tag': 'noindex'
}
});

await esbuild.build({
platform: 'browser',
conditions: ['worker', 'browser'],
sourcemap: 'linked',
target: 'es2020',
entryPoints: [`${tmp}/_worker.js`],
outfile: `${dest}/_worker.js`,
allowOverwrite: true,
format: 'esm',
bundle: true
});
} else {
// prerendered pages and index.html files
pathname = pathname.replace(/\/$/, '') || '/';

let file = pathname.substring(1);

try {
file = decodeURIComponent(file);
} catch (err) {
// ignore
}

if (
manifest.assets.has(file) ||
manifest.assets.has(file + '/index.html') ||
prerendered.has(pathname)
) {
res = await env.ASSETS.fetch(req);
} else {
// dynamically-generated pages
res = await server.respond(req, {
platform: { env, context, caches },
getClientAddress() {
return req.headers.get('cf-connecting-ip');
}
});
}
}
};
}

/**
* @param {import('@sveltejs/kit').Builder} builder
* @param {string[]} assets
* @returns {import('.').RoutesJSONSpec}
*/
function get_routes_json(builder, assets) {
/**
* The list of routes that will _not_ invoke functions (which cost money).
* This is done on a best-effort basis, as there is a limit of 100 rules
*/
const exclude = [
`/${builder.config.kit.appDir}/*`,
...assets
.filter((file) => !file.startsWith(`${builder.config.kit.appDir}/`))
.map((file) => `/${file}`)
];

const MAX_EXCLUSIONS = 99; // 100 minus existing `include` rules
let excess;

if (exclude.length > MAX_EXCLUSIONS) {
excess = 'static assets';

if (builder.prerendered.paths.length > 0) {
excess += ' or prerendered routes';
}
} else if (exclude.length + builder.prerendered.paths.length > MAX_EXCLUSIONS) {
excess = 'prerendered routes';
}

for (const path of builder.prerendered.paths) {
if (!builder.prerendered.redirects.has(path)) {
exclude.push(path);
}
// Writes to Cache only if allowed & specified
pragma = res.headers.get('cache-control');
return pragma && res.ok ? Cache.save(req, res, context) : res;
}
};

if (excess) {
const message = `Static file count exceeds _routes.json limits (see https://developers.cloudflare.com/pages/platform/functions/routing/#limits). Accessing some ${excess} will cause function invocations.`;
builder.log.warn(message);
exclude.length = 99;
}

return {
version: 1,
description: 'Generated by @sveltejs/adapter-cloudflare',
include: ['/*'],
exclude
};
}

/** @param {string} app_dir */
function generate_headers(app_dir) {
return `
# === START AUTOGENERATED SVELTE IMMUTABLE HEADERS ===
/${app_dir}/immutable/*
Cache-Control: public, immutable, max-age=31536000
X-Robots-Tag: noindex
# === END AUTOGENERATED SVELTE IMMUTABLE HEADERS ===
`.trimEnd();
}
export default worker;