discovery-media-player 0.1.23 → 0.1.24

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.
@@ -142,10 +142,24 @@ const TYPES = {
142
142
  * des premières pages avant la fin du téléchargement.
143
143
  */
144
144
  async function readLocal(cible, range) {
145
- let stat;
146
- try { stat = await fsp.stat(cible); } catch { return null; }
147
- if (!stat.isFile()) return null;
145
+ // ⚠️ ON OUVRE D'ABORD, ON INTERROGE ENSUITE. `stat(chemin)` puis `open(chemin)` sont deux
146
+ // résolutions du même NOM à deux instants : entre les deux, le fichier peut avoir été remplacé.
147
+ // On lisait alors les octets du NOUVEAU fichier avec la taille de l'ANCIEN — un `Content-Range`
148
+ // qui ne décrit pas ce qu'il transporte, donc un document faux sans qu'aucune erreur ne sorte.
149
+ // Un descripteur, lui, désigne un objet et non un nom : `fh.stat()` parle du même fichier que
150
+ // `fh.read()`, quoi qu'il advienne du chemin entre-temps.
151
+ let fh;
152
+ try { fh = await fsp.open(cible, "r"); } catch { return null; }
153
+ try {
154
+ const stat = await fh.stat();
155
+ if (!stat.isFile()) return null;
156
+ return await lireDepuis(fh, stat, cible, range);
157
+ } finally {
158
+ await fh.close();
159
+ }
160
+ }
148
161
 
162
+ async function lireDepuis(fh, stat, cible, range) {
149
163
  const type = TYPES[path.extname(cible).toLowerCase()] || "application/octet-stream";
150
164
  const total = stat.size;
151
165
  const m = /^bytes=(\d*)-(\d*)$/.exec(String(range || ""));
@@ -159,17 +173,12 @@ async function readLocal(cible, range) {
159
173
  statut = 206;
160
174
  }
161
175
 
162
- const fh = await fsp.open(cible, "r");
163
- try {
164
- const buf = Buffer.alloc(fin - debut + 1);
165
- const { bytesRead } = await fh.read(buf, 0, buf.length, debut);
166
- const corps = bytesRead === buf.length ? buf : buf.subarray(0, bytesRead);
167
- const entetes = { "content-type": type };
168
- if (statut === 206) entetes["content-range"] = `bytes ${debut}-${fin}/${total}`;
169
- return reponse(statut, entetes, corps);
170
- } finally {
171
- await fh.close();
172
- }
176
+ const buf = Buffer.alloc(fin - debut + 1);
177
+ const { bytesRead } = await fh.read(buf, 0, buf.length, debut);
178
+ const corps = bytesRead === buf.length ? buf : buf.subarray(0, bytesRead);
179
+ const entetes = { "content-type": type };
180
+ if (statut === 206) entetes["content-range"] = `bytes ${debut}-${fin}/${total}`;
181
+ return reponse(statut, entetes, corps);
173
182
  }
174
183
 
175
184
  function reponse(status, entetes, corps) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discovery-media-player",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
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
@@ -2755,7 +2755,13 @@ async function handler(req, res) {
2755
2755
  if (body.action === "reshare") {
2756
2756
  const mail = String(body.email || "").trim().toLowerCase();
2757
2757
  const j = (status, obj) => { res.statusCode = status; res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(obj)); };
2758
- if (!/.+@.+\..+/.test(mail)) return j(400, { ok: false, error: "email" });
2758
+ // ⚠️ LA LONGUEUR AVANT LE MOTIF — et ici ce n'est pas de la prudence, c'est mesuré :
2759
+ // `.+@.+\..+` reprend à chaque position de départ, coût 49 ms sur 10 000 caractères,
2760
+ // 3 900 ms sur 100 000. Une seule requête, sur une route ouverte au lecteur, bloquait la
2761
+ // boucle d'événements quatre secondes — l'instance entière, pas seulement l'appelant.
2762
+ // Le débit (8/h par IP) ne protégeait rien : il est vérifié APRÈS, deux lignes plus bas.
2763
+ // 254 est le maximum d'une adresse (RFC 5321) : au-delà ce n'est pas « long », c'est faux.
2764
+ if (mail.length > 254 || !/.+@.+\..+/.test(mail)) return j(400, { ok: false, error: "email" });
2759
2765
  const ip = adresseAppelant(req) || "anon";
2760
2766
  const allowed = await PLAYER.limits.allow(`reshare:${ip}`, 8, 3600);
2761
2767
  if (!allowed) return j(429, { ok: false, error: "rate", message: "Trop de partages, réessayez plus tard." });
package/server/shares.js CHANGED
@@ -58,6 +58,10 @@ async function createReshare(parentSlug, { email, name }) {
58
58
  // rapport qui a mené ici.
59
59
  // • `allow_download` (défaut `true`) — le bouton Télécharger revenait sur un document où il
60
60
  // avait été refusé.
61
+ // ⚠️ À ne pas prendre pour une protection : c'est une PRÉFÉRENCE D'AFFICHAGE. Le lecteur qui
62
+ // voit le document en a déjà les octets ; masquer le bouton retire une commodité, pas un
63
+ // accès. Un document qu'on ne veut pas voir sortir ne doit pas être partagé, ou doit l'être
64
+ // derrière `require_auth` — celui-là, lui, décide de qui obtient les octets.
61
65
  // • `brand_key` — la marque se perdait à l'endroit exact où le document commence à circuler :
62
66
  // le lecteur d'un document VALONEUF transmettait un lien qui s'ouvre sous une autre marque.
63
67
  //