mirror of
https://github.com/kevinveenbirkenbach/homepage.veen.world.git
synced 2026-10-10 10:26:49 +00:00
Compare commits
4 Commits
v2.2.1
...
7353e8d96a
| Author | SHA1 | Date | |
|---|---|---|---|
| 7353e8d96a | |||
| 4cc8052a88 | |||
| 87c4405aa4 | |||
| ff6e7d3689 |
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -51,7 +51,7 @@ jobs:
|
|||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: "24"
|
||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: app/package.json
|
cache-dependency-path: app/package.json
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -153,7 +153,7 @@ jobs:
|
|||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "20"
|
node-version: "24"
|
||||||
cache: npm
|
cache: npm
|
||||||
cache-dependency-path: app/package.json
|
cache-dependency-path: app/package.json
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const SHARED = {
|
|||||||
openDynamicPopup: 'readonly',
|
openDynamicPopup: 'readonly',
|
||||||
closeAllModals: 'readonly',
|
closeAllModals: 'readonly',
|
||||||
isSafeUrl: 'readonly',
|
isSafeUrl: 'readonly',
|
||||||
|
safeUrl: 'readonly',
|
||||||
openIframe: 'readonly',
|
openIframe: 'readonly',
|
||||||
enterFullscreen: 'readonly',
|
enterFullscreen: 'readonly',
|
||||||
exitFullscreen: 'readonly',
|
exitFullscreen: 'readonly',
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ const markedCandidates = [
|
|||||||
path.join(NM, 'marked', 'marked.min.js'), // v4.x
|
path.join(NM, 'marked', 'marked.min.js'), // v4.x
|
||||||
path.join(NM, 'marked', 'lib', 'marked.umd.min.js'), // v5.x
|
path.join(NM, 'marked', 'lib', 'marked.umd.min.js'), // v5.x
|
||||||
path.join(NM, 'marked', 'dist', 'marked.min.js'), // v9+
|
path.join(NM, 'marked', 'dist', 'marked.min.js'), // v9+
|
||||||
|
path.join(NM, 'marked', 'lib', 'marked.umd.js'), // v16+
|
||||||
];
|
];
|
||||||
const markedSrc = markedCandidates.find(p => fs.existsSync(p));
|
const markedSrc = markedCandidates.find(p => fs.existsSync(p));
|
||||||
if (!markedSrc) throw new Error('marked: no browser UMD build found in node_modules');
|
if (!markedSrc) throw new Error('marked: no browser UMD build found in node_modules');
|
||||||
|
|||||||
@@ -2,19 +2,20 @@
|
|||||||
let mainElement, originalContent, originalMainStyle, container, customScrollbar, scrollbarContainer;
|
let mainElement, originalContent, originalMainStyle, container, customScrollbar, scrollbarContainer;
|
||||||
let currentIframeUrl = null;
|
let currentIframeUrl = null;
|
||||||
|
|
||||||
function isAllowedIframeUrl(url) {
|
function allowedIframeUrl(url) {
|
||||||
if (!isSafeUrl(url)) {
|
const candidate = safeUrl(url);
|
||||||
return false;
|
if (candidate === null) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
const allowedOrigins = new Set([window.location.origin]);
|
const allowedOrigins = new Set([window.location.origin]);
|
||||||
document.querySelectorAll('a.iframe-link[href]').forEach((link) => allowedOrigins.add(link.origin));
|
document.querySelectorAll('a.iframe-link[href]').forEach((link) => allowedOrigins.add(link.origin));
|
||||||
return allowedOrigins.has(new URL(url, window.location.href).origin);
|
return allowedOrigins.has(new URL(candidate).origin) ? candidate : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Auto-open iframe if URL parameter is present ===
|
// === Auto-open iframe if URL parameter is present ===
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
const paramUrl = new URLSearchParams(window.location.search).get('iframe');
|
const paramUrl = allowedIframeUrl(new URLSearchParams(window.location.search).get('iframe'));
|
||||||
if (paramUrl && isAllowedIframeUrl(paramUrl)) {
|
if (paramUrl) {
|
||||||
currentIframeUrl = paramUrl;
|
currentIframeUrl = paramUrl;
|
||||||
enterFullscreen();
|
enterFullscreen();
|
||||||
openIframe(paramUrl);
|
openIframe(paramUrl);
|
||||||
@@ -43,7 +44,8 @@ function syncIframeHeight() {
|
|||||||
|
|
||||||
// Function to open a URL in an iframe (jQuery version mit 1500 ms Fade)
|
// Function to open a URL in an iframe (jQuery version mit 1500 ms Fade)
|
||||||
function openIframe(url) {
|
function openIframe(url) {
|
||||||
if (!isSafeUrl(url)) {
|
const target = safeUrl(url);
|
||||||
|
if (target === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +75,7 @@ function openIframe(url) {
|
|||||||
|
|
||||||
// Quelle setzen und mit 1500 ms einblenden
|
// Quelle setzen und mit 1500 ms einblenden
|
||||||
$iframe
|
$iframe
|
||||||
.attr('src', url)
|
.attr('src', target)
|
||||||
.fadeIn(1500, function() {
|
.fadeIn(1500, function() {
|
||||||
syncIframeHeight();
|
syncIframeHeight();
|
||||||
observeIframeNavigation();
|
observeIframeNavigation();
|
||||||
@@ -81,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', url);
|
newUrl.searchParams.set('iframe', target);
|
||||||
window.history.pushState({ iframe: url }, '', newUrl);
|
window.history.pushState({ iframe: target }, '', newUrl);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,8 +150,8 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||||||
*/
|
*/
|
||||||
function openIframeInNewTab() {
|
function openIframeInNewTab() {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(window.location.search);
|
||||||
const iframeUrl = params.get('iframe');
|
const iframeUrl = allowedIframeUrl(params.get('iframe'));
|
||||||
if (iframeUrl && isAllowedIframeUrl(iframeUrl)) {
|
if (iframeUrl) {
|
||||||
window.open(iframeUrl, '_blank');
|
window.open(iframeUrl, '_blank');
|
||||||
} else {
|
} else {
|
||||||
alert('No iframe is currently open.');
|
alert('No iframe is currently open.');
|
||||||
|
|||||||
@@ -7,15 +7,19 @@ function t(source) {
|
|||||||
|
|
||||||
const SAFE_URL_SCHEMES = ['http:', 'https:', 'mailto:'];
|
const SAFE_URL_SCHEMES = ['http:', 'https:', 'mailto:'];
|
||||||
|
|
||||||
function isSafeUrl(url) {
|
function safeUrl(url) {
|
||||||
try {
|
try {
|
||||||
const parsed = new URL(String(url == null ? '' : url), window.location.href);
|
const parsed = new URL(String(url == null ? '' : url), window.location.href);
|
||||||
return SAFE_URL_SCHEMES.includes(parsed.protocol);
|
return SAFE_URL_SCHEMES.includes(parsed.protocol) ? parsed.href : null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSafeUrl(url) {
|
||||||
|
return safeUrl(url) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
function iconAndName(item) {
|
function iconAndName(item) {
|
||||||
const nodes = [];
|
const nodes = [];
|
||||||
if (item.icon && item.icon.class) {
|
if (item.icon && item.icon.class) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
from html.parser import HTMLParser
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import Mock, patch
|
from unittest.mock import Mock, patch
|
||||||
|
|
||||||
@@ -112,18 +113,29 @@ class TestEscaping(AppRouteMixin, unittest.TestCase):
|
|||||||
self.assertIn("<script>alert('config')", body)
|
self.assertIn("<script>alert('config')", body)
|
||||||
|
|
||||||
|
|
||||||
|
class InlineScriptCollector(HTMLParser):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.inline = []
|
||||||
|
|
||||||
|
def handle_starttag(self, tag, attrs):
|
||||||
|
if tag != "script":
|
||||||
|
return
|
||||||
|
attributes = dict(attrs)
|
||||||
|
if "src" in attributes or attributes.get("type") == "application/json":
|
||||||
|
return
|
||||||
|
self.inline.append(self.get_starttag_text())
|
||||||
|
|
||||||
|
|
||||||
class TestContentSecurityPolicy(AppRouteMixin, unittest.TestCase):
|
class TestContentSecurityPolicy(AppRouteMixin, unittest.TestCase):
|
||||||
def test_page_ships_no_executable_inline_script(self):
|
def test_page_ships_no_executable_inline_script(self):
|
||||||
body = self.client.get("/de/").get_data(as_text=True)
|
body = self.client.get("/de/").get_data(as_text=True)
|
||||||
|
|
||||||
inline = [
|
collector = InlineScriptCollector()
|
||||||
tag
|
collector.feed(body)
|
||||||
for tag in re.findall(r"<script\b[^>]*>", body)
|
|
||||||
if "src=" not in tag and 'type="application/json"' not in tag
|
|
||||||
]
|
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
inline,
|
collector.inline,
|
||||||
[],
|
[],
|
||||||
"a host CSP can only hash an inline script whose content it knows, "
|
"a host CSP can only hash an inline script whose content it knows, "
|
||||||
"and this one changes with every language",
|
"and this one changes with every language",
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import re
|
|||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
import unittest.mock
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ class TestCatalogMerge(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_an_unsupported_code_never_becomes_a_path(self):
|
def test_an_unsupported_code_never_becomes_a_path(self):
|
||||||
with mock.patch.object(i18n, "read_catalog") as read:
|
with unittest.mock.patch.object(i18n, "read_catalog") as read:
|
||||||
self.assertEqual(i18n.catalog("../content/de"), {})
|
self.assertEqual(i18n.catalog("../content/de"), {})
|
||||||
|
|
||||||
read.assert_not_called()
|
read.assert_not_called()
|
||||||
|
|||||||
Reference in New Issue
Block a user