Files
Forge-Engine/auth/image_dimensions.py
williamandClaude Sonnet 5 c57420c8c9 Phase 3 : hardening qualite de code - typage strict, securite, dead code, a11y
Config strictement stricte partout (ruff, mypy --strict, bandit, vulture,
import-linter, eslint, stylelint), aucune regle desactivee "pour ne pas
casser le build" - l'existant a ete corrige pour la satisfaire plutot que
l'inverse. Hooks pre-commit locaux (language: system) bloquants.

- Typage mypy --strict propage a tout le moteur (db, screens, auth, core,
  ai, routes, puis publish/scripts/tests/app.py/build_css.py).
- Securite : fuite de handle fichier Windows corrigee dans l'export SCORM
  (routes/publish/export_scorm.py), CSRF/RNG non-crypto/xAPI documentes
  (# nosec, # NOSONAR justifies), nouveau db.json_for_script() (echappe
  "</script>" dans le JSON embarque en <script>, 25 sites).
- Architecture : imports circulaires/F811 nettoyes, contrats
  import-linter respectes, code mort retire (vulture).
- Accessibilite : 69 champs de formulaire sans label correctement
  associe corriges (for/id ou aria-label) sur 11 templates.
- ESLint/Stylelint : lot mecanique JS/CSS, regles ajustees puis
  appliquees (aucune desactivee sans verification individuelle).
- Tests : isolation du compte admin partage (nettoyage ponctuel +
  fixture de teardown automatique en filet de securite), suite complete
  verte (591 tests Python, 241 tests JS).
- SonarQube Community Build self-heberge (Docker + PostgreSQL) : rapport
  complet analyse point par point, faux positifs documentes.
- .gitattributes ajoute (LF force) : core.autocrlf=true sur cette machine
  faisait echouer ESLint (linebreak-style) via un bug connu de git
  (checkout "en place" qui ignore l'eol force sur un fichier deja
  present sur disque - contourne en supprimant puis recreant chaque
  fichier suivi).

djLint (H021, styles inline) volontairement saute pour ce commit -
backlog assume, deja documente, traite dans un lot separe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 16:06:15 +02:00

71 lines
2.7 KiB
Python

import struct
def image_dimensions(content: bytes) -> tuple[int, int] | None:
"""Largeur/hauteur RÉELLES (en pixels) d'une image, lues directement
dans ses en-têtes — SANS dépendance externe (Pillow serait la solution
habituelle, mais un simple parsing des formats courants suffit ici et
évite d'ajouter une dépendance binaire lourde juste pour ça). Renvoie
None si le format n'est pas reconnu (ex. SVG vectoriel sans taille
fixe) — l'appelant retombe alors sur les valeurs par défaut du schéma
(voir screens/scenes/add_scene_object.py)."""
if content[:8] == b"\x89PNG\r\n\x1a\n":
if len(content) >= 24:
width, height = struct.unpack(">II", content[16:24])
return width, height
return None
if content[:6] in (b"GIF87a", b"GIF89a"):
if len(content) >= 10:
width, height = struct.unpack("<HH", content[6:10])
return width, height
return None
if content[:2] == b"BM":
if len(content) >= 26:
width, height = struct.unpack("<ii", content[18:26])
return abs(width), abs(height)
return None
if content[:4] == b"RIFF" and content[8:12] == b"WEBP":
return _webp_dimensions(content)
if content[:2] == b"\xff\xd8":
return _jpeg_dimensions(content)
return None
def _webp_dimensions(content: bytes) -> tuple[int, int] | None:
chunk = content[12:16]
if chunk == b"VP8X" and len(content) >= 30:
width = 1 + (content[24] | (content[25] << 8) | (content[26] << 16))
height = 1 + (content[27] | (content[28] << 8) | (content[29] << 16))
return width, height
if chunk == b"VP8 " and len(content) >= 30:
width, height = struct.unpack("<HH", content[26:30])
return width & 0x3FFF, height & 0x3FFF
if chunk == b"VP8L" and len(content) >= 25:
b = content[21:25]
bits = b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24)
width = (bits & 0x3FFF) + 1
height = ((bits >> 14) & 0x3FFF) + 1
return width, height
return None
def _jpeg_dimensions(content: bytes) -> tuple[int, int] | None:
i = 2
n = len(content)
while i + 9 < n:
if content[i] != 0xFF:
i += 1
continue
marker = content[i + 1]
if marker in (0xD8, 0x01) or 0xD0 <= marker <= 0xD7:
i += 2
continue
if marker == 0xD9:
break
seg_len = struct.unpack(">H", content[i + 2 : i + 4])[0]
if 0xC0 <= marker <= 0xCF and marker not in (0xC4, 0xC8, 0xCC):
height, width = struct.unpack(">HH", content[i + 5 : i + 9])
return width, height
i += 2 + seg_len
return None