Le besoin était simple : partager des fichiers volumineux — des dizaines de gigaoctets de rushes, des archives de projets, des vidéos de formation — sans passer par un cloud tiers. Pas de Google Drive, pas de WeTransfer, pas de Dropbox. Un serveur dédié, des fichiers qu'on contrôle, des liens qui expirent quand on veut. Résultat : ShareBox, une plateforme de partage de fichiers auto-hébergée avec streaming vidéo intégré, écrite en PHP 8.1 pur sans framework.
Le repo est sur GitHub.
Zéro dépendance runtime
Le premier choix — et le plus structurant — était de ne pas utiliser de framework.
Pas de Symfony, pas de Laravel. PHP pur, stdlib native, SQLite auto-créé au premier
démarrage. Composer est présent, mais uniquement en dépendance de développement pour
PHPUnit. En production, aucun vendor/ n'est nécessaire.
Ce choix contraint, mais il clarifie. Chaque feature doit être justifiée par son coût d'implémentation réel — pas externalisée dans un package qui fait dix fois ce dont on a besoin. Routing manuel en quelques dizaines de lignes, gestion de sessions native PHP, requêtes SQLite avec PDO : ce sont des choses qu'on fait une fois et qu'on comprend entièrement.
SQLite s'est imposé pour la même raison : pas de serveur à configurer, pas de migrations
complexes, le fichier de base de données vit à côté du code et se sauvegarde avec un
simple cp. Pour un outil mono-instance, c'est largement suffisant.
L'installateur suit la même logique — un one-liner Debian/Ubuntu :
curl -sSL https://raw.githubusercontent.com/ohugonnot/sharebox/main/install.sh | bash
Un script qui installe les dépendances système, configure Apache ou nginx, crée la base SQLite, et pose les permissions correctes. Pas de Docker obligatoire, pas de registry à contacter.
Le vrai défi : le streaming vidéo
Partager un fichier texte ou une archive ZIP, c'est trivial. Partager une vidéo et permettre de la regarder directement dans le navigateur — avec seek, sélection de piste audio, sous-titres — c'est une autre affaire.
La première décision est d'éviter le transcodage systématique. Si le fichier est déjà dans un format compatible navigateur (H.264/AAC dans un conteneur MP4 ou MKV), on fait un remux à la volée : on réemballe les flux existants sans les réencoder. FFmpeg travaille en quelques secondes, le CPU est quasi inutilisé, et la qualité est identique à l'original.
Le transcodage complet intervient uniquement quand c'est nécessaire — typiquement pour les fichiers HEVC/x265, que très peu de navigateurs décodent nativement. Dans ce cas, FFmpeg transcrit vers H.264 à la volée, avec choix de qualité : 480p, 720p, ou 1080p.
Le seek dans un stream transcodé à la volée est le point le plus délicat.
Quand on fait Range: bytes=... sur un stream HLS classique, ça fonctionne
parce que les segments sont pré-générés. Ici, le stream est généré en temps réel —
il faut donc gérer le seek en relançant FFmpeg avec -ss au bon timecode,
plutôt qu'en cherchant un offset byte dans un fichier déjà généré. Le code qui
orchestre ça dans stream.php mérite d'être lu si vous attaquez un
problème similaire.
Le lecteur vidéo intégré : seek natif, sélection de qualité, sous-titres WebVTT.
Les slugs lisibles
Un UUID pour identifier un partage, c'est sécurisé mais inutilisable humainement.
Recevoir /dl/3f7a2c1d-8b4e-4f9a-b2d6-1c5e8f3a7b2c dans un email,
c'est opaque. ShareBox génère des slugs à partir du nom de fichier :
batman-begins-2005.mkv → /dl/batman-begins-2005-x7k2
Le suffixe aléatoire de 4 caractères suffit à éviter les collisions sans sacrifier
la lisibilité. Le fichier batman-begins-2005-x7k2 dit immédiatement
ce qu'il contient, reste copiable à la main, et passe sans problème dans un message
ou une URL.
La génération du slug normalise la chaîne : translittération des accents, conversion en minuscules, remplacement des espaces et caractères spéciaux par des tirets, suppression des tirets consécutifs. Le résultat est ensuite vérifié contre la base pour garantir l'unicité avant insertion.
Sécurité sans framework
Un framework gère discrètement une quantité de problèmes de sécurité — souvent sans que le développeur en soit conscient. Sans framework, ces problèmes doivent être adressés explicitement. En voici quatre qui méritaient une attention particulière.
Traversée de répertoires. Quand on sert des fichiers depuis le
disque, il faut s'assurer qu'un paramètre comme ../../../etc/passwd
ne passe pas. La solution : realpath() sur le chemin résolu, puis
vérification que le résultat est bien un sous-chemin du répertoire d'upload autorisé.
Si le chemin résolu sort du répertoire cible, la requête est rejetée.
Protection CSRF. Le panneau admin expose des actions destructrices (suppression de fichiers, révocation de liens). Chaque formulaire POST inclut un token CSRF stocké en session, regénéré après chaque action sensible.
Session fixation. Après authentification, on appelle
session_regenerate_id(true) pour invalider l'ancien identifiant de session
et en émettre un nouveau. Sans ça, un attaquant qui connaît l'identifiant de session
d'un visiteur non authentifié peut le réutiliser après connexion.
Injection via headers mail. Les formulaires d'envoi par email acceptent une adresse de destination. Sans validation stricte, un attaquant peut injecter des headers SMTP supplémentaires dans le champ email et détourner l'envoi. Toutes les entrées utilisateur destinées aux headers sont filtrées pour supprimer les sauts de ligne avant d'être passées aux fonctions d'envoi.
Le panneau admin : gestion des partages, expiration, protection par mot de passe.
Les tests : ce qui vaut la peine d'être couvert
44 tests PHPUnit, sans mock framework, sans dépendance externe en test. La question n'était pas "comment tester tout" — c'était "qu'est-ce qui casse silencieusement si on ne teste pas".
Trois catégories ressortent comme particulièrement utiles :
Les slugs. La génération de slugs est une fonction pure avec beaucoup de cas limites : noms avec accents, espaces multiples, caractères Unicode, collisions, noms de fichiers sans extension. Des tests exhaustifs sur cette fonction valent leur poids en or — c'est exactement le genre de code qui semble trivial et qui casse de façon inattendue sur des entrées réelles.
La détection de formats. La logique qui décide si une vidéo peut être remuxée ou doit être transcodée repose sur la lecture des métadonnées FFprobe. Tester avec des fixtures de sortie FFprobe pour différents codecs (H.264, HEVC, VP9, AV1) garantit que la décision remux/transcode est correcte sans avoir besoin de vrais fichiers vidéo dans la suite de tests.
La sécurité. Les tests de traversée de répertoires, d'injection de headers, et de validation des tokens CSRF sont les plus précieux. Ce sont des vecteurs d'attaque bien documentés, ils se testent facilement avec des payloads connus, et rater un cas limite a des conséquences directes.
Le partage de dossiers : navigation des fichiers, téléchargement ZIP en un clic.
Conclusion
La philosophie "zéro dépendance runtime" n'est pas une posture. C'est un engagement sur ce qu'on comprend et sur ce qu'on maintient. Chaque ligne de code dans ShareBox a une raison d'être explicite — pas de magie cachée dans un composant tiers dont on n'a jamais lu les sources.
Ce que ce projet a concrètement appris : PHP est capable de faire du streaming vidéo adaptatif, du seek dans un transcodage à la volée, et de la sécurité correcte — sans le moindre framework. La difficulté n'est pas dans le langage, elle est dans la compréhension de ce qu'on construit.
Le code est disponible sur github.com/ohugonnot/sharebox.