# Shares, rutas y configuración

> Diseño previsto; aún no implementado.

## Un solo concepto: el share

En la 2.0 se distinguía "servir una web" y "compartir una carpeta". En la 3 son lo mismo: un
**share** publica una carpeta del disco en una *location* (ruta URL) y se ofrece **a la vez**
por web, FTP y, si se activa, WebDAV.

- Si la carpeta pedida tiene documento índice (`index.html` por defecto), la web lo sirve.
- Si no lo tiene y el listado está permitido, se muestra el navegador de directorios.
- Una URL directa a un fichero lo descarga (o el navegador lo muestra, según su tipo MIME).

## Locations: gana la más específica

Cada share tiene una `Location` (`/`, `/fotos`, `/fotos/viaje`…). Se resuelve como las
*locations* de prefijo de nginx: **gana el prefijo más largo**.

- El prefijo se compara **por segmentos completos**: `/foto` no captura `/fotos`.
- La comparación no distingue mayúsculas y minúsculas; el resto de la ruta sigue las reglas del
  sistema de ficheros del share.
- `/fotos` sin barra final redirige a `/fotos/`.

Ejemplo:

| Share | Location | Carpeta |
|---|---|---|
| web | `/` | `C:\www` |
| fotos | `/fotos` | `D:\Fotos` |
| viaje | `/fotos/2026/viaje` | `E:\Viaje` |

| Petición | Share | Ruta real |
|---|---|---|
| `/index.html` | web | `C:\www\index.html` |
| `/fotos/a.jpg` | fotos | `D:\Fotos\a.jpg` |
| `/fotos/2026/b.jpg` | fotos | `D:\Fotos\2026\b.jpg` |
| `/fotos/2026/viaje/c.jpg` | viaje | `E:\Viaje\c.jpg` |

### Directorios virtuales y ocultación

- Al listar una carpeta, las *locations* hijas aparecen como carpetas aunque no existan en
  disco: en `/fotos/` aparece `2026/` aunque `D:\Fotos\2026` no exista, y dentro `viaje/`.
- Si existe una carpeta real con el mismo nombre que una *location* hija, **queda tapada** por
  ella (como en nginx, lo específico tiene prioridad).
- Los segmentos intermedios sin share propio son directorios virtuales de solo lectura.
- FTP y WebDAV ven exactamente el mismo árbol que la web.

### La raíz

| Situación | `/` muestra |
|---|---|
| Hay un share en `/` | Ese share (con los demás como directorios virtuales) |
| No hay share en `/` y `RootMenu = true` | El menú de shares |
| No hay share en `/` y `RootMenu = false` | 404 (la web) / carpeta vacía (FTP) |

### Rutas reservadas

`/$sunfish/` está reservado para los recursos internos (iconos, CSS, JS), el panel de
administración (`/$sunfish/admin/`) y la autodescarga del ejecutable (`/$sunfish/download`).
Ninguna *location* puede empezar por `/$sunfish`.

## Validación de rutas

Toda ruta de web, FTP o WebDAV pasa por la misma validación antes de tocar el disco:

- Se decodifica (`%XX`) una sola vez y se normaliza el separador a `/`.
- Se rechazan: `..` y `.` como componentes, `\`, `:` (flujos alternativos de NTFS), NUL y
  caracteres de control, y rutas absolutas incrustadas.
- En Windows se rechazan además los nombres reservados (`CON`, `PRN`, `AUX`, `NUL`, `COM1`–`COM9`,
  `LPT1`–`LPT9`) y los nombres acabados en punto o espacio.
- Se calcula la ruta real y se comprueba que queda **dentro de la carpeta del share**. Los
  enlaces simbólicos y *junctions* cuyo destino sale del share no se siguen.

(La 2.0 hacía `Path.Combine` sin validar: este es el cambio de seguridad más importante.)

## Permisos

Por share, todos desactivados por defecto salvo la lectura:

| Opción | Permite |
|---|---|
| `Listing` | Ver el navegador de directorios cuando no hay índice (y listar por FTP/WebDAV) |
| `Upload` | Subir ficheros nuevos y crear carpetas |
| `Overwrite` | Subir sobre un fichero existente |
| `Rename` | Renombrar y mover dentro del share |
| `Delete` | Borrar ficheros y carpetas |
| `Zip` | Descargar una carpeta como ZIP |

**Usuarios:** si un share tiene `Users`, todo acceso (web, FTP, WebDAV) requiere usuario y
contraseña de esa lista; si no, es público. **Pendiente:** permisos distintos por usuario dentro
de un mismo share (p. ej. lectura pública y escritura con usuario).

## Fichero de configuración

INI editable a mano, con claves en inglés (los mensajes al usuario van en español o inglés).

```ini
# sunfish.conf

[Server]
Name = Sunfish
HttpPort = 8080
FtpPort = 2121
FtpPassivePorts = 50000-50050
Bind = 0.0.0.0, ::
RootMenu = true
SelfDownload = true

[Admin]
Enabled = false
PasswordHash = <PBKDF2>
AllowFrom = 127.0.0.1, ::1

[User ana]
PasswordHash = <PBKDF2>

[Share web]
Location = /
Path = C:\www
Index = index.html
Listing = false

[Share fotos]
Location = /fotos
Path = D:\Fotos
Listing = true
Upload = true
Zip = true
Users = ana
Mac = true
WebDav = true
Enabled = true
```

- Valores por defecto: `Index = index.html` (vacío = sin índice), `Listing = true`,
  `Ftp = true`, `WebDav = false`, `Mac = false`, `Enabled = true`, el resto de permisos `false`.
- La GUI, la TUI, los subcomandos y el panel web reescriben el fichero **conservando
  comentarios y orden**, con escritura atómica (temporal + renombrado).
- Un cambio en el fichero hecho por otro proceso se detecta y se recarga; si no es válido se
  conserva la configuración anterior y se anota en el registro.

### Ubicación

La primera que exista:

1. `-c FICHERO` (o `--config`).
2. `sunfish.conf` junto al ejecutable (modo portable).
3. Carpeta de configuración del usuario: `%APPDATA%\Sunfish\sunfish.conf`,
   `~/.config/sunfish/sunfish.conf` (o `$XDG_CONFIG_HOME`),
   `~/Library/Application Support/Sunfish/sunfish.conf`.

Si no existe ninguna, la aplicación ofrece crearla junto al ejecutable o en la carpeta del
usuario. `Sunfish.exe` y `sunfish-cli` usan el mismo formato y las mismas rutas, así que pueden
compartir configuración (no a la vez: competirían por los puertos).

### Importar la configuración de la 2.0

La 2.0 guardaba un JSON llamado `sunfish2` en el directorio de trabajo. Si no hay configuración
nueva y existe `sunfish2` junto al ejecutable, se ofrece importarlo:

| 2.0 | 3 |
|---|---|
| `Port` | `HttpPort` |
| `SunfishRoot` | `RootMenu` |
| servicio `WebService`: `Name`, `Location`, `Enabled` | `[Share <Name>]`, `Location`, `Enabled` |
| `path` | `Path` |
| `defaultFile` | `Index` |
| `folderShare` | `Listing` |
| `readOnly = false` | `Upload = true`, `Overwrite = true`, `Rename = true` |
| `allowDeletion` | `Delete` (solo si no era de solo lectura) |
| `allowServerExecution` | se ignora (la función ya no existe) |
| `AdminPanel`, `AdminPwd` | se ignoran: se pide una contraseña nueva (la 2.0 la guardaba en claro) |
