mirror of
https://github.com/kevinveenbirkenbach/homepage.veen.world.git
synced 2026-10-09 09:56:47 +00:00
Compare commits
2 Commits
7353e8d96a
...
d386160237
| Author | SHA1 | Date | |
|---|---|---|---|
| d386160237 | |||
| 7de3c74f3c |
@@ -1,5 +1,14 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [2.2.2] - 2026-09-23
|
||||||
|
|
||||||
|
* Vendor assets: marked 18 is found again, so the image build stops failing
|
||||||
|
* Iframe: a page without an iframe parameter no longer frames itself
|
||||||
|
* Security: the iframe and the new tab only open a URL the validator returned
|
||||||
|
* Tests: the inline-script check parses the page instead of matching a regex
|
||||||
|
* Tests: a regression spec guards the page that carries no iframe parameter
|
||||||
|
* CI: the Node jobs run the version Cypress 16 supports
|
||||||
|
|
||||||
## [2.2.1] - 2026-09-23
|
## [2.2.1] - 2026-09-23
|
||||||
|
|
||||||
* Images: releases ship arm64 next to amd64, for ARM servers and Apple Silicon
|
* Images: releases ship arm64 next to amd64, for ARM servers and Apple Silicon
|
||||||
|
|||||||
31
app/cypress/e2e/iframe_param_absent.spec.js
Normal file
31
app/cypress/e2e/iframe_param_absent.spec.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
describe('A page without an iframe parameter', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stays out of fullscreen', () => {
|
||||||
|
cy.get('body').should('not.have.class', 'fullscreen');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('frames nothing', () => {
|
||||||
|
cy.get('#main').find('iframe').should('not.exist');
|
||||||
|
cy.url().should('not.include', 'iframe=');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('never grows an iframe parameter out of its own URL', () => {
|
||||||
|
cy.wait(2000);
|
||||||
|
|
||||||
|
cy.url().then((url) => {
|
||||||
|
expect((url.match(/iframe/g) || []).length, 'iframe parameters').to.equal(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('treats an absent URL as unsafe', () => {
|
||||||
|
cy.window().then((win) => {
|
||||||
|
expect(win.safeUrl(null), 'null').to.equal(null);
|
||||||
|
expect(win.safeUrl(''), 'empty string').to.equal(null);
|
||||||
|
expect(win.safeUrl('javascript:alert(1)'), 'script URL').to.equal(null);
|
||||||
|
expect(win.safeUrl('/de/'), 'relative path').to.equal(`${win.location.origin}/de/`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -14,8 +14,8 @@ function allowedIframeUrl(url) {
|
|||||||
|
|
||||||
// === Auto-open iframe if URL parameter is present ===
|
// === Auto-open iframe if URL parameter is present ===
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
const paramUrl = allowedIframeUrl(new URLSearchParams(window.location.search).get('iframe'));
|
const paramUrl = new URLSearchParams(window.location.search).get('iframe');
|
||||||
if (paramUrl) {
|
if (paramUrl && allowedIframeUrl(paramUrl)) {
|
||||||
currentIframeUrl = paramUrl;
|
currentIframeUrl = paramUrl;
|
||||||
enterFullscreen();
|
enterFullscreen();
|
||||||
openIframe(paramUrl);
|
openIframe(paramUrl);
|
||||||
@@ -83,8 +83,8 @@ function openIframe(url) {
|
|||||||
|
|
||||||
// URL-State pushen
|
// URL-State pushen
|
||||||
var newUrl = new URL(window.location);
|
var newUrl = new URL(window.location);
|
||||||
newUrl.searchParams.set('iframe', target);
|
newUrl.searchParams.set('iframe', url);
|
||||||
window.history.pushState({ iframe: target }, '', newUrl);
|
window.history.pushState({ iframe: url }, '', newUrl);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,11 @@ function t(source) {
|
|||||||
const SAFE_URL_SCHEMES = ['http:', 'https:', 'mailto:'];
|
const SAFE_URL_SCHEMES = ['http:', 'https:', 'mailto:'];
|
||||||
|
|
||||||
function safeUrl(url) {
|
function safeUrl(url) {
|
||||||
|
if (url == null || String(url) === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const parsed = new URL(String(url == null ? '' : url), window.location.href);
|
const parsed = new URL(String(url), window.location.href);
|
||||||
return SAFE_URL_SCHEMES.includes(parsed.protocol) ? parsed.href : null;
|
return SAFE_URL_SCHEMES.includes(parsed.protocol) ? parsed.href : null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "portfolio-ui"
|
name = "portfolio-ui"
|
||||||
version = "2.2.1"
|
version = "2.2.2"
|
||||||
description = "A lightweight YAML-driven portfolio and landing-page generator."
|
description = "A lightweight YAML-driven portfolio and landing-page generator."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|||||||
Reference in New Issue
Block a user