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); }); });