La librairie Python Werkzeug
Werkzeug est une bibliothèque d'outils pour les applications WSGI, en particulier utilisée avec Flask.
Petite aparté sur WSGI
Web Server Gateway Interface (WSGI) est une norme Python qui définit une interface simple entre un serveur et une application web.
Fonctionnement
WSGI sépare le rôle du serveur web de celui de l'application. Le serveur gère les requêtes HTTP et les transmet à l'application. L'application traite la requête et renvoie une réponse.
FastAPI
FastAPI utilise la norme ASGI (Asynchronous Server Gateway Interface), qui permet une meilleure gestion de la concurrence. C'est plus rapide et mieux adapté au contexte actuel du développement web.
werkzeug et FastAPI
Werkzeug est une bibliothèque WSGI. Par conséquent, il n'est pas pertinent de l'utiliser dans une application FastAPI qui repose sur ASGI.
Il est donc temps de dire au revoir à Werkzeug et de recréer la fonction secure_filename
.
La fonction secure_filename
https://tedboy.github.io/flask/_modules/werkzeug/utils.html#secure_filename
Cette fonction accepte une chaîne de caractères en paramètre et la transforme, si nécessaire, pour assurer une gestion sécurisée des fichiers.
Les transformations appliquées sont principalement :
- Remplacement des caractères non sûrs (par exemple
"\\ "
) par des underscores - Conservation uniquement des caractères suivants :
a-zA-Z0-9_-
Cela permet d'obtenir un nom de fichier facile à manipuler.
Comme illustré dans la documentation, voici quelques exemples :
>>> secure_filename("My cool movie.mov")
'My_cool_movie.mov'
>>> secure_filename("../../../etc/passwd")
'etc_passwd'
>>> secure_filename(u'i contain cool \xfcml\xe4uts.txt')
'i_contain_cool_umlauts.txt'
Ma première idée était de supprimer tous les caractères spéciaux et de ne garder que les lettres :
def secure_filename(filename: str) -> str:
filename = filename.strip().replace(" ", "_")
filename = re.sub(r'[^a-zA-Z0-9_.-]', '', filename)
return filename
Cependant, cela ne fonctionne pas parfaitement, car il faut d'abord séparer le nom du fichier de son extension, puis modifier uniquement la partie du nom. Après un petit prompt pour me faciliter le travail:
def secure_filename(filename: str) -> str:
# Strip leading/trailing whitespace and replace problematic characters
match = re.match(r"(.+)\.(png|jpg|jpeg)$", filename, re.IGNORECASE)
if match is not None:
name, ext = match.groups()
# Replace space or . by "_"
name = re.sub(r"[ \.]", "_", name) # Remove any path separators or unsafe characters #
name = re.sub(r'[^a-zA-Z0-9_-]', '', name) # erase all _ at start or at the end of the word #
name = re.sub(r'^_*|_*$', '', name)
filename = f"{name}.{ext.lower()}"
return filename
else:
raise HTTPException(status_code=401, detail="Image must have a name and have a .png, .jpeg, .jpg extension")
Mais là encore, ce n'est pas parfait... Comment gérer un fichier nommé "weird!@#$#%!file.png"
?
Le résultat devrait être "weird_file.png"
, mais ce n'est pas le cas avec ce code.
Je dois donc isoler les blocs composés uniquement de caractères alphabétiques, et chaque partie doit être dans un tuple. Grâce à ChatGPT, j'ai découvert la fonction re.findall()
. Top, c'est exactement ce dont j'ai besoin. Je me suis ensuite souvenu de la méthode "".join(tuple)
qui crée une chaîne de caractères à partir des éléments d'un tuple.
Plus besoin de toutes les manipulations de string, voici donc la version finale :
def secure_filename(filename: str) -> str:
# Strip leading/trailing whitespace and replace problematic characters
match = re.match(r"(.+)\.(png|jpg|jpeg)$", filename, re.IGNORECASE)
if match is not None:
name, ext = match.groups()
groups = re.findall(r"[a-zA-Z]+", name)
name = "_".join(groups)
return filename
else:
raise HTTPException(status_code=401, detail="Image must have a name and have a .png, .jpeg, .jpg extension")
Bravo to myLLM&me