diff --git a/.env.example b/.env.example index f36cfd4..b7623fc 100644 --- a/.env.example +++ b/.env.example @@ -44,6 +44,8 @@ DOMAIN={{PUBLIC_HOST}} # Origine publique dérivée (http:// ou https:// + hôte) SUITE_ORIGIN=http{{SECURE}}://{{PUBLIC_HOST}} CLOUDFLARE_TUNNEL_PUBLIC_URL={{SUITE_ORIGIN}} +# X-Forwarded-Proto envoyé aux upstreams (nginx :80 + tunnel → https côté navigateur) +FORWARDED_PROTO=http{{SECURE}} # ----------------------------------------------------------------------------- # General diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index a1c2b90..56c313c 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -10,6 +10,7 @@ services: - ./nginx/patches/QGuclOcQ.js:/etc/nginx/patches/QGuclOcQ.js:ro environment: DOMAIN: ${DOMAIN:-localhost} + FORWARDED_PROTO: ${FORWARDED_PROTO:-http} MAIL_FRONTEND_UPSTREAM: ${MAIL_FRONTEND_UPSTREAM:-host.docker.internal:3004} env_file: ../.env.resolved extra_hosts: diff --git a/deploy/nginx/default.conf.template b/deploy/nginx/default.conf.template index a0a3d76..c3a1483 100644 --- a/deploy/nginx/default.conf.template +++ b/deploy/nginx/default.conf.template @@ -40,7 +40,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } location ^~ /api/v1/migration/ { resolver 127.0.0.11 valid=10s ipv6=off; @@ -63,7 +63,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } location ^~ /api/v1/admin/ { resolver 127.0.0.11 valid=10s ipv6=off; @@ -86,7 +86,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } location ^~ /api/v1/drive/ { resolver 127.0.0.11 valid=10s ipv6=off; @@ -109,7 +109,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } location ^~ /api/v1/office/ { resolver 127.0.0.11 valid=10s ipv6=off; @@ -132,7 +132,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } location ^~ /api/v1/richtext/ { resolver 127.0.0.11 valid=10s ipv6=off; @@ -155,7 +155,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } location ^~ /api/v1/ultidraw/ { resolver 127.0.0.11 valid=10s ipv6=off; @@ -178,7 +178,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } location ^~ /api/v1/ai/mcp { resolver 127.0.0.11 valid=10s ipv6=off; @@ -208,7 +208,7 @@ server { proxy_set_header X-OpenWebUI-User-Role $http_x_openwebui_user_role; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_buffering off; proxy_cache off; proxy_read_timeout 86400s; @@ -242,7 +242,7 @@ server { proxy_set_header X-OpenWebUI-User-Role $http_x_openwebui_user_role; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_buffering off; proxy_cache off; proxy_read_timeout 86400s; @@ -269,7 +269,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } location ^~ /api/v1/calendar/ { resolver 127.0.0.11 valid=10s ipv6=off; @@ -292,7 +292,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } location ^~ /api/v1/contacts/ { resolver 127.0.0.11 valid=10s ipv6=off; @@ -315,7 +315,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } location ^~ /api/v1/meet/ { resolver 127.0.0.11 valid=10s ipv6=off; @@ -338,7 +338,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } location ^~ /api/v1/photos/ { resolver 127.0.0.11 valid=10s ipv6=off; @@ -361,7 +361,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } location ^~ /api/v1/search { resolver 127.0.0.11 valid=10s ipv6=off; @@ -384,7 +384,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } # OpenWebUI trusted-header signin — ultid injects X-Ulti-User-* (auth_request + POST body deadlocks) @@ -411,7 +411,7 @@ server { proxy_set_header Authorization $http_authorization; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } # --- OpenWebUI API at site root (prod SPA: WEBUI_BASE_URL="" → fetch("/api/…")) --- @@ -425,7 +425,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Accept-Encoding ""; @@ -444,7 +444,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_read_timeout 86400s; @@ -476,7 +476,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } # OpenWebUI socket.io (ultid uses /ws without /socket.io) @@ -490,7 +490,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_buffering off; proxy_cache off; proxy_read_timeout 86400s; @@ -509,7 +509,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_buffering off; proxy_cache off; proxy_read_timeout 86400s; @@ -532,7 +532,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } # OpenWebUI Ollama/OpenAI proxy mounts (SPA uses /ollama, /openai at root) @@ -544,7 +544,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } location ^~ /openai/ { resolver 127.0.0.11 valid=10s ipv6=off; @@ -554,7 +554,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } # TipTap / Hocuspocus — proxy WS without redirect (301 breaks upgrade) @@ -570,7 +570,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_read_timeout 86400s; proxy_send_timeout 86400s; } @@ -584,7 +584,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } @@ -597,7 +597,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_read_timeout 86400; @@ -616,7 +616,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } # Public Nextcloud share links → UltiDrive viewer @@ -633,7 +633,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } location = /cloud { @@ -649,7 +649,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_read_timeout 300s; } @@ -664,7 +664,7 @@ server { proxy_set_header Authorization $http_authorization; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } location /ai/ { @@ -685,7 +685,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header X-Forwarded-Prefix /ai; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; @@ -775,7 +775,7 @@ server { proxy_set_header X-Ulti-User-Role $ulti_user_role; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } # OpenWebUI API (prefix /ai/api — évite collision avec ultid /api/v1/) @@ -789,7 +789,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_buffering off; @@ -825,7 +825,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header X-Ulti-User-Email $ulti_user_email; proxy_set_header X-Ulti-User-Name $ulti_user_name; proxy_set_header X-Ulti-User-Role $ulti_user_role; @@ -859,7 +859,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header X-Ulti-User-Email $ulti_user_email; proxy_set_header X-Ulti-User-Name $ulti_user_name; proxy_set_header X-Ulti-User-Role $ulti_user_role; @@ -893,7 +893,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header X-Ulti-User-Email $ulti_user_email; proxy_set_header X-Ulti-User-Name $ulti_user_name; proxy_set_header X-Ulti-User-Role $ulti_user_role; @@ -907,7 +907,7 @@ server { proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Host $host/office; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; @@ -928,7 +928,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } @@ -941,7 +941,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } @@ -957,7 +957,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } @@ -971,7 +971,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } location ^~ /api/auth/ { @@ -982,7 +982,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } location ^~ /mail/ { @@ -993,7 +993,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } @@ -1007,7 +1007,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } @@ -1024,7 +1024,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } @@ -1037,7 +1037,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } @@ -1050,7 +1050,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } @@ -1063,7 +1063,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } @@ -1077,7 +1077,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } @@ -1091,7 +1091,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } @@ -1114,7 +1114,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } @@ -1127,7 +1127,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } @@ -1141,7 +1141,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } location ^~ /brand/ { @@ -1150,7 +1150,7 @@ server { proxy_pass http://$mail_upstream; proxy_http_version 1.1; proxy_set_header Host $host; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } location ^~ /mail-backgrounds/ { @@ -1159,7 +1159,7 @@ server { proxy_pass http://$mail_upstream; proxy_http_version 1.1; proxy_set_header Host $host; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } # Public assets at repo root (launcher icons, etc.) @@ -1169,7 +1169,7 @@ server { proxy_pass http://$mail_upstream$request_uri; proxy_http_version 1.1; proxy_set_header Host $host; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } # Landing page de la suite (servie par le frontend Next). @@ -1181,7 +1181,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; } # OpenWebUI SPA routes without /ai prefix (client nav + hard reload in UltiAI iframe). @@ -1212,7 +1212,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Proto ${FORWARDED_PROTO}; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } diff --git a/internal/mail/imap/body_repair.go b/internal/mail/imap/body_repair.go index c670f2d..253f143 100644 --- a/internal/mail/imap/body_repair.go +++ b/internal/mail/imap/body_repair.go @@ -21,6 +21,8 @@ func RepairStoredBodies(text, html string) (string, string) { text = decodeBareBase64IfNeeded(text) html = decodeBareBase64IfNeeded(html) text = stripPlainTextPreheaderPadding(text) + text = repairUTF8Mojibake(text) + html = repairUTF8Mojibake(html) return text, html } @@ -49,7 +51,7 @@ func RepairSnippet(snippet string) string { // RepairSnippetWithBodies decodes a stored snippet and optionally rebuilds from bodies. func RepairSnippetWithBodies(snippet, bodyText, bodyHTML string) string { - snippet = stripSnippetMarkup(snippet) + snippet = repairUTF8Mojibake(stripSnippetMarkup(snippet)) if decoded := decodeBareQuotedPrintableIfNeeded(snippet); decoded != snippet { snippet = stripSnippetMarkup(decoded) } diff --git a/internal/mail/imap/charset.go b/internal/mail/imap/charset.go index 448dadb..5c81a6d 100644 --- a/internal/mail/imap/charset.go +++ b/internal/mail/imap/charset.go @@ -2,6 +2,7 @@ package imap import ( "mime" + "regexp" "strings" "unicode/utf8" @@ -11,6 +12,9 @@ import ( "golang.org/x/text/transform" ) +// UTF-8 misread as Latin-1: "réactivité" (U+00C3 U+00A9 …). +var utf8MojibakeRE = regexp.MustCompile(`[\xC2\xC3][\x80-\xBF]`) + func charsetFromContentType(contentType string) string { if contentType == "" { return "" @@ -77,10 +81,79 @@ func repairRawBytesToUTF8(data []byte) string { return strings.ToValidUTF8(string(data), "") } -// repairLegacyCharsetString fixes text already loaded as a Go string with invalid UTF-8 bytes. -func repairLegacyCharsetString(s string) string { - if s == "" || utf8.ValidString(s) { +func looksLikeUTF8Mojibake(s string) bool { + return utf8MojibakeRE.MatchString(s) +} + +// repairUTF8Mojibake fixes UTF-8 text misread as Latin-1 (e.g. "réactivité" → "réactivité"). +// Repairs pair-by-pair so mixed/corrupted sequences (e.g. NBSP → space in "Déjà") still partially fix. +func repairUTF8Mojibake(s string) string { + if s == "" || !looksLikeUTF8Mojibake(s) { return s } - return repairRawBytesToUTF8([]byte(s)) + runes := []rune(s) + var b strings.Builder + b.Grow(len(s)) + for i := 0; i < len(runes); i++ { + r := runes[i] + if (r == 0xC2 || r == 0xC3) && i+1 < len(runes) { + next := runes[i+1] + if next >= 0x80 && next <= 0xBF { + seq := []byte{byte(r), byte(next)} + if utf8.Valid(seq) { + decoded, _ := utf8.DecodeRune(seq) + b.WriteRune(decoded) + i++ + continue + } + } + } + b.WriteRune(r) + } + out := b.String() + if out == s { + return s + } + return repairLoneMojibakeLeaders(out) +} + +func repairLoneMojibakeLeaders(s string) string { + runes := []rune(s) + var b strings.Builder + b.Grow(len(s)) + for i := 0; i < len(runes); i++ { + r := runes[i] + if r == 0xC3 || r == 0xC2 { + if i+1 < len(runes) && isLoneMojibakeLeaderBoundary(runes[i+1]) { + if r == 0xC3 { + b.WriteRune('à') + } else { + b.WriteRune('Â') + } + continue + } + } + b.WriteRune(r) + } + return b.String() +} + +func isLoneMojibakeLeaderBoundary(r rune) bool { + switch r { + case ' ', '\t', ',', '.', ';', ':', '!', '?': + return true + default: + return false + } +} + +// repairLegacyCharsetString fixes text already loaded as a Go string with invalid UTF-8 bytes. +func repairLegacyCharsetString(s string) string { + if s == "" { + return s + } + if !utf8.ValidString(s) { + s = repairRawBytesToUTF8([]byte(s)) + } + return repairUTF8Mojibake(s) } diff --git a/internal/mail/imap/charset_test.go b/internal/mail/imap/charset_test.go index 4d0eba6..3840cae 100644 --- a/internal/mail/imap/charset_test.go +++ b/internal/mail/imap/charset_test.go @@ -28,6 +28,44 @@ func TestParseBody_iso88591Charset(t *testing.T) { } } +func TestRepairUTF8Mojibake_doubleEncodedFrench(t *testing.T) { + raw := "Si elle bouge, tu perds en réactivité. La NEXOR a été pensée pour ça." + repaired := repairUTF8Mojibake(raw) + want := "Si elle bouge, tu perds en réactivité. La NEXOR a été pensée pour ça." + if repaired != want { + t.Fatalf("repaired = %q, want %q", repaired, want) + } +} + +func TestRepairUTF8Mojibake_longMarketingBody(t *testing.T) { + raw := "Si elle bouge, tu perds en réactivité. La NEXOR a été pensée pour ça. Nylon ultra résistant, boucle rapide, compatible 100% F1 et TSI. Sobre, solide, zéro fioritures. Détache la sangle, passe-la dans les passants, c'est prêt. Déjà adoptée par +8 000 pompiers en Europe. Elle était en rupture. Elle est de retour." + repaired := repairUTF8Mojibake(raw) + if strings.Contains(repaired, "Ã") { + t.Fatalf("repaired still has mojibake: %q", repaired) + } + if !strings.Contains(repaired, "réactivité") || !strings.Contains(repaired, "Déjà") { + t.Fatalf("repaired = %q", repaired) + } +} + +func TestRepairUTF8Mojibake_leavesValidUTF8Untouched(t *testing.T) { + raw := "Si elle bouge, tu perds en réactivité." + if got := repairUTF8Mojibake(raw); got != raw { + t.Fatalf("repaired = %q, want unchanged", got) + } +} + +func TestRepairSnippetWithBodies_mojibakePreview(t *testing.T) { + stored := "Nylon ultra résistant, boucle rapide, zéro fioritures." + got := RepairSnippetWithBodies(stored, "", "") + if strings.Contains(got, "Ã") { + t.Fatalf("snippet = %q, want accents repaired", got) + } + if !strings.Contains(got, "résistant") || !strings.Contains(got, "zéro") { + t.Fatalf("snippet = %q", got) + } +} + func TestRepairLegacyCharsetString_latin1BytesInString(t *testing.T) { // Simulates DB row stored before charset decode (raw Latin-1 bytes in text column). raw := string([]byte{0x56, 0x6f, 0x75, 0x73, 0x20, 0x72, 0xe9, 0x75, 0x6e, 0x69, 0x6f, 0x6e})