435 lines
22 KiB
Markdown
435 lines
22 KiB
Markdown
# Accéder au WebDAV d'un Nextcloud depuis une page statique
|
|
|
|
Tofu est servi en statique et parle au Nextcloud **directement depuis le navigateur**
|
|
(`app/services/webdav/`). Aucun backend, aucun proxy applicatif : c'est une contrainte
|
|
assumée du projet, pas un oubli. Le seul obstacle réel est le CORS.
|
|
|
|
Ce document dit ce qui bloque exactement, les trois façons de le débloquer, et laquelle
|
|
choisir. Les vérifications ont été faites le **2026-09-17** ; les commandes pour les
|
|
refaire sont au §7.
|
|
|
|
---
|
|
|
|
## 1. Ce que fait le navigateur, et ce que Nextcloud répond
|
|
|
|
### 1.1 Toutes les requêtes de Tofu déclenchent un préflight
|
|
|
|
Une requête cross-origin échappe au préflight uniquement si elle cumule une méthode
|
|
sûre (`GET`, `HEAD`, `POST`) **et** seulement des en-têtes de la liste blanche CORS
|
|
(`Accept`, `Accept-Language`, `Content-Language`, `Content-Type` limité à trois types,
|
|
`Range`). Tofu ne coche aucune de ces cases :
|
|
|
|
| Appel du client WebDAV | Méthode | En-têtes non safelistés | Préflight |
|
|
|------------------------|----------|----------------------------------------------------|-----------|
|
|
| `listFolder` | PROPFIND | `Authorization`, `Depth`, `Content-Type: application/xml` | oui |
|
|
| `readJson` | GET | `Authorization` | oui |
|
|
| `writeJson` | PUT | `Authorization`, `Content-Type: application/json` | oui |
|
|
| `ensureFolder` | MKCOL | `Authorization` | oui |
|
|
| `remove` | DELETE | `Authorization` | oui |
|
|
|
|
`Authorization` suffit à lui seul : il n'est pas safelisté. Autrement dit **chaque**
|
|
requête de Tofu est précédée d'un `OPTIONS` vers la même URL, portant `Origin`,
|
|
`Access-Control-Request-Method` et `Access-Control-Request-Headers`.
|
|
|
|
### 1.2 Nextcloud répond 401 à ce préflight, sans aucun en-tête CORS
|
|
|
|
Mesuré sur deux instances Nextcloud publiques et à jour, indépendantes l'une de l'autre :
|
|
|
|
```sh
|
|
curl -sS -D - -o /dev/null -X OPTIONS https://cloud.disroot.org/remote.php/dav/ \
|
|
-H 'Origin: https://example.org' \
|
|
-H 'Access-Control-Request-Method: PROPFIND' \
|
|
-H 'Access-Control-Request-Headers: authorization,depth,content-type'
|
|
```
|
|
|
|
```
|
|
HTTP/1.1 401 Unauthorized
|
|
Content-Type: application/xml; charset=utf-8
|
|
WWW-Authenticate: Basic realm="Disroot Cloud", charset="UTF-8"
|
|
Content-Security-Policy: default-src 'none';
|
|
X-Frame-Options: SAMEORIGIN
|
|
(aucun en-tête Access-Control-*)
|
|
```
|
|
|
|
Résultat identique sur `https://framagenda.org`. Deux choses clochent, et **une seule
|
|
suffit à tout faire échouer** :
|
|
|
|
- la réponse ne porte **aucun** `Access-Control-*` → le navigateur refuse le préflight ;
|
|
- elle est en **401** → même avec les bons en-têtes, un statut hors 2xx fait échouer le
|
|
préflight, donc la vraie requête n'est jamais envoyée.
|
|
|
|
Le 401 vient de Sabre/DAV : le plugin d'authentification tourne sur `beforeMethod` sans
|
|
exempter `OPTIONS`, alors que le préflight part par construction sans credentials. Toute
|
|
solution doit donc traiter **deux problèmes distincts** : poser les en-têtes, *et*
|
|
répondre au préflight **avant** l'authentification.
|
|
|
|
Côté navigateur, l'échec est indiscernable d'une panne réseau : `fetch` rejette avec un
|
|
`TypeError` nu, sans statut. C'est exactement pour cela que `describeWebdavFailure`
|
|
renvoie `kind: 'cors'` et pointe vers ce document plutôt que de parler de « réseau ».
|
|
|
|
### 1.3 Pourquoi il n'y a rien par défaut
|
|
|
|
`/remote.php/dav/` n'est pas servi par le framework applicatif de Nextcloud : c'est un
|
|
serveur Sabre/DAV. Le `CORSMiddleware` de Nextcloud, celui qui gère l'annotation `#[CORS]`
|
|
des contrôleurs OCS, ne le traverse jamais. Il n'existe **aucune clé de `config.php`** qui
|
|
pose des en-têtes CORS sur le DAV.
|
|
|
|
Le support natif est demandé depuis janvier 2017 (`nextcloud/server#3131`). La demande est
|
|
toujours **ouverte** à ce jour ; elle est assignée et jalonnée « Nextcloud 36 ». Ce n'est
|
|
donc pas disponible aujourd'hui, et un jalon peut glisser : ne rien construire dessus.
|
|
|
|
---
|
|
|
|
## 2. Le détail qui change tout : nos requêtes ne sont pas « créditées »
|
|
|
|
`webdavRequest` fait `credentials: 'omit'` et pose l'en-tête `Authorization` lui-même.
|
|
Au sens de la spécification Fetch, une requête est *créditée* quand le **navigateur** y
|
|
joint des cookies, un certificat client TLS ou une authentification HTTP — c'est-à-dire
|
|
quand le mode credentials vaut `include`. Un `Authorization` que l'on écrit soi-même est
|
|
un en-tête de requête ordinaire : il ne crédite pas la requête.
|
|
|
|
Conséquences, vérifiées :
|
|
|
|
1. **`Access-Control-Allow-Origin: *` suffit.** La règle « le serveur doit nommer une
|
|
origine explicite » ne s'applique qu'aux requêtes créditées. Le serveur n'a donc pas
|
|
besoin de connaître l'URL où Tofu est servi, et rien n'est à reconfigurer si elle change.
|
|
2. **`Access-Control-Allow-Credentials` est inutile — et nuisible.** Il ne sert à rien
|
|
ici, et s'il est posé il interdit le `*`. Ne pas le poser.
|
|
3. **Piège :** `Access-Control-Allow-Headers: *` **ne couvre jamais** `Authorization`.
|
|
MDN, textuellement : *« The `Authorization` header doesn't accept wildcard and always
|
|
needs to be listed explicitly. »* C'est l'erreur la plus fréquente : la configuration
|
|
a l'air correcte, le préflight passe pour `Depth` et `Content-Type`, et échoue sur
|
|
`Authorization`.
|
|
4. **Sans `Access-Control-Max-Age`, un préflight n'est valable que 5 secondes**, soit un
|
|
`OPTIONS` avant presque chaque requête. Poser `7200` : c'est le plafond de Chromium ;
|
|
Firefox plafonne à 86400 et rabote simplement toute valeur plus haute.
|
|
5. Pour lire un en-tête de réponse depuis JS (`ETag`, `DAV`), il faut le déclarer dans
|
|
`Access-Control-Expose-Headers` : seuls `Cache-Control`, `Content-Language`,
|
|
`Content-Length`, `Content-Type`, `Expires`, `Last-Modified` et `Pragma` sont visibles
|
|
par défaut.
|
|
|
|
---
|
|
|
|
## 3. Option A — l'application Nextcloud WebAppPassword
|
|
|
|
`digital-blueprint/webapppassword`, catégorie Sécurité de l'App Store. C'est
|
|
l'application dédiée exactement à ce cas : permettre à une page web tierce de parler au
|
|
WebDAV/CalDAV.
|
|
|
|
**Est-elle maintenue ?** Oui, franchement oui. Dernière version **26.8.0**, publiée il y a
|
|
moins d'un mois, compatible **Nextcloud 25 à 35**. Ce n'est pas un projet abandonné.
|
|
|
|
**Ce qu'elle fait réellement** (lu dans `lib/Connector/Sabre/CorsPlugin.php`) : elle
|
|
enregistre un plugin Sabre sur le serveur DAV via l'événement `SabrePluginEvent`, et,
|
|
si l'`Origin` de la requête figure dans la liste autorisée :
|
|
|
|
- pose `access-control-allow-origin` avec **l'origine de la requête** (pas `*`) ;
|
|
- renvoie en écho `access-control-allow-methods` et `access-control-allow-headers` depuis
|
|
les en-têtes `access-control-request-*` du préflight — donc `PROPFIND`, `MKCOL`,
|
|
`Authorization`, `Depth` passent sans rien énumérer ;
|
|
- pose `access-control-expose-headers: etag, dav` ;
|
|
- pose `access-control-allow-credentials: true` ;
|
|
- **répond 204 à un `OPTIONS` sans en-tête `Authorization`, avant l'authentification** —
|
|
c'est ce qui règle le 401 du §1.2. La présence même de ce court-circuit dans le code de
|
|
l'application confirme le diagnostic.
|
|
|
|
**Activation :**
|
|
|
|
```sh
|
|
sudo -u www-data php occ app:install webapppassword
|
|
sudo -u www-data php occ app:enable webapppassword
|
|
```
|
|
|
|
**Déclaration de l'origine** — soit dans la page de réglages de l'application (section
|
|
Administration → Sécurité), soit dans `config/config.php` :
|
|
|
|
```php
|
|
'webapppassword.origins' => ['https://tofu.exemple.fr'],
|
|
```
|
|
|
|
L'origine inclut le schéma, l'hôte et le port éventuel. Un joker de sous-domaine sur un
|
|
seul niveau est accepté (`https://*.exemple.fr`). Deux autres clés existent,
|
|
`webapppassword.files_sharing_origins` et `webapppassword.preview_origins`, pour des API
|
|
que Tofu n'utilise pas : les laisser vides.
|
|
|
|
**Limites, dites franchement :**
|
|
|
|
- Il faut déclarer l'origine **exacte** de Tofu, et la reconfigurer si elle change. En
|
|
développement local, `http://localhost:8000` est une origine de plus à déclarer.
|
|
- L'application pose `allow-credentials: true` et échoue l'origine : c'est strictement
|
|
plus que ce dont Tofu a besoin (§2), sans bénéfice pour nous.
|
|
- Elle ne pose pas de `Access-Control-Max-Age` : on garde un préflight de 5 secondes,
|
|
donc un `OPTIONS` avant quasiment chaque requête. Fonctionnellement correct, deux fois
|
|
plus de requêtes.
|
|
- C'est une application tierce **dans le chemin d'authentification**. À revérifier à
|
|
chaque montée de version majeure de Nextcloud.
|
|
- Elle propose aussi un mécanisme de mot de passe d'application temporaire via une fenêtre
|
|
de connexion. Tofu **ne l'utilise pas** : il demande un mot de passe d'application
|
|
classique dans son propre formulaire. Seule la partie CORS nous intéresse.
|
|
|
|
---
|
|
|
|
## 4. Option B — poser les en-têtes dans le serveur web
|
|
|
|
À faire si l'on tient la configuration nginx/Apache du Nextcloud et qu'on préfère ne pas
|
|
installer d'application tierce. Comme nos requêtes ne sont pas créditées (§2), `*` suffit :
|
|
le serveur n'a pas besoin de connaître l'URL de Tofu.
|
|
|
|
### 4.1 nginx
|
|
|
|
Dans le bloc `http { }` (nginx ne sait pas faire un `if` à deux conditions, on les combine
|
|
dans des `map`) :
|
|
|
|
```nginx
|
|
# Origine autorisée, uniquement sur le chemin DAV. Une valeur vide fait que nginx
|
|
# n'ajoute pas l'en-tête du tout : le reste du vhost n'est pas touché.
|
|
map $request_uri $tofu_cors_origin {
|
|
default "";
|
|
"~^/remote\.php/dav" "*";
|
|
}
|
|
|
|
map $tofu_cors_origin $tofu_cors_expose {
|
|
default "";
|
|
"*" "Content-Length, Content-Type, DAV, ETag, Last-Modified, OC-ETag, OC-FileId";
|
|
}
|
|
|
|
map "$request_method:$request_uri" $tofu_dav_preflight {
|
|
default 0;
|
|
"~^OPTIONS:/remote\.php/dav" 1;
|
|
}
|
|
```
|
|
|
|
Dans le bloc `server { }` du Nextcloud :
|
|
|
|
```nginx
|
|
# « always » est obligatoire : sans lui, nginx ne pose l'en-tête que sur les
|
|
# réponses 2xx/3xx, et le 401 comme le 207 partent nus.
|
|
add_header Access-Control-Allow-Origin $tofu_cors_origin always;
|
|
add_header Access-Control-Expose-Headers $tofu_cors_expose always;
|
|
```
|
|
|
|
Puis, **au début** du bloc PHP existant (`location ~ \.php(?:$|/)`), sans rien y changer
|
|
d'autre :
|
|
|
|
```nginx
|
|
location ~ \.php(?:$|/) {
|
|
|
|
# Répondre au préflight ici, donc avant PHP, donc avant l'authentification.
|
|
# Sinon Sabre renvoie 401 et le navigateur s'arrête là.
|
|
if ($tofu_dav_preflight) {
|
|
add_header Access-Control-Allow-Origin "*" always;
|
|
add_header Access-Control-Allow-Methods "GET, HEAD, PUT, DELETE, OPTIONS, PROPFIND, PROPPATCH, MKCOL, MOVE, COPY, LOCK, UNLOCK, REPORT" always;
|
|
add_header Access-Control-Allow-Headers "Authorization, Content-Type, Depth, Destination, Overwrite, If, If-Match, If-None-Match, Lock-Token, Timeout, X-Requested-With, OCS-APIRequest" always;
|
|
add_header Access-Control-Max-Age "7200" always;
|
|
add_header Content-Length 0;
|
|
return 204;
|
|
}
|
|
|
|
# ... bloc fastcgi d'origine, inchangé ...
|
|
}
|
|
```
|
|
|
|
Trois pièges nginx, dans l'ordre où ils mordent :
|
|
|
|
1. **`always`.** Sans lui, aucun en-tête sur un 401 ou un 207 (la réponse normale d'un
|
|
PROPFIND) — et le navigateur bloque la lecture.
|
|
2. **`add_header` n'est pas cumulatif entre niveaux.** Dès qu'un niveau imbriqué déclare
|
|
un `add_header`, il perd *tous* ceux du niveau parent. C'est pour cela que
|
|
`Access-Control-Allow-Origin` est réécrit dans le bloc `if`. Corollaire : la réponse de
|
|
préflight perd les en-têtes de sécurité du vhost — sans conséquence, elle n'a pas de
|
|
corps, mais autant le savoir avant de s'en étonner.
|
|
3. **Ne pas ajouter d'`add_header` dans le bloc PHP hors du `if`** sans y recopier les
|
|
en-têtes de sécurité du vhost, pour la même raison.
|
|
|
|
### 4.2 Apache
|
|
|
|
Dans le `VirtualHost` (**pas** dans le `.htaccess` de Nextcloud : les règles du vhost
|
|
passent avant, et le `.htaccess` est réécrit aux montées de version).
|
|
|
|
```apache
|
|
<IfModule mod_headers.c>
|
|
<LocationMatch "^/remote\.php/dav">
|
|
Header always set Access-Control-Allow-Origin "*"
|
|
Header always set Access-Control-Allow-Methods "GET, HEAD, PUT, DELETE, OPTIONS, PROPFIND, PROPPATCH, MKCOL, MOVE, COPY, LOCK, UNLOCK, REPORT"
|
|
Header always set Access-Control-Allow-Headers "Authorization, Content-Type, Depth, Destination, Overwrite, If, If-Match, If-None-Match, Lock-Token, Timeout, X-Requested-With, OCS-APIRequest"
|
|
Header always set Access-Control-Expose-Headers "Content-Length, Content-Type, DAV, ETag, Last-Modified, OC-ETag, OC-FileId"
|
|
Header always set Access-Control-Max-Age "7200"
|
|
</LocationMatch>
|
|
</IfModule>
|
|
|
|
# Court-circuiter le préflight avant que PHP (donc Nextcloud, donc Sabre) ne le voie.
|
|
<IfModule mod_rewrite.c>
|
|
RewriteEngine On
|
|
RewriteCond %{REQUEST_METHOD} =OPTIONS
|
|
RewriteCond %{REQUEST_URI} ^/remote\.php/dav
|
|
RewriteRule ^ - [R=200,L]
|
|
</IfModule>
|
|
```
|
|
|
|
`Header always set` (et non `Header set`) pour la même raison que `always` sous nginx :
|
|
sans lui, rien sur les réponses d'erreur. Le `[R=200,L]` renvoie un 200 au corps vide, ce
|
|
qui est une réponse de préflight valide ; les en-têtes du `LocationMatch` s'y appliquent
|
|
puisque l'URI correspond toujours.
|
|
|
|
### 4.3 Ce que cette option coûte
|
|
|
|
Une configuration écrite à la main, à relire à chaque changement de la configuration
|
|
Nextcloud recommandée — laquelle est régulièrement republiée et souvent recopiée telle
|
|
quelle lors d'une montée de version, ce qui efface l'ajout sans prévenir.
|
|
|
|
---
|
|
|
|
## 5. Option C — supprimer le problème : la même origine
|
|
|
|
S'il n'y a pas de cross-origin, il n'y a pas de CORS. Pas d'en-tête à poser, pas de
|
|
préflight, pas d'application tierce, rien à réparer après une montée de version. C'est
|
|
l'option la plus robuste à long terme, et de loin.
|
|
|
|
Concrètement : servir les fichiers statiques de Tofu **par le même serveur web, sur le
|
|
même hôte, à un chemin voisin**. `https://cloud.exemple.fr/tofu/` parle à
|
|
`https://cloud.exemple.fr/remote.php/dav/…` : même schéma, même hôte, même port, donc
|
|
même origine.
|
|
|
|
**Attention, un sous-domaine ne suffit pas.** `https://tofu.exemple.fr` et
|
|
`https://cloud.exemple.fr` sont deux origines distinctes et tout le CORS s'applique. Seul
|
|
un **chemin sous le même hôte** fonctionne.
|
|
|
|
nginx, dans le bloc `server { }` du Nextcloud :
|
|
|
|
```nginx
|
|
# « ^~ » arrête l'évaluation des expressions régulières : les .js et .css de Tofu
|
|
# sont servis tels quels et ne repartent pas dans index.php.
|
|
location ^~ /tofu/ {
|
|
alias /var/www/tofu/;
|
|
index index.html;
|
|
try_files $uri $uri/ =404;
|
|
}
|
|
```
|
|
|
|
Apache, dans le `VirtualHost` :
|
|
|
|
```apache
|
|
Alias /tofu /var/www/tofu
|
|
<Directory /var/www/tofu>
|
|
Require all granted
|
|
Options -Indexes
|
|
AllowOverride None
|
|
DirectoryIndex index.html
|
|
</Directory>
|
|
```
|
|
|
|
Le déploiement se réduit alors à copier le dépôt (`index.html`, `app/`, `libs/`,
|
|
`styles/`) dans `/var/www/tofu`. Aucune étape de build, c'est tout l'intérêt.
|
|
|
|
**Deux précautions :**
|
|
|
|
- **Ne pas déposer les fichiers dans le dossier d'installation de Nextcloud.** Le
|
|
vérificateur d'intégrité de Nextcloud signale les fichiers étrangers à sa racine, et
|
|
une montée de version peut les effacer. D'où l'`alias` / `Alias` vers un répertoire
|
|
situé ailleurs.
|
|
- `credentials: 'omit'` reste le bon choix même en même origine : le cookie de session
|
|
Nextcloud n'est pas envoyé, le mot de passe d'application reste le seul justificatif, et
|
|
on ne touche jamais au CSRF de Nextcloud.
|
|
|
|
**Ce que cette option ne règle pas :** le développement local. `python3 -m http.server
|
|
8000` sert Tofu sur `http://localhost:8000`, qui est une autre origine que le Nextcloud.
|
|
Pour développer contre un vrai serveur, il faut soit activer en plus l'option A ou B pour
|
|
l'origine de développement, soit se contenter de « Continuer sans Nextcloud » (§6, point 4).
|
|
|
|
---
|
|
|
|
## 6. Recommandation, classée et assumée
|
|
|
|
1. **Option C — même origine.** À retenir dès qu'on a accès à la configuration du serveur
|
|
web qui sert le Nextcloud. Le CORS n'entre pas en jeu, donc rien ne peut le casser : ni
|
|
une montée de version, ni une application tierce abandonnée, ni une configuration
|
|
recopiée. *Prix :* Tofu devient une partie du déploiement du Nextcloud, et le
|
|
développement en local demande l'une des deux autres options.
|
|
|
|
2. **Option A — WebAppPassword.** Le bon choix quand on est administrateur Nextcloud mais
|
|
pas administrateur du serveur web (hébergement mutualisé, Nextcloud géré par un tiers).
|
|
Deux commandes `occ` et une ligne de configuration, pour un résultat correct.
|
|
*Prix :* une application tierce dans le chemin d'authentification, l'origine exacte à
|
|
déclarer et à maintenir, et un préflight non mis en cache.
|
|
|
|
3. **Option B — reverse proxy.** Quand on tient la configuration web mais qu'on refuse une
|
|
application tierce. C'est aussi l'option la plus fine : `*` sans credentials, cache de
|
|
préflight à 7200 s, exactement ce dont Tofu a besoin et rien de plus. *Prix :* une
|
|
configuration à la main, deux pièges faciles (`always`, court-circuit du préflight), et
|
|
un ajout qu'une mise à jour de configuration peut effacer silencieusement.
|
|
|
|
4. **Aucune des trois — et c'est prévu.** Si l'on n'est administrateur de rien, aucune de
|
|
ces options n'est accessible : il n'y a pas de solution côté client, point. Tofu le
|
|
gère par conception : l'écran de connexion propose « Continuer sans Nextcloud », le
|
|
planning tourne alors sur `createMemoryPlanningStore()` et affiche « Hors ligne — rien
|
|
n'est enregistré ». La feuille reste saisissable et imprimable. Ce n'est pas un échec
|
|
silencieux, c'est un mode annoncé.
|
|
|
|
**Le support natif de Nextcloud (issue #3131) n'est pas une option.** Toujours ouverte
|
|
après neuf ans, jalonnée Nextcloud 36. À surveiller, pas à attendre.
|
|
|
|
### Ce qui n'est pas proposé, et ne le sera pas
|
|
|
|
**Un proxy applicatif ou un petit backend maison est exclu du projet.** Ce n'est pas une
|
|
préférence esthétique : ce serait un service de plus à déployer, à maintenir, à mettre à
|
|
jour et à surveiller, qui verrait passer le mot de passe en clair, et qui annulerait la
|
|
seule propriété qui fait tenir Tofu dans le temps — un dossier de fichiers que n'importe
|
|
quel serveur web sait servir. Si aucune des options ci-dessus n'est accessible, la réponse
|
|
est le mode hors ligne du point 4, pas un backend.
|
|
|
|
---
|
|
|
|
## 7. Vérifier en trois commandes
|
|
|
|
Remplacer `cloud.exemple.fr`, `tofu.exemple.fr` et `UTILISATEUR`. Le mot de passe est un
|
|
**mot de passe d'application** (Nextcloud → Paramètres personnels → Sécurité → Créer un
|
|
mot de passe d'application), jamais le mot de passe du compte.
|
|
|
|
```sh
|
|
# 1. Le préflight passe-t-il ?
|
|
curl -sS -D - -o /dev/null -X OPTIONS https://cloud.exemple.fr/remote.php/dav/ \
|
|
-H 'Origin: https://tofu.exemple.fr' \
|
|
-H 'Access-Control-Request-Method: PROPFIND' \
|
|
-H 'Access-Control-Request-Headers: authorization,depth,content-type'
|
|
```
|
|
|
|
Attendu : un statut **2xx**, un `Access-Control-Allow-Origin`, un
|
|
`Access-Control-Allow-Methods` contenant **PROPFIND**, et un `Access-Control-Allow-Headers`
|
|
contenant **authorization** en toutes lettres. Un 401 ici, c'est le §1.2 : le préflight
|
|
n'est pas court-circuité avant l'authentification.
|
|
|
|
```sh
|
|
# 2. La vraie requête renvoie-t-elle l'en-tête d'origine ?
|
|
curl -sS -D - -o /dev/null -X PROPFIND \
|
|
https://cloud.exemple.fr/remote.php/dav/files/UTILISATEUR/ \
|
|
-H 'Origin: https://tofu.exemple.fr' -H 'Depth: 1' \
|
|
-u 'UTILISATEUR:MOT_DE_PASSE_APPLICATION'
|
|
```
|
|
|
|
Attendu : **207 Multi-Status** *et* un `Access-Control-Allow-Origin`. Un 207 sans en-tête,
|
|
c'est le `always` de nginx ou le `Header always set` d'Apache qui manque.
|
|
|
|
```sh
|
|
# 3. Depuis le navigateur, en réel
|
|
python3 -m http.server 8000 # puis http://localhost:8000
|
|
```
|
|
|
|
L'écran de connexion de Tofu lance `runWebdavCheck` : joindre le serveur, créer le
|
|
dossier, écrire, relire, lister, nettoyer. Chaque étape est reportée avec un marqueur
|
|
textuel `OK` / `ÉCHEC` et son détail. C'est le test qui fait foi — les deux commandes
|
|
`curl` ne parlent pas CORS, seul un navigateur applique la règle.
|
|
|
|
---
|
|
|
|
## Sources
|
|
|
|
- [MDN — Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) — méthodes et en-têtes safelistés, définition d'une requête créditée, `*` interdit aux requêtes créditées.
|
|
- [MDN — `Access-Control-Allow-Headers`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Access-Control-Allow-Headers) — « The `Authorization` header doesn't accept wildcard and always needs to be listed explicitly. »
|
|
- [MDN — `Access-Control-Max-Age`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Access-Control-Max-Age) — défaut 5 s, plafond 7200 s (Chromium ≥ 76), 86400 s (Firefox).
|
|
- [nextcloud/server#3131 — Support for cross-domain WebDAV access (CORS)](https://github.com/nextcloud/server/issues/3131) — ouverte depuis 2017, jalon Nextcloud 36.
|
|
- [nextcloud/server#19719 — CORS Problem CalDav](https://github.com/nextcloud/server/issues/19719) — symptôme identique sur `/remote.php/dav/`.
|
|
- [digital-blueprint/webapppassword](https://github.com/digital-blueprint/webapppassword) — dépôt, clés `webapppassword.*`, jokers de sous-domaine.
|
|
- [`lib/Connector/Sabre/CorsPlugin.php`](https://github.com/digital-blueprint/webapppassword/blob/main/lib/Connector/Sabre/CorsPlugin.php) — en-têtes réellement posés et court-circuit du préflight en 204 avant authentification.
|
|
- [WebAppPassword sur l'App Store Nextcloud](https://apps.nextcloud.com/apps/webapppassword) — version 26.8.0, Nextcloud 25 à 35.
|
|
- [Nextcloud — Basic File & Folder Operations (WebDAV)](https://docs.nextcloud.com/server/stable/developer_manual/client_apis/WebDAV/basic.html) — `/remote.php/dav/files/<user>/`, authentification Basic.
|
|
- Mesures directes du 2026-09-17 sur `cloud.disroot.org` et `framagenda.org` (§1.2).
|