From 93ce803c153e454ccc3b889d8e53a5a2eb879bf1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 18 Nov 2021 11:58:49 -0500 Subject: [PATCH 1/2] follow redirects when prerendering --- .changeset/angry-tigers-prove.md | 5 +++ packages/kit/src/core/adapt/prerender.js | 8 ++++- .../kit/src/runtime/server/page/load_node.js | 32 +------------------ packages/kit/src/utils/url.js | 30 +++++++++++++++++ .../load_node.spec.js => utils/url.spec.js} | 2 +- 5 files changed, 44 insertions(+), 33 deletions(-) create mode 100644 .changeset/angry-tigers-prove.md create mode 100644 packages/kit/src/utils/url.js rename packages/kit/src/{runtime/server/page/load_node.spec.js => utils/url.spec.js} (97%) diff --git a/.changeset/angry-tigers-prove.md b/.changeset/angry-tigers-prove.md new file mode 100644 index 000000000000..ba2ad1fd4150 --- /dev/null +++ b/.changeset/angry-tigers-prove.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +Follow redirects when prerendering diff --git a/packages/kit/src/core/adapt/prerender.js b/packages/kit/src/core/adapt/prerender.js index 00458476c342..368f39eaba52 100644 --- a/packages/kit/src/core/adapt/prerender.js +++ b/packages/kit/src/core/adapt/prerender.js @@ -1,10 +1,11 @@ import { readFileSync, writeFileSync } from 'fs'; import { dirname, join, resolve as resolve_path } from 'path'; -import { pathToFileURL, resolve, URL } from 'url'; +import { pathToFileURL, URL } from 'url'; import { mkdirp } from '../../utils/filesystem.js'; import { __fetch_polyfill } from '../../install-fetch.js'; import { SVELTE_KIT } from '../constants.js'; import { get_single_valued_header } from '../../utils/http.js'; +import { resolve } from '../../utils/url.js'; /** * @typedef {import('types/config').PrerenderErrorHandler} PrerenderErrorHandler @@ -191,6 +192,11 @@ export async function prerender({ cwd, out, log, config, build_data, fallback, a log.warn(`${rendered.status} ${decoded_path} -> ${location}`); writeFileSync(file, ``); written_files.push(file); + + const resolved = resolve(path, location); + if (resolved.startsWith('/')) { + await visit(resolved, path); + } } else { log.warn(`location header missing on redirect received from ${decoded_path}`); } diff --git a/packages/kit/src/runtime/server/page/load_node.js b/packages/kit/src/runtime/server/page/load_node.js index b6df74168d2f..fc36ff232ce7 100644 --- a/packages/kit/src/runtime/server/page/load_node.js +++ b/packages/kit/src/runtime/server/page/load_node.js @@ -1,6 +1,7 @@ import { normalize } from '../../load.js'; import { respond } from '../index.js'; import { escape_json_string_in_html } from '../../../utils/escape.js'; +import { resolve } from '../../../utils/url.js'; const s = JSON.stringify; @@ -303,34 +304,3 @@ export async function load_node({ uses_credentials }; } - -const absolute = /^([a-z]+:)?\/?\//; - -/** - * @param {string} base - * @param {string} path - */ -export function resolve(base, path) { - const base_match = absolute.exec(base); - const path_match = absolute.exec(path); - - if (!base_match) { - throw new Error(`bad base path: "${base}"`); - } - - const baseparts = path_match ? [] : base.slice(base_match[0].length).split('/'); - const pathparts = path_match ? path.slice(path_match[0].length).split('/') : path.split('/'); - - baseparts.pop(); - - for (let i = 0; i < pathparts.length; i += 1) { - const part = pathparts[i]; - if (part === '.') continue; - else if (part === '..') baseparts.pop(); - else baseparts.push(part); - } - - const prefix = (path_match && path_match[0]) || (base_match && base_match[0]) || ''; - - return `${prefix}${baseparts.join('/')}`; -} diff --git a/packages/kit/src/utils/url.js b/packages/kit/src/utils/url.js new file mode 100644 index 000000000000..0a0b32a93929 --- /dev/null +++ b/packages/kit/src/utils/url.js @@ -0,0 +1,30 @@ +const absolute = /^([a-z]+:)?\/?\//; + +/** + * @param {string} base + * @param {string} path + */ +export function resolve(base, path) { + const base_match = absolute.exec(base); + const path_match = absolute.exec(path); + + if (!base_match) { + throw new Error(`bad base path: "${base}"`); + } + + const baseparts = path_match ? [] : base.slice(base_match[0].length).split('/'); + const pathparts = path_match ? path.slice(path_match[0].length).split('/') : path.split('/'); + + baseparts.pop(); + + for (let i = 0; i < pathparts.length; i += 1) { + const part = pathparts[i]; + if (part === '.') continue; + else if (part === '..') baseparts.pop(); + else baseparts.push(part); + } + + const prefix = (path_match && path_match[0]) || (base_match && base_match[0]) || ''; + + return `${prefix}${baseparts.join('/')}`; +} diff --git a/packages/kit/src/runtime/server/page/load_node.spec.js b/packages/kit/src/utils/url.spec.js similarity index 97% rename from packages/kit/src/runtime/server/page/load_node.spec.js rename to packages/kit/src/utils/url.spec.js index 75fbd6f0845b..11df39fee009 100644 --- a/packages/kit/src/runtime/server/page/load_node.spec.js +++ b/packages/kit/src/utils/url.spec.js @@ -1,6 +1,6 @@ import { test } from 'uvu'; import * as assert from 'uvu/assert'; -import { resolve } from './load_node.js'; +import { resolve } from './url.js'; test('resolves a root-relative path', () => { assert.equal(resolve('/a/b/c', '/x/y/z'), '/x/y/z'); From d73a7e2dd22b558dc7b081084a3e9dd1c2be25e2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 23 Nov 2021 12:15:54 -0500 Subject: [PATCH 2/2] add is_root_relative helper --- packages/kit/src/core/adapt/prerender.js | 6 +++--- packages/kit/src/runtime/server/page/load_node.js | 4 ++-- packages/kit/src/utils/url.js | 5 +++++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/kit/src/core/adapt/prerender.js b/packages/kit/src/core/adapt/prerender.js index 368f39eaba52..97d47cb35142 100644 --- a/packages/kit/src/core/adapt/prerender.js +++ b/packages/kit/src/core/adapt/prerender.js @@ -5,7 +5,7 @@ import { mkdirp } from '../../utils/filesystem.js'; import { __fetch_polyfill } from '../../install-fetch.js'; import { SVELTE_KIT } from '../constants.js'; import { get_single_valued_header } from '../../utils/http.js'; -import { resolve } from '../../utils/url.js'; +import { is_root_relative, resolve } from '../../utils/url.js'; /** * @typedef {import('types/config').PrerenderErrorHandler} PrerenderErrorHandler @@ -194,7 +194,7 @@ export async function prerender({ cwd, out, log, config, build_data, fallback, a written_files.push(file); const resolved = resolve(path, location); - if (resolved.startsWith('/')) { + if (is_root_relative(resolved)) { await visit(resolved, path); } } else { @@ -270,7 +270,7 @@ export async function prerender({ cwd, out, log, config, build_data, fallback, a if (!href) continue; const resolved = resolve(path, href); - if (!resolved.startsWith('/') || resolved.startsWith('//')) continue; + if (!is_root_relative(resolved)) continue; const parsed = new URL(resolved, 'http://localhost'); const pathname = decodeURI(parsed.pathname).replace(config.kit.paths.base, ''); diff --git a/packages/kit/src/runtime/server/page/load_node.js b/packages/kit/src/runtime/server/page/load_node.js index fc36ff232ce7..9cb9b1b8eef9 100644 --- a/packages/kit/src/runtime/server/page/load_node.js +++ b/packages/kit/src/runtime/server/page/load_node.js @@ -1,7 +1,7 @@ import { normalize } from '../../load.js'; import { respond } from '../index.js'; import { escape_json_string_in_html } from '../../../utils/escape.js'; -import { resolve } from '../../../utils/url.js'; +import { is_root_relative, resolve } from '../../../utils/url.js'; const s = JSON.stringify; @@ -127,7 +127,7 @@ export async function load_node({ `http://${page.host}/${asset.file}`, /** @type {RequestInit} */ (opts) ); - } else if (resolved.startsWith('/') && !resolved.startsWith('//')) { + } else if (is_root_relative(resolved)) { const relative = resolved; const headers = /** @type {import('types/helper').RequestHeaders} */ ({ diff --git a/packages/kit/src/utils/url.js b/packages/kit/src/utils/url.js index 0a0b32a93929..486742357d0c 100644 --- a/packages/kit/src/utils/url.js +++ b/packages/kit/src/utils/url.js @@ -28,3 +28,8 @@ export function resolve(base, path) { return `${prefix}${baseparts.join('/')}`; } + +/** @param {string} path */ +export function is_root_relative(path) { + return path[0] === '/' && path[1] !== '/'; +}