Compare commits

..

No commits in common. "59fcc15ca6abc99b3a37c0b8231ef0010fe80635" and "5d344f940e61b506d9ffeae9adb0b4b52820a401" have entirely different histories.

8 changed files with 9 additions and 104 deletions

View File

@ -122,11 +122,8 @@ pdf-thumbs:
# A failing pdftoppm must at least warn: the `find | while` pipeline's # A failing pdftoppm must at least warn: the `find | while` pipeline's
# exit status is the last iteration's, so without the `||` a corrupt # exit status is the last iteration's, so without the `||` a corrupt
# PDF would silently ship without a thumbnail. # PDF would silently ship without a thumbnail.
# Walk ALL of static/ (not just papers/): /cv.pdf and /resume.pdf are
# the most-linked PDFs on the site and need hover thumbnails too.
# pdfjs/ is pruned — the vendored viewer ships sample PDFs.
@if command -v pdftoppm >/dev/null 2>&1; then \ @if command -v pdftoppm >/dev/null 2>&1; then \
find static -path static/pdfjs -prune -o -name '*.pdf' -print 2>/dev/null | while read pdf; do \ find static/papers -name '*.pdf' 2>/dev/null | while read pdf; do \
thumb="$${pdf%.pdf}.thumb"; \ thumb="$${pdf%.pdf}.thumb"; \
if [ ! -f "$${thumb}.png" ] || [ "$$pdf" -nt "$${thumb}.png" ]; then \ if [ ! -f "$${thumb}.png" ] || [ "$$pdf" -nt "$${thumb}.png" ]; then \
echo " pdf-thumb $$pdf"; \ echo " pdf-thumb $$pdf"; \

View File

@ -33,28 +33,11 @@ resolver_timeout 5s;
# (revisions get distinct IDs like 2604.06217v2), so 30d is safe. # (revisions get distinct IDs like 2604.06217v2), so 30d is safe.
location /proxy/arxiv/ { location /proxy/arxiv/ {
set $upstream_arxiv export.arxiv.org; set $upstream_arxiv export.arxiv.org;
# With a VARIABLE upstream, a URI part on proxy_pass is passed to proxy_pass https://$upstream_arxiv/;
# the upstream literally — "proxy_pass https://$up/;" sends every
# request to the upstream's homepage instead of prefix-stripping.
# Strip the prefix explicitly; `break` keeps args intact.
rewrite ^/proxy/arxiv/(.*)$ /$1 break;
proxy_pass https://$upstream_arxiv;
proxy_set_header Host $upstream_arxiv; proxy_set_header Host $upstream_arxiv;
proxy_set_header User-Agent "levineuwirth.org popup-proxy (ln@levineuwirth.org)"; proxy_set_header User-Agent "levineuwirth.org popup-proxy (ln@levineuwirth.org)";
proxy_ssl_server_name on; proxy_ssl_server_name on;
# Keep the security baseline: the add_header directives below
# would otherwise drop it for /proxy/ responses (same pattern
# as archive.conf). The upstream's own security headers are hidden
# first — browsers honor only the FIRST Strict-Transport-Security
# header (RFC 6797 §8.1), so an upstream's short max-age passing
# through ahead of ours would downgrade the domain's cached HSTS
# policy on every popup fetch.
proxy_hide_header Strict-Transport-Security;
proxy_hide_header Content-Security-Policy;
proxy_hide_header X-Frame-Options;
include snippets/security-headers.conf;
proxy_cache popup_proxy; proxy_cache popup_proxy;
proxy_cache_valid 200 30d; proxy_cache_valid 200 30d;
proxy_cache_valid any 5m; proxy_cache_valid any 5m;
@ -72,26 +55,11 @@ location /proxy/arxiv/ {
# change, but rarely; 7d strikes a reasonable balance. # change, but rarely; 7d strikes a reasonable balance.
location /proxy/archive/ { location /proxy/archive/ {
set $upstream_archive archive.org; set $upstream_archive archive.org;
# Prefix-strip explicitly — see the arXiv block for why a URI part proxy_pass https://$upstream_archive/;
# on a variable proxy_pass would break this.
rewrite ^/proxy/archive/(.*)$ /$1 break;
proxy_pass https://$upstream_archive;
proxy_set_header Host $upstream_archive; proxy_set_header Host $upstream_archive;
proxy_set_header User-Agent "levineuwirth.org popup-proxy (ln@levineuwirth.org)"; proxy_set_header User-Agent "levineuwirth.org popup-proxy (ln@levineuwirth.org)";
proxy_ssl_server_name on; proxy_ssl_server_name on;
# Keep the security baseline: the add_header directives below
# would otherwise drop it for /proxy/ responses (same pattern
# as archive.conf). The upstream's own security headers are hidden
# first — browsers honor only the FIRST Strict-Transport-Security
# header (RFC 6797 §8.1), so an upstream's short max-age passing
# through ahead of ours would downgrade the domain's cached HSTS
# policy on every popup fetch.
proxy_hide_header Strict-Transport-Security;
proxy_hide_header Content-Security-Policy;
proxy_hide_header X-Frame-Options;
include snippets/security-headers.conf;
proxy_cache popup_proxy; proxy_cache popup_proxy;
proxy_cache_valid 200 7d; proxy_cache_valid 200 7d;
proxy_cache_valid any 5m; proxy_cache_valid any 5m;
@ -108,10 +76,7 @@ location /proxy/archive/ {
# them server-side so popups.js stays focused on rendering. # them server-side so popups.js stays focused on rendering.
location /proxy/pubmed/ { location /proxy/pubmed/ {
set $upstream_pubmed eutils.ncbi.nlm.nih.gov; set $upstream_pubmed eutils.ncbi.nlm.nih.gov;
# Prefix-strip explicitly — see the arXiv block for why a URI part proxy_pass https://$upstream_pubmed/;
# on a variable proxy_pass would break this.
rewrite ^/proxy/pubmed/(.*)$ /$1 break;
proxy_pass https://$upstream_pubmed;
proxy_set_header Host $upstream_pubmed; proxy_set_header Host $upstream_pubmed;
proxy_set_header User-Agent "levineuwirth.org popup-proxy (ln@levineuwirth.org)"; proxy_set_header User-Agent "levineuwirth.org popup-proxy (ln@levineuwirth.org)";
proxy_ssl_server_name on; proxy_ssl_server_name on;
@ -120,18 +85,6 @@ location /proxy/pubmed/ {
# caching this is rarely exercised, but the burst guards a hot page. # caching this is rarely exercised, but the burst guards a hot page.
limit_req zone=pubmed burst=3 nodelay; limit_req zone=pubmed burst=3 nodelay;
# Keep the security baseline: the add_header directives below
# would otherwise drop it for /proxy/ responses (same pattern
# as archive.conf). The upstream's own security headers are hidden
# first — browsers honor only the FIRST Strict-Transport-Security
# header (RFC 6797 §8.1), so an upstream's short max-age passing
# through ahead of ours would downgrade the domain's cached HSTS
# policy on every popup fetch.
proxy_hide_header Strict-Transport-Security;
proxy_hide_header Content-Security-Policy;
proxy_hide_header X-Frame-Options;
include snippets/security-headers.conf;
proxy_cache popup_proxy; proxy_cache popup_proxy;
proxy_cache_valid 200 30d; proxy_cache_valid 200 30d;
proxy_cache_valid any 5m; proxy_cache_valid any 5m;

View File

@ -75,4 +75,4 @@ add_header Permissions-Policy
# #
# To collect violation reports, set up a `report-uri` endpoint and add # To collect violation reports, set up a `report-uri` endpoint and add
# `report-uri /csp-report;` (and/or `report-to <group>;`) below. # `report-uri /csp-report;` (and/or `report-to <group>;`) below.
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-eval' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data: https://*.basemaps.cartocdn.com https://upload.wikimedia.org;font-src 'self' data: https://cdn.jsdelivr.net; connect-src 'self' https://cdn.jsdelivr.net https://*.wikipedia.org https://api.crossref.org https://api.github.com https://openlibrary.org https://api.biorxiv.org https://www.youtube.com https://git.levineuwirth.org; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'; upgrade-insecure-requests" always; add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-eval' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data: https://*.basemaps.cartocdn.com; font-src 'self' data: https://cdn.jsdelivr.net; connect-src 'self' https://cdn.jsdelivr.net https://*.wikipedia.org https://api.crossref.org https://api.github.com https://openlibrary.org https://api.biorxiv.org https://www.youtube.com https://git.levineuwirth.org; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'; upgrade-insecure-requests" always;

View File

@ -55,26 +55,16 @@ brotli off; # we ship pre-compressed sidecars only, no on-the-fly b
# instantaneous. Same reasoning applies to fingerprinted fonts and the # instantaneous. Same reasoning applies to fingerprinted fonts and the
# locally vendored ML model files. # locally vendored ML model files.
location ^~ /pdfjs/ { location ^~ /pdfjs/ {
# Re-include the security baseline: this location declares its
# own add_header, which (per nginx inheritance rules) would
# otherwise drop HSTS/nosniff/CSP for everything it serves —
# and nosniff matters most on exactly these JS/CSS responses.
# Same pattern as archive.conf.
include snippets/security-headers.conf;
add_header Cache-Control "public, max-age=31536000, immutable" always; add_header Cache-Control "public, max-age=31536000, immutable" always;
access_log off; access_log off;
} }
location ^~ /fonts/ { location ^~ /fonts/ {
# Keep the security baseline (see first location above).
include snippets/security-headers.conf;
add_header Cache-Control "public, max-age=31536000, immutable" always; add_header Cache-Control "public, max-age=31536000, immutable" always;
access_log off; access_log off;
} }
location ^~ /models/ { location ^~ /models/ {
# Keep the security baseline (see first location above).
include snippets/security-headers.conf;
add_header Cache-Control "public, max-age=31536000, immutable" always; add_header Cache-Control "public, max-age=31536000, immutable" always;
access_log off; access_log off;
} }
@ -84,8 +74,6 @@ location ^~ /models/ {
# keeps them responsive to deploys (~1h staleness window for warm clients) # keeps them responsive to deploys (~1h staleness window for warm clients)
# without forcing a fetch on every page navigation. # without forcing a fetch on every page navigation.
location ~* \.(?:css|js|mjs|woff2?|svg|webp|png|jpg|jpeg|ico)$ { location ~* \.(?:css|js|mjs|woff2?|svg|webp|png|jpg|jpeg|ico)$ {
# Keep the security baseline (see first location above).
include snippets/security-headers.conf;
add_header Cache-Control "public, max-age=3600, must-revalidate" always; add_header Cache-Control "public, max-age=3600, must-revalidate" always;
access_log off; access_log off;
} }

View File

@ -78,18 +78,6 @@
line-height: 1.35; line-height: 1.35;
} }
/* Optional lead image (Wikipedia pageimages thumbnail, etc.) floats
beside the title/extract so text wraps around it; contained by the
popup's own overflow box. */
.popup-image {
float: right;
max-width: 96px;
max-height: 120px;
margin: 0 0 0.4rem 0.6rem;
border-radius: 4px;
border: 1px solid var(--border-muted);
}
.popup-abstract, .popup-abstract,
.popup-extract { .popup-extract {
font-size: 0.78rem; font-size: 0.78rem;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

View File

@ -453,14 +453,6 @@
var html = '<div class="popup-' + p.name + '">' var html = '<div class="popup-' + p.name + '">'
+ srcHtml(iconKey, p.label); + srcHtml(iconKey, p.label);
/* Optional lead image (e.g. Wikipedia pageimages thumbnail).
https-only: the URL comes from the provider's API response,
and anything else (protocol-relative, data:, ) is dropped
rather than guessed at. esc() handles attribute safety. */
if (fields.image && /^https:\/\//.test(fields.image)) {
html += '<img class="popup-image" src="' + esc(fields.image)
+ '" alt="" loading="lazy">';
}
if (fields.tags) html += '<div class="popup-tags">' + esc(fields.tags) + '</div>'; if (fields.tags) html += '<div class="popup-tags">' + esc(fields.tags) + '</div>';
html += '<div class="popup-title">' + esc(fields.title) + '</div>'; html += '<div class="popup-title">' + esc(fields.title) + '</div>';
if (authors) html += '<div class="popup-authors">' + esc(authors) + '</div>'; if (authors) html += '<div class="popup-authors">' + esc(authors) + '</div>';
@ -523,14 +515,8 @@
var hostMatch = ctx.href.match(/\/\/([a-z0-9-]+)\.wikipedia\.org\//i); var hostMatch = ctx.href.match(/\/\/([a-z0-9-]+)\.wikipedia\.org\//i);
var sub = hostMatch ? hostMatch[1].toLowerCase() : 'en'; var sub = hostMatch ? hostMatch[1].toLowerCase() : 'en';
if (sub === 'www') sub = 'en'; if (sub === 'www') sub = 'en';
/* pageimages|extracts in one call: the article's lead
image thumbnail rides along with the intro text.
Thumbnails come from upload.wikimedia.org that host
must stay in the CSP's img-src. */
return 'https://' + sub + '.wikipedia.org/w/api.php' return 'https://' + sub + '.wikipedia.org/w/api.php'
+ '?action=query&prop=extracts%7Cpageimages&exintro=1' + '?action=query&prop=extracts&exintro=1&format=json&redirects=1'
+ '&piprop=thumbnail&pithumbsize=320'
+ '&format=json&redirects=1'
+ '&titles=' + encodeURIComponent(decodeURIComponent(ctx.match[1])) + '&titles=' + encodeURIComponent(decodeURIComponent(ctx.match[1]))
+ '&origin=*'; + '&origin=*';
}, },
@ -547,21 +533,14 @@
}); });
var text = (doc.body.textContent || '').replace(/\s+/g, ' ').trim(); var text = (doc.body.textContent || '').replace(/\s+/g, ' ').trim();
if (!text) return null; if (!text) return null;
return { return { title: page.title, extract: text };
title: page.title,
extract: text,
image: page.thumbnail && page.thumbnail.source
};
} }
}, },
/* arXiv Atom API (CORS-broken upstream, proxied). /* arXiv — Atom API (CORS-broken upstream, proxied). */
ID forms: new-style 2403.12345(v2), and old-style
archive/0211159 or archive.SC/0211159 (pre-2007); /pdf/ URLs
may carry a trailing .pdf, which stays outside the capture. */
{ {
name: 'arxiv', label: 'arXiv', name: 'arxiv', label: 'arXiv',
match: /arxiv\.org\/(?:abs|pdf)\/((?:\d{4}\.\d{4,5}|[a-z-]+(?:\.[A-Z]{2})?\/\d{7})(?:v\d+)?)/, match: /arxiv\.org\/(?:abs|pdf)\/(\d{4}\.\d{4,5}(?:v\d+)?)/,
fetchType: 'xml', fetchType: 'xml',
url: function (ctx) { url: function (ctx) {
return '/proxy/arxiv/api/query?id_list=' return '/proxy/arxiv/api/query?id_list='

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 KiB