← Node.js: JavaScript fuera del navegador

Sesión 12 · Semana 4

Servir ficheros estáticos

Hoy · Hoja de ruta

  1. 1. Aprende: Cómo se sirve un fichero del disco y por qué eso es peligroso si no se hace bien.
  2. 2. Haz: Sirve desde tu servidor la web de las unidades anteriores.
  3. 3. Comprueba: No se puede pedir un fichero fuera de la carpeta pública.

Antes de empezar · 5 minutos, sin apuntes

  1. Hasta ahora, ¿quién servía tu web durante el desarrollo?
  2. ¿Cómo sabe el navegador que un fichero es CSS y no texto?
  3. ¿Qué pasaría si alguien pidiera /../../.env?

Servir un fichero

import { readFile } from "node:fs/promises";
import path from "node:path";

const PUBLICO = path.join(aqui, "..", "publico");

const TIPOS = {
  ".html": "text/html; charset=utf-8",
  ".css": "text/css; charset=utf-8",
  ".js": "text/javascript; charset=utf-8",
  ".json": "application/json; charset=utf-8",
  ".webp": "image/webp",
  ".svg": "image/svg+xml"
};

async function servirEstatico(ruta, respuesta) {
  const relativa = ruta === "/" ? "/index.html" : ruta;
  const destino = path.join(PUBLICO, relativa);

  if (!destino.startsWith(PUBLICO)) {
    return responderJson(respuesta, 403, { error: "Prohibido" });
  }

  try {
    const contenido = await readFile(destino);
    respuesta.writeHead(200, { "Content-Type": TIPOS[path.extname(destino)] ?? "application/octet-stream" });
    respuesta.end(contenido);
  } catch (error) {
    if (error.code === "ENOENT") return responderJson(respuesta, 404, { error: "No encontrado" });
    throw error;
  }
}

La comprobación que no puede faltar

Un servidor de ficheros sin comprobar la ruta es un agujero

Si compones la ruta con lo que pide el cliente y no compruebas nada, una petición con tramos .. sale de tu carpeta pública y llega a cualquier fichero que el proceso pueda leer: tu .env, tu código, ficheros del sistema.

Se llama path traversal y es una de las vulnerabilidades más antiguas que existen. La defensa es la de arriba: resolver la ruta completa y comprobar que sigue estando dentro de la carpeta permitida. Después de resolver, nunca antes.

El tipo de contenido

Si sirves un CSS sin su cabecera, el navegador recibe texto y no aplica los estilos, sin decir nada claro. Con un módulo de JavaScript es peor: se niega a ejecutarlo por el tipo incorrecto. Es un fallo que parece de tu código y es de tus cabeceras.

Servidor de estáticos y API a la vez

if (ruta.startsWith("/api/")) return atenderApi(peticion, respuesta, url);
return servirEstatico(ruta, respuesta);

Con esto, tu proyecto entero se sirve desde un solo sitio: la web en / y los datos en /api/. Al proceder todo del mismo origen, desaparece el problema de CORS que viste en la UD4.

Tarea 12 · Tu web, servida por ti

  1. Copia el sitio de la UD4 a publico/.
  2. Sírvelo desde tu servidor, con index.html por defecto.
  3. Resuelve el tipo de contenido por extensión.
  4. Protege contra rutas que salgan de la carpeta pública.
  5. Devuelve 404 con una página propia cuando el fichero no exista.
  6. Comprueba que el fetch de la UD4 funciona ahora contra tu propia API.
Objetivo mínimoLa web servida, con tipos correctos y rutas protegidas.
Si lo tienesAñade cabeceras de caché a las imágenes y comprueba el efecto en Network.
RetoDemuestra el ataque de path traversal contra una versión sin comprobación.

Cierre de la semana 4

  • Tienes un servidor propio que sirve web y API.
  • Cada situación devuelve su código de estado.
  • Los ficheros se sirven con su tipo correcto.
  • No se puede salir de la carpeta pública.
Ver respuestas

1 · Comprobar que la ruta resuelta sigue dentro de la carpeta pública.

2 · Por su extensión, para escribir la cabecera de tipo de contenido.

3 · Porque cliente y API pasan a compartir origen.

Microprueba semanal 4 · 5–10 minutos

Individual, sin IA y sin apuntes.

  1. Nombra las tres cosas que se escriben en una respuesta HTTP, con un ejemplo de cada una.
  2. Di qué código de estado devolverías al crear, al no encontrar, ante datos inválidos y ante un fallo interno.
  3. ¿Qué comprobación evita que alguien pida un fichero fuera de la carpeta pública?
---