Skip to content

Commit

Permalink
follow redirects when prerendering (#2832)
Browse files Browse the repository at this point in the history
* follow redirects when prerendering

* add is_root_relative helper
  • Loading branch information
Rich-Harris authored Nov 24, 2021
1 parent e6ffdc5 commit f12bbcc
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 35 deletions.
5 changes: 5 additions & 0 deletions .changeset/angry-tigers-prove.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

Follow redirects when prerendering
10 changes: 8 additions & 2 deletions packages/kit/src/core/adapt/prerender.js
Original file line number Diff line number Diff line change
@@ -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 { is_root_relative, resolve } from '../../utils/url.js';

/**
* @typedef {import('types/config').PrerenderErrorHandler} PrerenderErrorHandler
Expand Down Expand Up @@ -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, `<meta http-equiv="refresh" content="0;url=${encodeURI(location)}">`);
written_files.push(file);

const resolved = resolve(path, location);
if (is_root_relative(resolved)) {
await visit(resolved, path);
}
} else {
log.warn(`location header missing on redirect received from ${decoded_path}`);
}
Expand Down Expand Up @@ -264,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, '');
Expand Down
34 changes: 2 additions & 32 deletions packages/kit/src/runtime/server/page/load_node.js
Original file line number Diff line number Diff line change
@@ -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 { is_root_relative, resolve } from '../../../utils/url.js';

const s = JSON.stringify;

Expand Down Expand Up @@ -126,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} */ ({
Expand Down Expand Up @@ -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('/')}`;
}
35 changes: 35 additions & 0 deletions packages/kit/src/utils/url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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('/')}`;
}

/** @param {string} path */
export function is_root_relative(path) {
return path[0] === '/' && path[1] !== '/';
}
Original file line number Diff line number Diff line change
@@ -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');
Expand Down

0 comments on commit f12bbcc

Please sign in to comment.