discovery-media-player 0.1.20 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/context/standalone.js +12 -0
- package/package.json +1 -1
- package/server/handler.js +33 -4
package/context/standalone.js
CHANGED
|
@@ -360,6 +360,18 @@ function createStandaloneContext(env = process.env) {
|
|
|
360
360
|
errors: journal,
|
|
361
361
|
|
|
362
362
|
legal: {
|
|
363
|
+
/**
|
|
364
|
+
* L'URL PUBLIQUE de cette instance, telle que l'exploitant l'a écrite.
|
|
365
|
+
*
|
|
366
|
+
* ⚠️ Sert à fabriquer les liens qui partent par email. L'en-tête `Host` ne convient pas :
|
|
367
|
+
* il est choisi par le client, donc un lecteur pouvait faire envoyer un message signé de
|
|
368
|
+
* l'hôte dont le bouton pointe ailleurs. Vide ⇒ repli sur `Host`, mais signalé.
|
|
369
|
+
*/
|
|
370
|
+
get publicUrl() {
|
|
371
|
+
const v = sansBarreFinale(env.PLAYER_PUBLIC_URL).trim();
|
|
372
|
+
return v.startsWith("https://") ? v : "";
|
|
373
|
+
},
|
|
374
|
+
|
|
363
375
|
get sourceUrl() { return String(env.PLAYER_SOURCE_URL || "https://github.com/Juli1artha/discovery-media-player").trim(); },
|
|
364
376
|
get legalUrl() { return String(env.PLAYER_LEGAL_URL || "").trim(); },
|
|
365
377
|
get privacyUrl() { return String(env.PLAYER_PRIVACY_URL || "").trim(); },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discovery-media-player",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"description": "Self-hosted document viewer: per-recipient tracked links, reading analytics, live presentation. The core knows nothing about the application hosting it — everything it borrows arrives through an injected context.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pdf-viewer",
|
package/server/handler.js
CHANGED
|
@@ -49,6 +49,21 @@ const dispositionInline = (name) => {
|
|
|
49
49
|
return `inline; filename="${ascii}"; filename*=UTF-8''${encodeURIComponent(raw)}`;
|
|
50
50
|
};
|
|
51
51
|
const originOf = (u) => { try { return new URL(u).origin; } catch { return ""; } };
|
|
52
|
+
// ⚠️ CETTE VERSION EST DANS LA PLAGE DE CVE-2024-4367 (exécution de script à l'ouverture d'un PDF
|
|
53
|
+
// forgé, quand `isEvalSupported` garde sa valeur par défaut). Deux raisons pour lesquelles elle
|
|
54
|
+
// est encore là, et il faut les distinguer :
|
|
55
|
+
//
|
|
56
|
+
// 1. Notre CSP n'autorise pas `unsafe-eval`, ce qui bloque le chemin d'exploitation. Mais cette
|
|
57
|
+
// atténuation était IMPLICITE — une modification de CSP la rouvrait sans que rien ne le dise.
|
|
58
|
+
// Les quatre appels à `getDocument` forcent donc désormais `isEvalSupported: false`, en
|
|
59
|
+
// défense en profondeur : la protection ne dépend plus d'un en-tête écrit ailleurs.
|
|
60
|
+
// 2. La montée vers 4.x n'est PAS un changement de numéro : cdnjs ne publie plus que des modules
|
|
61
|
+
// ES (`pdf.min.mjs`) à partir de 4.0, alors qu'on charge un script classique et qu'on
|
|
62
|
+
// configure le worker à la main. C'est une migration, elle est suivie séparément dans
|
|
63
|
+
// docs/AUDIT-2026-08-14-SUIVI.md (P1-3) avec l'embarquement de la bibliothèque, qui règle
|
|
64
|
+
// aussi la dépendance CDN sans intégrité (P2-4).
|
|
65
|
+
//
|
|
66
|
+
// Signalé par un audit externe. Fermer le chemin d'abord, migrer ensuite — dans cet ordre.
|
|
52
67
|
const PDFJS = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174";
|
|
53
68
|
const SUPAJS = "https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2/dist/umd/supabase.js";
|
|
54
69
|
|
|
@@ -286,7 +301,7 @@ var Live=(function(){
|
|
|
286
301
|
function renderMsgInner(m){return Player.chat.renderMessage(m,{me:ME,reactIcon:RSVG});}
|
|
287
302
|
function cmClass(m){return Player.chat.messageClassName(m,ME,isMentioned(m));}
|
|
288
303
|
function hydratePdf(d,m){if(m&&m.attachment&&m.attachment.kind==='pdf'&&!m.deleted){var ph=d.querySelector('.cm-att-ph');if(ph)pdfThumb(m.attachment.url,ph);}}
|
|
289
|
-
function pdfThumb(url,ph){if(!ph)return;if(pdfCache[url]){ph.innerHTML='<img src="'+pdfCache[url]+'" alt="">';return;}if(!window.pdfjsLib)return;try{pdfjsLib.getDocument(url).promise.then(function(pdf){return pdf.getPage(1);}).then(function(pg){var v0=pg.getViewport({scale:1}),sc=Math.min(1.6,208/v0.width),vp=pg.getViewport({scale:sc}),cv=document.createElement('canvas');cv.width=Math.ceil(vp.width);cv.height=Math.ceil(vp.height);return pg.render({canvasContext:cv.getContext('2d'),viewport:vp}).promise.then(function(){var u=cv.toDataURL('image/jpeg',0.8);pdfCache[url]=u;ph.innerHTML='<img src="'+u+'" alt="">';});}).catch(function(){});}catch(e){}}
|
|
304
|
+
function pdfThumb(url,ph){if(!ph)return;if(pdfCache[url]){ph.innerHTML='<img src="'+pdfCache[url]+'" alt="">';return;}if(!window.pdfjsLib)return;try{pdfjsLib.getDocument({url:url,isEvalSupported:false}).promise.then(function(pdf){return pdf.getPage(1);}).then(function(pg){var v0=pg.getViewport({scale:1}),sc=Math.min(1.6,208/v0.width),vp=pg.getViewport({scale:sc}),cv=document.createElement('canvas');cv.width=Math.ceil(vp.width);cv.height=Math.ceil(vp.height);return pg.render({canvasContext:cv.getContext('2d'),viewport:vp}).promise.then(function(){var u=cv.toDataURL('image/jpeg',0.8);pdfCache[url]=u;ph.innerHTML='<img src="'+u+'" alt="">';});}).catch(function(){});}catch(e){}}
|
|
290
305
|
// Renvoie true seulement si le message a réellement été AJOUTÉ. Pendant la transition, il
|
|
291
306
|
// arrive par deux voies (diffusion et lecture de table) : sans cette réponse, l'affichage était
|
|
292
307
|
// bien dédoublonné mais le compteur de non-lus comptait deux fois — une pastille à 2 pour un
|
|
@@ -1934,7 +1949,7 @@ ${LEGAL_CSS}
|
|
|
1934
1949
|
}
|
|
1935
1950
|
function render(){
|
|
1936
1951
|
if(IS_IMG){ renderImage(); return; }
|
|
1937
|
-
var task=pdfjsLib.getDocument(CFG.fileUrl);
|
|
1952
|
+
var task=pdfjsLib.getDocument({url:CFG.fileUrl,isEvalSupported:false});
|
|
1938
1953
|
task.onProgress=function(p){ if(p&&p.total){ var pct=Math.max(8,Math.min(99,Math.round(p.loaded/p.total*100))); var bar=document.getElementById('lbar'); if(bar)bar.classList.remove('idle'); var f=document.getElementById('lbarFill'); if(f)f.style.width=pct+'%'; var l=document.getElementById('lpct'); if(l)l.textContent=pct+' %'; } };
|
|
1939
1954
|
task.promise.then(function(pdf){
|
|
1940
1955
|
pdfDoc=pdf; numPages=pdf.numPages; window.__n=pdf.numPages; T.setPageCount(pdf.numPages);
|
|
@@ -2161,7 +2176,7 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
|
|
|
2161
2176
|
}).catch(function(){ pdfjsLib.GlobalWorkerOptions.workerSrc=wsrc; load(); });
|
|
2162
2177
|
}
|
|
2163
2178
|
function load(){
|
|
2164
|
-
pdfjsLib.getDocument(CFG.fileUrl).promise.then(function(pdf){
|
|
2179
|
+
pdfjsLib.getDocument({url:CFG.fileUrl,isEvalSupported:false}).promise.then(function(pdf){
|
|
2165
2180
|
PDF=pdf; total=pdf.numPages; ready=true;
|
|
2166
2181
|
var tot=document.getElementById('tot'); if(tot)tot.textContent=total;
|
|
2167
2182
|
show(cur); hideLoader();
|
|
@@ -2750,7 +2765,21 @@ async function handler(req, res) {
|
|
|
2750
2765
|
refusEnvoi = "no-recipient";
|
|
2751
2766
|
throw new Error("envoi réservé aux liens nominatifs");
|
|
2752
2767
|
}
|
|
2753
|
-
|
|
2768
|
+
// ⚠️ `Host` EST CHOISI PAR LE CLIENT. Le lien inséré dans un email signé par l'hôte
|
|
2769
|
+
// était construit avec cet en-tête : sur le serveur autonome, ou derrière un proxy qui
|
|
2770
|
+
// ne le réécrit pas strictement, un lecteur pouvait demander un envoi parfaitement
|
|
2771
|
+
// légitime dont le bouton pointe vers SON domaine. L'email part de l'hôte, avec sa
|
|
2772
|
+
// marque et sa réputation, vers le destinataire choisi par l'attaquant.
|
|
2773
|
+
//
|
|
2774
|
+
// `PLAYER_PUBLIC_URL` d'abord — une valeur que l'exploitant a écrite. Le repli sur
|
|
2775
|
+
// `Host` reste pour ne casser aucune instance existante, mais il est SIGNALÉ : une
|
|
2776
|
+
// instance qui envoie des emails sans URL publique configurée doit le savoir avant de
|
|
2777
|
+
// le découvrir dans un rapport d'hameçonnage.
|
|
2778
|
+
const publique = String(PLAYER.legal.publicUrl || "").trim();
|
|
2779
|
+
if (!publique) {
|
|
2780
|
+
try { PLAYER.errors.capture(new Error("PLAYER_PUBLIC_URL non configurée : le lien de l'email est construit depuis l'en-tête Host, que le client choisit"), { route: "reshare" }); } catch { /* jamais bloquant */ }
|
|
2781
|
+
}
|
|
2782
|
+
const origin = publique || `https://${req.headers.host}`;
|
|
2754
2783
|
const r = await sendReshareEmail({ parent, childSlug: out.slug, origin, toEmail: mail, toName: body.name });
|
|
2755
2784
|
sent = !!(r && r.sent);
|
|
2756
2785
|
} catch { /* best-effort : le lien existe quand même */ }
|