Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 2 additions & 1 deletion assets/components/pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { connect, machine, type Api, type Props } from "@zag-js/pagination";
import type { IntlTranslations } from "@zag-js/pagination";
import { VanillaMachine } from "@zag-js/vanilla";
import { Component } from "../lib/core";
import { isAllowedRedirectDestination } from "../lib/redirect";
import { cloneTemplateChildren, getString } from "../lib/util";

export class Pagination extends Component<Props, Api> {
Expand Down Expand Up @@ -184,7 +185,7 @@ export function applyPhoenixLinkAttrsToNavigableParts(rootEl: HTMLElement): void
export function buildGetPageUrl(el: HTMLElement): Props["getPageUrl"] | undefined {
const triggerType = getString(el, "type");
const base = el.dataset.to;
if (triggerType !== "link" || !base) return undefined;
if (triggerType !== "link" || !base || !isAllowedRedirectDestination(base)) return undefined;

const pageParam = el.dataset.pageParam ?? "page";
const pageSizeParam = el.dataset.pageSizeParam ?? "page_size";
Expand Down
2 changes: 2 additions & 0 deletions assets/test/component/component-pagination-matrix.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ describe("buildGetPageUrl matrix", () => {
[{ type: "link", to: "/x", pageParam: "p", pageSizeParam: "ps" }, "p=3"],
[{ type: "button", to: "/x" }, null],
[{ type: "link" }, null],
[{ type: "link", to: "javascript:alert(1)" }, null],
[{ type: "link", to: "//evil.example" }, null],
] as const)("%#", (dataset, fragment) => {
const getUrl = buildGetPageUrl(el(dataset as Record<string, string>));
if (fragment == null) expect(getUrl).toBeUndefined();
Expand Down
5 changes: 5 additions & 0 deletions assets/test/component/pagination.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ describe("buildGetPageUrl", () => {
it("returns undefined for button type", () => {
expect(buildGetPageUrl(el({ type: "button" }))).toBeUndefined();
});

it("returns undefined for disallowed base URL", () => {
expect(buildGetPageUrl(el({ type: "link", to: "javascript:alert(1)" }))).toBeUndefined();
expect(buildGetPageUrl(el({ type: "link", to: "//evil.example" }))).toBeUndefined();
});
});

describe("applyPhoenixLinkAttrs", () => {
Expand Down
8 changes: 7 additions & 1 deletion lib/components/pagination/connect.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ defmodule Corex.Pagination.Connect do
"data-sibling-count" => to_string(assigns.sibling_count),
"data-boundary-count" => to_string(assigns.boundary_count),
"data-type" => assigns.type,
"data-to" => assigns.to,
"data-to" => link_base_to(assigns),
"data-page-param" => assigns.page_param,
"data-page-size-param" => assigns.page_size_param,
"data-redirect" => assigns.redirect,
Expand All @@ -37,6 +37,12 @@ defmodule Corex.Pagination.Connect do
|> maybe_put_data_dir_from(assigns)
end

defp link_base_to(%{to: to}) when is_binary(to) do
if Corex.Url.allowed_href?(to), do: to
end

defp link_base_to(_), do: nil

defp translation_json(assigns) do
case Map.get(assigns, :translation) do
%PaginationTranslation{} = t ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ function performRedirect(input, ctx) {
}

export {
isAllowedRedirectDestination,
readDomItemRedirect,
performRedirect
};
2 changes: 1 addition & 1 deletion priv/static/combobox.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
import {
performRedirect,
readDomItemRedirect
} from "./chunks/chunk-6Q6MB27T.mjs";
} from "./chunks/chunk-HZLPIQBD.mjs";
import {
getInteractionModality,
setInteractionModality,
Expand Down
19 changes: 10 additions & 9 deletions priv/static/corex.js
Original file line number Diff line number Diff line change
Expand Up @@ -13011,7 +13011,7 @@ var Corex = (() => {
}
});

// ../priv/static/chunks/chunk-6Q6MB27T.mjs
// ../priv/static/chunks/chunk-HZLPIQBD.mjs
function isAllowedRedirectDestination(destination) {
const trimmed = destination.trim();
if (!trimmed) return false;
Expand Down Expand Up @@ -13059,8 +13059,8 @@ var Corex = (() => {
return true;
}
var REDIRECT_MODES, SCHEME_PREFIX;
var init_chunk_6Q6MB27T = __esm({
"../priv/static/chunks/chunk-6Q6MB27T.mjs"() {
var init_chunk_HZLPIQBD = __esm({
"../priv/static/chunks/chunk-HZLPIQBD.mjs"() {
"use strict";
REDIRECT_MODES = ["href", "patch", "navigate"];
SCHEME_PREFIX = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
Expand Down Expand Up @@ -13614,7 +13614,7 @@ var Corex = (() => {
init_chunk_CNPBJL2G();
init_chunk_NICWUGGL();
init_chunk_FVGYE2AE();
init_chunk_6Q6MB27T();
init_chunk_HZLPIQBD();
init_chunk_VDUSDBJS();
init_chunk_I2HPUDHJ();
init_chunk_77HPO22C();
Expand Down Expand Up @@ -27360,7 +27360,7 @@ ${err}`);
"use strict";
init_chunk_NICWUGGL();
init_chunk_FVGYE2AE();
init_chunk_6Q6MB27T();
init_chunk_HZLPIQBD();
init_chunk_VDUSDBJS();
init_chunk_I2HPUDHJ();
init_chunk_77HPO22C();
Expand Down Expand Up @@ -28722,7 +28722,7 @@ ${err}`);
init_chunk_WJDVLJMP();
init_chunk_B5L2AGOH();
init_chunk_CNPBJL2G();
init_chunk_6Q6MB27T();
init_chunk_HZLPIQBD();
init_chunk_VDUSDBJS();
init_chunk_2WCNJX5P();
init_chunk_2GQRP3FN();
Expand Down Expand Up @@ -31853,7 +31853,7 @@ ${err}`);
var _a4, _b;
const triggerType = getString(el, "type");
const base = el.dataset.to;
if (triggerType !== "link" || !base) return void 0;
if (triggerType !== "link" || !base || !isAllowedRedirectDestination(base)) return void 0;
const pageParam = (_a4 = el.dataset.pageParam) != null ? _a4 : "page";
const pageSizeParam = (_b = el.dataset.pageSizeParam) != null ? _b : "page_size";
return ({ page, pageSize }) => {
Expand Down Expand Up @@ -31931,6 +31931,7 @@ ${err}`);
"../priv/static/pagination.mjs"() {
"use strict";
init_chunk_HWSJUKAB();
init_chunk_HZLPIQBD();
init_chunk_77HPO22C();
init_chunk_2WCNJX5P();
init_chunk_2GQRP3FN();
Expand Down Expand Up @@ -34790,7 +34791,7 @@ ${err}`);
init_chunk_CNPBJL2G();
init_chunk_NICWUGGL();
init_chunk_FVGYE2AE();
init_chunk_6Q6MB27T();
init_chunk_HZLPIQBD();
init_chunk_VDUSDBJS();
init_chunk_I2HPUDHJ();
init_chunk_77HPO22C();
Expand Down Expand Up @@ -43656,7 +43657,7 @@ ${err}`);
init_chunk_JDGMEOQK();
init_chunk_SBA2GV3P();
init_chunk_FVGYE2AE();
init_chunk_6Q6MB27T();
init_chunk_HZLPIQBD();
init_chunk_77HPO22C();
init_chunk_2WCNJX5P();
init_chunk_2GQRP3FN();
Expand Down
18 changes: 9 additions & 9 deletions priv/static/corex.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion priv/static/listbox.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import "./chunks/chunk-FVGYE2AE.mjs";
import {
performRedirect,
readDomItemRedirect
} from "./chunks/chunk-6Q6MB27T.mjs";
} from "./chunks/chunk-HZLPIQBD.mjs";
import "./chunks/chunk-VDUSDBJS.mjs";
import {
readStringListControlledZagProps,
Expand Down
2 changes: 1 addition & 1 deletion priv/static/menu.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
import {
performRedirect,
readDomItemRedirect
} from "./chunks/chunk-6Q6MB27T.mjs";
} from "./chunks/chunk-HZLPIQBD.mjs";
import {
getInteractionModality,
setInteractionModality,
Expand Down
5 changes: 4 additions & 1 deletion priv/static/pagination.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import {
memo
} from "./chunks/chunk-HWSJUKAB.mjs";
import {
isAllowedRedirectDestination
} from "./chunks/chunk-HZLPIQBD.mjs";
import {
createDomEventRegistry,
createHookHandleEventRegistry
Expand Down Expand Up @@ -534,7 +537,7 @@ function applyPhoenixLinkAttrsToNavigableParts(rootEl) {
function buildGetPageUrl(el) {
const triggerType = getString(el, "type");
const base = el.dataset.to;
if (triggerType !== "link" || !base) return void 0;
if (triggerType !== "link" || !base || !isAllowedRedirectDestination(base)) return void 0;
const pageParam = el.dataset.pageParam ?? "page";
const pageSizeParam = el.dataset.pageSizeParam ?? "page_size";
return ({ page, pageSize }) => {
Expand Down
2 changes: 1 addition & 1 deletion priv/static/select.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
import {
performRedirect,
readDomItemRedirect
} from "./chunks/chunk-6Q6MB27T.mjs";
} from "./chunks/chunk-HZLPIQBD.mjs";
import {
getInteractionModality,
setInteractionModality,
Expand Down
2 changes: 1 addition & 1 deletion priv/static/tree-view.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import {
performRedirect,
readDomItemRedirect
} from "./chunks/chunk-6Q6MB27T.mjs";
} from "./chunks/chunk-HZLPIQBD.mjs";
import {
createDomEventRegistry,
createHookHandleEventRegistry
Expand Down
24 changes: 24 additions & 0 deletions test/components/pagination_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,30 @@ defmodule Corex.PaginationTest do
assert props["data-default-page-size"] == nil
assert props["data-controlled-page-size"] == ""
end

test "omits data-to for disallowed base URL" do
props =
Connect.props(%Corex.Pagination.Anatomy.Props{
id: "p1",
count: 100,
type: "link",
to: "javascript:alert(1)"
})

assert props["data-to"] == nil
end

test "includes data-to for allowed base URL" do
props =
Connect.props(%Corex.Pagination.Anatomy.Props{
id: "p1",
count: 100,
type: "link",
to: "/items"
})

assert props["data-to"] == "/items"
end
end

describe "set_page/2" do
Expand Down
Loading