From 634409188cee0b0cbef1e9cb4df313bc871cf3ba Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Tue, 10 Oct 2023 20:56:38 +0200 Subject: [PATCH] [IMP] web: url utils: add redirect function This function must be used to safely redirect to another url. It ensures that we stay on the same origin, and thus that the url is safe. Part-of: odoo/odoo#138054 --- addons/web/static/src/core/utils/urls.js | 17 +++++++++ .../web/static/tests/core/utils/urls_tests.js | 37 ++++++++++++++++++- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/core/utils/urls.js b/addons/web/static/src/core/utils/urls.js index 501b484e938..fa7011d79b2 100644 --- a/addons/web/static/src/core/utils/urls.js +++ b/addons/web/static/src/core/utils/urls.js @@ -3,6 +3,8 @@ import { session } from "@web/session"; import { browser } from "../browser/browser"; +export class RedirectionError extends Error {} + /** * Transforms a key value mapping to a string formatted as url hash, e.g. * {a: "x", b: 2} -> "a=x&b=2" @@ -83,3 +85,18 @@ export function getDataURLFromFile(file) { reader.readAsDataURL(file); }); } + +/** + * Safely redirects to the given url within the same origin. + * + * @param {string} url + * @throws {RedirectionError} if the given url has a different origin + */ +export function redirect(url) { + const { origin, pathname } = browser.location; + const _url = new URL(url, `${origin}${pathname}`); + if (_url.origin !== origin) { + throw new RedirectionError("Can't redirect to another origin"); + } + browser.location = _url.href; +} diff --git a/addons/web/static/tests/core/utils/urls_tests.js b/addons/web/static/tests/core/utils/urls_tests.js index 993a46fdfc8..890a0c73a0e 100644 --- a/addons/web/static/tests/core/utils/urls_tests.js +++ b/addons/web/static/tests/core/utils/urls_tests.js @@ -1,7 +1,13 @@ /** @odoo-module */ import { browser } from "@web/core/browser/browser"; -import { getDataURLFromFile, getOrigin, url } from "@web/core/utils/urls"; +import { + getDataURLFromFile, + getOrigin, + redirect, + RedirectionError, + url, +} from "@web/core/utils/urls"; import { patchWithCleanup } from "../../helpers/utils"; QUnit.module("URLS", (hooks) => { @@ -63,6 +69,33 @@ QUnit.module("URLS", (hooks) => { QUnit.test("getDataURLFromFile handles empty file", async (assert) => { const emptyFile = new File([""], "empty.txt", { type: "text/plain" }); const dataUrl = await getDataURLFromFile(emptyFile); - assert.strictEqual(dataUrl, "data:text/plain;base64,", "dataURL for empty file is not proper"); + assert.strictEqual( + dataUrl, + "data:text/plain;base64,", + "dataURL for empty file is not proper" + ); + }); + + QUnit.test("redirect", (assert) => { + function testRedirect(url) { + browser.location = { + protocol: "http:", + host: "testhost", + origin: "http://www.test.com", + pathname: "/some/tests", + }; + redirect(url); + return browser.location; + } + + assert.strictEqual(testRedirect("abc"), "http://www.test.com/some/abc"); + assert.strictEqual(testRedirect("./abc"), "http://www.test.com/some/abc"); + assert.strictEqual(testRedirect("../abc/def"), "http://www.test.com/abc/def"); + assert.strictEqual(testRedirect("/abc/def"), "http://www.test.com/abc/def"); + assert.strictEqual(testRedirect("/abc/def?x=y"), "http://www.test.com/abc/def?x=y"); + assert.strictEqual(testRedirect("/abc?x=y#a=1&b=2"), "http://www.test.com/abc?x=y#a=1&b=2"); + + assert.throws(() => testRedirect("https://www.odoo.com"), RedirectionError); + assert.throws(() => testRedirect("javascript:alert('boom');"), RedirectionError); }); });