diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..43c7107 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,177 @@ +# Changelog + +Todos los cambios publicados de **drop**, de más reciente a más antiguo. + +El formato sigue [Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/) y el versionado es +[SemVer](https://semver.org/lang/es/). Mientras la versión mayor sea `0`, una versión menor +puede romper compatibilidad; cuando pasa, se dice aquí. + +Las notas de cada release, con los binarios, están en +[Releases](https://github.com/Oloxx/drop/releases). + +## [Sin publicar] + +### Añadido +- Tests en Linux, macOS y Windows antes de desplegar a producción, y la suite arranca su propio + servidor de señalización: `npm test` ya no necesita nada levantado a mano (#22, #23). +- `LICENSE` (Apache 2.0), `CONTRIBUTING.md` y este `CHANGELOG.md` (#31). +- Cabeceras de seguridad en todas las respuestas: CSP ajustada a lo que la web usa de verdad, + `X-Content-Type-Options`, `Referrer-Policy` y `Permissions-Policy`; HSTS en Caddy (#30). + +### Cambiado +- La versión del CLI sale solo del `package.json` y esbuild la mete en el binario al compilar: + se acabó la constante escrita a mano que descuadraba `drop update` (#26). +- El contenedor corre como el usuario `node`, sin privilegios, y declara un `HEALTHCHECK` (#30). +- Una sola implementación de SHA-256, en `public/shared/sha256.js`, que comparten la web y sus + tests: antes el test validaba una copia del código (#25). + +## [0.5.2] — 2026-09-11 + +### Añadido +- **Releases firmadas con Ed25519** en formato minisign. Cada release lleva `SHA256SUMS` y + `SHA256SUMS.minisig`: el hash dice que el binario llegó entero, la firma dice quién lo + publicó (#16). +- `drop update` **rechaza una release sin firma**; `--allow-unsigned` es el escape para las + anteriores a esta versión. + +## [0.5.1] — 2026-09-10 + +### Añadido +- **TURN efímero**: `/config` firma credenciales que caducan (12 h por defecto) en vez de + repartir un usuario y una contraseña perpetuos, con lo que el relay dejaba de ser propio + (#2). +- **Cuotas en el servidor de señalización**: salas por IP y minuto, receptores por sala, salas + totales, caducidad por inactividad y un cubo de mensajes por socket. `maxPayload` baja de los + 100 MiB que trae `ws` por defecto a 256 KiB (#7). +- **Huella corta de sesión (SAS)** para detectar a un intermediario comparando cuatro palabras + por otro canal, y confirmación explícita del emisor antes de servir a un receptor (#14, #15). + +### Corregido +- El CLI cierra el descriptor del archivo en `finally`, y deja de avisar de `DEP0137`. + +## [0.5.0] — 2026-09-06 + +### Corregido +- **Path traversal y sobrescrituras en el receptor**: los nombres que llegan en el manifiesto se + sanean antes de tocar el disco, cada archivo se escribe en `.part` y solo se renombra cuando + el SHA-256 cuadra (#17, #18, #19). +- Errores legibles en lugar de volcados de pila: `EADDRINUSE`, código equivocado, versión de + protocolo distinta. + +## [0.4.2] — 2026-09-05 + +### Corregido +- El mapeo UPnP se renueva periódicamente, se limpia al salir y reintenta si el puerto está + cogido. + +## [0.4.1] — 2026-09-05 + +### Corregido +- Path traversal en el receptor del CLI. +- El relay se colgaba al pasar de 8 MB: el receptor no acusaba recibo y el emisor se quedaba + esperando con la ventana llena. + +## [0.4.0] — 2026-09-05 + +### Añadido +- **Códigos de sala memorizables** del tipo `4271-lemon-radar-tiger-orbit`, que se pueden dictar + por teléfono. +- **Releases automáticas** al empujar un tag: los cinco binarios se compilan y **se ejecutan** en + su plataforma nativa antes de publicarse, con checksums y firma ad-hoc en macOS. + +### Corregido +- El blob SEA se genera con el Node exacto de los binarios base: mezclarlos producía ejecutables + que morían al arrancar en todas las plataformas a la vez. + +## [0.3.5] — 2026-09-04 + +### Añadido +- **Mapeo automático de puertos por UPnP** y detección de la IP pública, para conexiones + directas por internet sin tocar el router. +- Sondeo concurrente al estilo *Happy Eyeballs*: se prueban en paralelo LAN, VPN y WAN y se + adopta la primera que responde. +- `-p, --port` para fijar el puerto de escucha. + +## [0.3.4] — 2026-09-04 + +### Corregido +- Condiciones de carrera en enlaces de 1 Gbps: cola de mensajes de control persistente para que + el cambio de fase no se pierda bajo carga. + +## [0.3.3] — 2026-09-04 + +### Cambiado +- El medidor de velocidad deja de cifrar el relleno, que era el cuello de botella en Raspberry + Pi y equipos ARM: ahora mide la red y no la CPU. + +## [0.3.2] — 2026-09-04 + +### Añadido +- Broadcast UDP por todas las interfaces, para equipos con varias tarjetas de red. + +## [0.3.1] — 2026-09-04 + +### Corregido +- Solapamiento de texto en la consola al reportar la latencia. + +## [0.3.0] — 2026-09-04 + +### Añadido +- **Verificación de integridad SHA-256** de cada archivo en las dos puntas. +- **`drop speed`**: medidor de velocidad entre dos terminales, con RTT, medición en los dos + sentidos y elección automática entre TCP directo y relay. + +## [0.2.4] — 2026-09-03 + +### Añadido +- Resumen y métricas al terminar una transferencia. + +## [0.2.3] — 2026-09-03 + +### Añadido +- **`drop update`**: comprueba la última release en GitHub y sustituye el binario instalado. +- Los binarios llevan la versión en el nombre. + +## [0.2.2] — 2026-09-03 + +### Cambiado +- El canal del emisor se queda abierto hasta `Ctrl+C`, para que varias personas descarguen a la + vez o una detrás de otra. +- Ejecutar `drop recv` desde `System32` redirige la descarga a la carpeta de descargas del + usuario, en vez de fallar con `EPERM`. + +## [0.2.1] — 2026-09-03 + +### Corregido +- El receptor esperaba un mensaje con otro nombre que el que mandaba el emisor. +- Caída automática a relay por WebSocket cuando la ruta directa no es posible. + +## [0.2.0] — 2026-09-03 + +Primera release con binarios autónomos. + +### Añadido +- **CLI** de transferencia P2P por TCP con AES-256-GCM, a 100-115 MB/s. +- Ejecutables para las cinco plataformas, sin Node.js instalado, que se añaden solos al PATH. +- **Interoperabilidad CLI ↔ web**: lo enviado desde la terminal se descarga desde cualquier + navegador. +- Descubrimiento en la red local por broadcast UDP. + +[Sin publicar]: https://github.com/Oloxx/drop/compare/v0.5.2...HEAD +[0.5.2]: https://github.com/Oloxx/drop/compare/v0.5.1...v0.5.2 +[0.5.1]: https://github.com/Oloxx/drop/compare/v0.5.0...v0.5.1 +[0.5.0]: https://github.com/Oloxx/drop/compare/v0.4.2...v0.5.0 +[0.4.2]: https://github.com/Oloxx/drop/compare/v0.4.1...v0.4.2 +[0.4.1]: https://github.com/Oloxx/drop/compare/v0.4.0...v0.4.1 +[0.4.0]: https://github.com/Oloxx/drop/compare/v0.3.5...v0.4.0 +[0.3.5]: https://github.com/Oloxx/drop/compare/v0.3.4...v0.3.5 +[0.3.4]: https://github.com/Oloxx/drop/compare/v0.3.3...v0.3.4 +[0.3.3]: https://github.com/Oloxx/drop/compare/v0.3.2...v0.3.3 +[0.3.2]: https://github.com/Oloxx/drop/compare/v0.3.1...v0.3.2 +[0.3.1]: https://github.com/Oloxx/drop/compare/v0.3.0...v0.3.1 +[0.3.0]: https://github.com/Oloxx/drop/compare/v0.2.4...v0.3.0 +[0.2.4]: https://github.com/Oloxx/drop/compare/v0.2.3...v0.2.4 +[0.2.3]: https://github.com/Oloxx/drop/compare/v0.2.2...v0.2.3 +[0.2.2]: https://github.com/Oloxx/drop/compare/v0.2.1...v0.2.2 +[0.2.1]: https://github.com/Oloxx/drop/compare/v0.2.0...v0.2.1 +[0.2.0]: https://github.com/Oloxx/drop/releases/tag/v0.2.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4eb3ea2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,113 @@ +# Contribuir a drop + +Gracias por querer echar una mano. Este documento es lo que hace falta saber para que un +cambio entre sin ida y vuelta: cómo montar el entorno, cómo probar lo que tocas y qué +convenciones sigue el repositorio. + +## Montar el entorno + +Solo hace falta **Node.js >= 18** (el CI y los binarios usan la 22). + +```bash +git clone https://github.com/Oloxx/drop.git +cd drop +npm install + +npm run dev # http://localhost:3000, recarga sola al guardar +``` + +No hay base de datos, ni servicios externos, ni claves que pedir: las salas viven en memoria +del proceso y los archivos nunca pasan por el servidor. + +Para probar el CLI sin compilar nada: + +```bash +npm run cli -- send fichero.zip +npm run cli -- recv 4271-lemon-radar-tiger-orbit +``` + +## Tests + +```bash +npm test +``` + +La suite arranca su propio servidor de señalización en un puerto libre y lo apaga al terminar, +así que **no hay que levantar nada antes**. Un caso suelto: + +```bash +node --test --test-name-pattern "NOT_FOUND" test/signaling.test.mjs +``` + +Todo cambio de comportamiento necesita un test. Los que hay ya marcan el tono: cada uno explica +en un comentario **qué se rompió o qué se rompería** si eso dejara de cumplirse, no lo que hace +la línea de abajo. + +Si tocas rendimiento (troceado, contrapresión, el bucle de acuses), los números salen de los +benchmarks, y **hay que comparar medianas de tres ejecuciones**: la dispersión entre ejecuciones +es de ~1 MB/s y una sola no dice nada. + +```bash +npm run bench # transferencia real entre dos pestañas de Chrome +npm run bench:cli # TCP nativo entre dos procesos del CLI +npm run bench:fanout # cadena de reenvío con varios receptores +``` + +Los benchmarks necesitan un servidor levantado (`npm run dev`) y, los de navegador, Chrome +instalado (`CHROME_PATH` si no está donde se espera). + +## Convenciones + +**Idiomas.** El código, los comentarios, los commits, las issues y los PR van en **español**. +Los textos de la interfaz web van en **inglés**, en minúscula y escuetos (`open channel`, +`transmitting…`, `delivered`). No es capricho: mezclarlo ya pasó y quedó a medias. + +**Comentarios.** Se comenta el *porqué*, no el *qué*. Un comentario que repite el nombre de la +función sobra; uno que dice "esto no puede ir en el canal directo porque adelanta a los últimos +trozos y cierra el archivo a medias" es el que evita que alguien lo simplifique dentro de seis +meses. Si algo parece rebuscado y no lo es, explica el caso que lo justifica. + +**Commits.** [Conventional Commits](https://www.conventionalcommits.org/es/), en español y en +imperativo: + +``` +fix(cli): cerrar el descriptor en finally para no filtrarlo al fallar + +Cuerpo opcional: qué pasaba antes, por qué se arregla así y no de otra +forma. Las referencias a issues, al final. + +Cierra #21. +``` + +Tipos en uso: `feat`, `fix`, `sec`, `refactor`, `test`, `docs`, `build`, `ci`, `chore`. + +**Pull requests.** Uno por tema, contra `main`. En el cuerpo: qué problema resuelve, qué se ha +verificado y cómo. Si cierra issues, usa las palabras clave en inglés (`closes #21`) — GitHub no +entiende "cierra #21" y la issue se queda abierta. + +El CI ejecuta la suite en Linux, macOS y Windows, y tiene que estar en verde antes de mezclar: +un push a `main` despliega a producción. + +**Dependencias.** La web hace **cero peticiones a terceros** y así se queda: la fuente está +servida desde `public/fonts/`, no hay CDN ni analítica. En el servidor hay dos dependencias +(`express` y `ws`) y en el CLI ninguna. Añadir una tiene que justificarse; casi siempre la +respuesta es la biblioteca estándar de Node. + +**Antes de tocar el protocolo o la cadena de reenvío**, lee las cabeceras de +[`public/app.js`](public/app.js) y [`cli/src/transfer.js`](cli/src/transfer.js). Documentan las +decisiones que parecen simplificables y no lo son -- por qué los trozos y los mensajes de +control van por canales distintos, por qué un relay tiene que volver a trocear lo que reenvía -- +y cada una está ahí porque romperla costó una tarde. + +## Seguridad + +Si encuentras un fallo con impacto en seguridad, **no abras una issue pública**: escribe por +[advisory privado](https://github.com/Oloxx/drop/security/advisories/new). Lo que trata el +proyecto como parte de su modelo de amenazas está en el README; en resumen: el +servidor no ve los archivos, el token de sala es un secreto que viaja en el fragmento de la URL, +y las releases van firmadas. + +## Licencia + +Al contribuir aceptas que tu código se publique bajo la [Apache License 2.0](LICENSE), que es la +del proyecto. diff --git a/Caddyfile b/Caddyfile index 658c958..93b23ab 100644 --- a/Caddyfile +++ b/Caddyfile @@ -1,5 +1,16 @@ # Caddy pide y renueva el certificado solo. Los WebSockets pasan sin configurar nada. {$DROP_DOMAIN} { encode gzip + + # HSTS vive aqui y no en la aplicacion porque es lo unico que depende de donde + # acaba el TLS: anunciarlo desde un servidor que tambien habla http (el `npm + # run dev` de cualquiera) dejaria ese localhost inaccesible en el navegador + # durante meses. El resto de cabeceras (CSP, nosniff, Referrer-Policy) las + # pone server/index.js, para que viajen con la aplicacion y se prueben. + # + # Dos anos, subdominios incluidos. Sin `preload`: pedir la lista de precarga + # es un camino de ida, y el dominio tendria que servir HTTPS para siempre. + header Strict-Transport-Security "max-age=63072000; includeSubDomains" + reverse_proxy drop:3000 } diff --git a/Dockerfile b/Dockerfile index e43e040..668c91a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,5 +12,20 @@ RUN npm ci --omit=dev COPY server ./server COPY public ./public +# La imagen trae un usuario `node` sin privilegios que no se estaba usando: el +# proceso corria como root, asi que cualquier ejecucion de codigo dentro del +# contenedor empezaba con todo. Los ficheros se copian antes y quedan de root, +# que es justo lo que se quiere: el servidor solo tiene que leerlos, y no puede +# reescribir su propio codigo. +USER node + EXPOSE 3000 + +# `/healthz` ya existia y solo lo miraba fly.toml. Aqui hace que Docker sepa si +# el proceso sigue sirviendo, no solo si sigue vivo: un servidor colgado con el +# bucle de eventos bloqueado no responde y el contenedor pasa a `unhealthy`. +# Sin curl en la imagen, se pregunta con el propio Node. +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||3000)+'/healthz').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))" + CMD ["node", "--env-file-if-exists=.env", "server/index.js"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d5b3eb3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 Àlex Rodriguez (Oloxx) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/README.md b/README.md index 44845fb..5e80ad8 100644 --- a/README.md +++ b/README.md @@ -346,3 +346,23 @@ El servidor solo acepta WebSockets de navegador desde su propio dominio (`DROP_D localhost, para que una web cualquiera no pueda abrir salas con el navegador de quien la visita. Si sirves el frontend desde otro sitio, añade el origen con `DROP_ALLOWED_ORIGINS`. Las conexiones sin cabecera `Origin` —el CLI— no se ven afectadas. + +Las respuestas llevan una **CSP** ajustada a lo que la web usa de verdad —ni scripts ni estilos +en línea, ni una sola petición a terceros—, además de `X-Content-Type-Options: nosniff`, +`Referrer-Policy: no-referrer` y `Permissions-Policy` sin cámara, micrófono ni ubicación. HSTS lo +pone Caddy, que es donde acaba el TLS. El contenedor corre como usuario sin privilegios y declara +un `HEALTHCHECK` contra `/healthz`. + +--- + +## 🤝 Contribuir + +Las convenciones del proyecto (idiomas, estilo de comentarios, formato de commits) y cómo montar +el entorno están en **[CONTRIBUTING.md](CONTRIBUTING.md)**. El historial de versiones, en +**[CHANGELOG.md](CHANGELOG.md)**. + +## 📄 Licencia + +[Apache License 2.0](LICENSE). Puedes usar, modificar y redistribuir drop, incluso +comercialmente, conservando el aviso de copyright y la licencia, e indicando los cambios que +hagas. La licencia incluye además una concesión expresa de patentes. diff --git a/cli/src/cli.js b/cli/src/cli.js index d74b56b..d96d888 100644 --- a/cli/src/cli.js +++ b/cli/src/cli.js @@ -664,6 +664,12 @@ const pendingProofs = new Map(); // IP con la que cada receptor entró en la sala, para poder decir a quién se sirve. const guestIps = new Map(); +// Lado EMISOR del protocolo de relay del CLI: manda cli-start, los trozos +// binarios, cli-end y cli-done, y avanza la ventana con los cli-ack que le +// llegan. El protocolo entero -- cada mensaje, quien lo emite y que espera de +// vuelta -- esta descrito en cli/src/transfer.js, encima de receiveFromRelay. +// Cambiar algo aqui sin mirar alli es como se llego a que el receptor no +// acusara recibo y el envio se parase a los 8 MB. async function streamToWebGuest(guestId, files, ws, onProgress) { const CHUNK = 64 * 1024; const MAX_IN_FLIGHT = 8 * 1024 * 1024; // Ventana deslizante de 8 MB máximo sin confirmar diff --git a/cli/src/transfer.js b/cli/src/transfer.js index 2b012fc..7ffb444 100644 --- a/cli/src/transfer.js +++ b/cli/src/transfer.js @@ -658,8 +658,81 @@ export function receiveFiles(host, port, code, outputDir, onProgress, connectTim }); } +// ============================================================================ +// PROTOCOLO DE RELAY DEL CLI (la descripcion canonica: esta solo aqui) +// ============================================================================ +// +// Cuando no hay ruta TCP directa -- NAT estricta, cortafuegos, o el receptor es +// un navegador -- los bytes viajan por el WebSocket de senializacion. Hay TRES +// implementaciones parciales de esto: el emisor en cli/src/cli.js +// (`streamToWebGuest` y el bucle de `signal`), el receptor de aqui +// (`receiveFromRelay`) y el receptor web en public/app.js (`routeSignal`). +// Estaban sin describir en ningun sitio, y las divergencias entre ellas son +// exactamente la causa de que el receptor CLI no mandase los acuses que el +// emisor esperaba y la transferencia se parase en seco a los 8 MB. +// +// El servidor no entiende nada de esto: reenvia `{t:'signal', to, data}` a quien +// diga `to`, dentro de la misma sala, y los frames binarios tal cual (del emisor +// llevan 4 bytes de guestId por delante que el servidor quita; del receptor van +// siempre al emisor). Ver la rama binaria de server/index.js. +// +// MENSAJES, en el orden en que ocurren +// +// cli-offer emisor -> receptor. Al entrar alguien en la sala. Lleva `v` +// (PROTOCOL_VERSION), las IPs y el puerto para intentar TCP +// directo, y un `nonce` nuevo por receptor. NO lleva el +// manifiesto: acertar una sala son 4 digitos y los nombres de +// los archivos ya son informacion. +// cli-proof receptor -> emisor. `secretProof(nonce, secreto)`: demuestra +// que sabe las palabras del codigo. Solo lo manda quien va a +// comer por el relay; por TCP directo la prueba es que AES-GCM +// autentique. +// cli-denied emisor -> receptor. El codigo era bueno pero quien envia ha +// dicho que no. Es un rechazo, no un fallo de emparejamiento. +// cli-manifest emisor -> receptor. La lista de archivos, ya autorizada. +// cli-accept receptor -> emisor. "Listo para recibir": abre el envio. +// +// cli-start emisor -> receptor. Empieza el archivo `index`, con nombre, +// tamano y mime. Un `cli-start` con un archivo aun abierto +// significa que el emisor se salto su `cli-end`: lo que hubiera +// a medias no esta verificado y su `.part` se tira. +// (binario) emisor -> receptor. Trozos de 64 KiB del archivo en curso, en +// orden. No llevan cabecera: el receptor solo cuenta bytes. +// cli-ack receptor -> emisor. Bytes totales recibidos, cada +// RELAY_ACK_EVERY (2 MB). Ver el control de flujo abajo. +// cli-end emisor -> receptor. Cierra el archivo `index` con su +// `sha256`. El receptor compara, borra el `.part` si no cuadra +// y solo entonces renombra al nombre definitivo. +// cli-done emisor -> receptor. No quedan archivos. +// cli-complete receptor -> emisor. Se manda con TODO ya escrito en disco, no +// al recibir `cli-done`: es lo que permite al emisor dar la +// transferencia por buena y soltar la ventana. +// +// cli-retry receptor -> emisor. Reenvia desde el archivo `index` +// (inclusive). Lo usa el receptor web cuando un hash no cuadra. +// cli-error receptor -> emisor. Algo se ha roto de este lado; el emisor +// suelta la ventana y da ese receptor por perdido. +// +// CONTROL DE FLUJO: es lo que se rompio, y es una ventana de acuses +// +// El emisor no manda mas de MAX_IN_FLIGHT (8 MB, cli.js) sin confirmar, y se +// para tambien si su propio `ws.bufferedAmount` pasa de 4 MB. Lo unico que mueve +// esa marca es el `cli-ack` del receptor. Un receptor que no acuse recibo no va +// "un poco mas lento": los dos extremos se quedan esperando para siempre en +// cuanto se llenan los 8 MB. Por eso los dos receptores acusan cada 2 MB +// (RELAY_ACK_EVERY aqui, ACK_EVERY en public/app.js) y ese numero tiene que +// quedarse muy por debajo de la ventana. +// +// El acuse dice BYTES TOTALES de la transferencia, no del archivo: es +// monotono creciente y el emisor se queda con el maximo, asi que un acuse que +// llegue tarde o repetido no hace retroceder nada. +// +// Y hay un reloj de inactividad en el receptor (RELAY_IDLE_TIMEOUT_MS) que se +// rearma con cualquier senial de vida: sin el, un fallo del emisor dejaba al +// receptor colgado sin error y sin salida. /** - * Cliente Relay que recibe los archivos en streaming a través del WebSocket de señalización + * Cliente Relay que recibe los archivos en streaming a traves del WebSocket de + * senializacion. La descripcion del protocolo esta justo arriba. */ export function receiveFromRelay(ws, manifest, outputDir, onProgress, options = {}) { const { overwrite = false } = options; diff --git a/package.json b/package.json index 2380cb7..7604ec5 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,9 @@ "name": "drop", "version": "0.5.2", "private": true, + "license": "Apache-2.0", + "author": "Àlex Rodriguez (Oloxx)", + "repository": { "type": "git", "url": "git+https://github.com/Oloxx/drop.git" }, "type": "module", "description": "Envio de archivos P2P entre navegadores (WebRTC). El servidor solo empareja: los archivos nunca pasan por el.", "scripts": { diff --git a/public/app.js b/public/app.js index a4976a5..3c843ab 100644 --- a/public/app.js +++ b/public/app.js @@ -1308,6 +1308,13 @@ async function acceptTransfer() { sendHost({ k: 'accept' }); } +// Lado RECEPTOR WEB del protocolo de relay del CLI (los mensajes `cli-*`): el +// emisor es un `drop send` y los bytes pasan por el servidor porque el +// navegador no habla el TCP del CLI. El protocolo esta descrito entero en +// cli/src/transfer.js, encima de receiveFromRelay; aqui hay que mantener sobre +// todo el `cli-ack` cada ACK_EVERY, que es lo que mueve la ventana del emisor. +// +// Lo demas que pasa por aqui es la senializacion normal de WebRTC entre pares. function routeSignal(from, data) { if (data.type === 'cli-offer') { rx.isCli = true; diff --git a/public/index.html b/public/index.html index e939912..b83710b 100644 --- a/public/index.html +++ b/public/index.html @@ -99,7 +99,7 @@
integrity discrepancy
+integrity discrepancy