discovery-media-player 0.1.19 → 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 +44 -5
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
|
|
@@ -399,6 +414,11 @@ var Live=(function(){
|
|
|
399
414
|
ch.on('presence',{event:'sync'},function(){renderPres(ch.presenceState());});
|
|
400
415
|
// Plus d'abonnement à la table des messages : elle n'est plus publiée ni lisible
|
|
401
416
|
// publiquement. Tout passe par la diffusion, et l'historique par la route de chat.
|
|
417
|
+
// ⚠️⚠️ TOUT CE BLOC VIT DANS UN TEMPLATE LITERAL : AUCUN BACKTICK, MÊME EN COMMENTAIRE.
|
|
418
|
+
// Un seul termine la chaîne qui porte tout le script navigateur, et l'erreur remonte
|
|
419
|
+
// ailleurs — « Unexpected token » sur une ligne qui n'a rien fait. Cinq fois sur ce fichier.
|
|
420
|
+
// Le lint l'attrape à chaque fois ; ça coûte un aller-retour, pas une panne.
|
|
421
|
+
//
|
|
402
422
|
// ⚠️ UNE DIFFUSION EST UN SIGNAL, PAS UNE VÉRITÉ.
|
|
403
423
|
//
|
|
404
424
|
// Ce canal est PUBLIC : la clé publiable et le slug sont dans la page, donc tout participant
|
|
@@ -433,7 +453,12 @@ var Live=(function(){
|
|
|
433
453
|
},120);}
|
|
434
454
|
function relireChat(){clearTimeout(_relChat);_relChat=setTimeout(function(){
|
|
435
455
|
relire('&chat=1',function(d){
|
|
436
|
-
|
|
456
|
+
// ⚠️ NOTIFIER CE QUI VIENT D'ARRIVER, ET SEULEMENT ÇA. 'addMsg' rend faux pour un
|
|
457
|
+
// message déjà connu — la relecture ramène tout l'historique, donc sans cette condition
|
|
458
|
+
// la pastille de non-lus compterait chaque message à chaque relecture. Et sans l'appel,
|
|
459
|
+
// elle ne compte plus rien : c'est la régression que le test d'un hôte a attrapée en
|
|
460
|
+
// lisant le source de ce paquet, à travers la frontière de deux dépôts.
|
|
461
|
+
if(d.messages)d.messages.forEach(function(m){if(addMsg(m))notifyMsg(m);else updateMsg(m);});
|
|
437
462
|
if(typeof d.locked!=='undefined')applyLock(d.locked);
|
|
438
463
|
});
|
|
439
464
|
},120);}
|
|
@@ -1924,7 +1949,7 @@ ${LEGAL_CSS}
|
|
|
1924
1949
|
}
|
|
1925
1950
|
function render(){
|
|
1926
1951
|
if(IS_IMG){ renderImage(); return; }
|
|
1927
|
-
var task=pdfjsLib.getDocument(CFG.fileUrl);
|
|
1952
|
+
var task=pdfjsLib.getDocument({url:CFG.fileUrl,isEvalSupported:false});
|
|
1928
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+' %'; } };
|
|
1929
1954
|
task.promise.then(function(pdf){
|
|
1930
1955
|
pdfDoc=pdf; numPages=pdf.numPages; window.__n=pdf.numPages; T.setPageCount(pdf.numPages);
|
|
@@ -2151,7 +2176,7 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
|
|
|
2151
2176
|
}).catch(function(){ pdfjsLib.GlobalWorkerOptions.workerSrc=wsrc; load(); });
|
|
2152
2177
|
}
|
|
2153
2178
|
function load(){
|
|
2154
|
-
pdfjsLib.getDocument(CFG.fileUrl).promise.then(function(pdf){
|
|
2179
|
+
pdfjsLib.getDocument({url:CFG.fileUrl,isEvalSupported:false}).promise.then(function(pdf){
|
|
2155
2180
|
PDF=pdf; total=pdf.numPages; ready=true;
|
|
2156
2181
|
var tot=document.getElementById('tot'); if(tot)tot.textContent=total;
|
|
2157
2182
|
show(cur); hideLoader();
|
|
@@ -2740,7 +2765,21 @@ async function handler(req, res) {
|
|
|
2740
2765
|
refusEnvoi = "no-recipient";
|
|
2741
2766
|
throw new Error("envoi réservé aux liens nominatifs");
|
|
2742
2767
|
}
|
|
2743
|
-
|
|
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}`;
|
|
2744
2783
|
const r = await sendReshareEmail({ parent, childSlug: out.slug, origin, toEmail: mail, toName: body.name });
|
|
2745
2784
|
sent = !!(r && r.sent);
|
|
2746
2785
|
} catch { /* best-effort : le lien existe quand même */ }
|