mirror of
https://github.com/kevinveenbirkenbach/homepage.veen.world.git
synced 2026-10-09 09:56:47 +00:00
Compare commits
2 Commits
7353e8d96a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d386160237 | |||
| 7de3c74f3c |
@@ -1,5 +1,14 @@
|
||||
# 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
|
||||
|
||||
* 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 ===
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const paramUrl = allowedIframeUrl(new URLSearchParams(window.location.search).get('iframe'));
|
||||
if (paramUrl) {
|
||||
const paramUrl = new URLSearchParams(window.location.search).get('iframe');
|
||||
if (paramUrl && allowedIframeUrl(paramUrl)) {
|
||||
currentIframeUrl = paramUrl;
|
||||
enterFullscreen();
|
||||
openIframe(paramUrl);
|
||||
@@ -83,8 +83,8 @@ function openIframe(url) {
|
||||
|
||||
// URL-State pushen
|
||||
var newUrl = new URL(window.location);
|
||||
newUrl.searchParams.set('iframe', target);
|
||||
window.history.pushState({ iframe: target }, '', newUrl);
|
||||
newUrl.searchParams.set('iframe', url);
|
||||
window.history.pushState({ iframe: url }, '', newUrl);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,8 +8,11 @@ function t(source) {
|
||||
const SAFE_URL_SCHEMES = ['http:', 'https:', 'mailto:'];
|
||||
|
||||
function safeUrl(url) {
|
||||
if (url == null || String(url) === '') {
|
||||
return null;
|
||||
}
|
||||
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;
|
||||
} catch (error) {
|
||||
return null;
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "portfolio-ui"
|
||||
version = "2.2.1"
|
||||
version = "2.2.2"
|
||||
description = "A lightweight YAML-driven portfolio and landing-page generator."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
Reference in New Issue
Block a user