web-dev-qa-db-fra.com

désinfectant de chaîne pour le nom de fichier

Je cherche une fonction php qui va assainir une chaîne et la rendre prête à utiliser pour un nom de fichier. Quelqu'un en connaît un pratique?

(Je pourrais en écrire un, mais je crains d'oublier un personnage!)

Edition: pour sauvegarder des fichiers sur un système de fichiers Windows NTFS.

88
user151841

Au lieu de vous soucier de négliger les personnages, pourquoi ne pas utiliser une liste blanche de personnages? Par exemple, vous pouvez autoriser simplement le bon vieux a-z, 0-9, _, et une seule instance d'une période (.). Ceci est évidemment plus limitant que la plupart des systèmes de fichiers, mais devrait vous protéger.

33
Dominic Rodger

En apportant un petit ajustement à la solution de Tor Valamo pour résoudre le problème signalé par Dominic Rodger, vous pouvez utiliser:

// Remove anything which isn't a Word, whitespace, number
// or any of the following caracters -_~,;[]().
// If you don't need to handle multi-byte characters
// you can use preg_replace rather than mb_ereg_replace
// Thanks @Łukasz Rysiak!
$file = mb_ereg_replace("([^\w\s\d\-_~,;\[\]\(\).])", '', $file);
// Remove any runs of periods (thanks falstro!)
$file = mb_ereg_replace("([\.]{2,})", '', $file);
143
Sean Vieira

Pourquoi ne pas utiliser rawurlencode ()? http://www.php.net/manual/en/function.rawurlencode.php

Voici une fonction qui désinfecte même les caractères chinois:

public static function normalizeString ($str = '')
{
    $str = strip_tags($str); 
    $str = preg_replace('/[\r\n\t ]+/', ' ', $str);
    $str = preg_replace('/[\"\*\/\:\<\>\?\'\|]+/', ' ', $str);
    $str = strtolower($str);
    $str = html_entity_decode( $str, ENT_QUOTES, "utf-8" );
    $str = htmlentities($str, ENT_QUOTES, "utf-8");
    $str = preg_replace("/(&)([a-z])([a-z]+;)/i", '$2', $str);
    $str = str_replace(' ', '-', $str);
    $str = rawurlencode($str);
    $str = str_replace('%', '-', $str);
    return $str;
}

Voici l'explication

  1. Supprimer les balises HTML
  2. Supprimer la pause/les onglets/le chariot de retour
  3. Supprimer les caractères illégaux pour le dossier et le nom de fichier
  4. Mettre la ficelle en minuscule
  5. Supprimez les accents étrangers tels que Éàû en le convertissant en entités html, puis supprimez le code et conservez la lettre.
  6. Remplacer les espaces par des tirets
  7. Encodez des caractères spéciaux pouvant passer les étapes précédentes et entrez un nom de fichier conflit sur le serveur. ex. "中文 百强 网"
  8. Remplacez "%" par des tirets pour vous assurer que le navigateur ne réécrira pas le lien du fichier lors de l'interrogation du fichier.

OK, certains noms de fichiers ne seront pas liés mais dans la plupart des cas, cela fonctionnera.

ex. Nom d'origine: "-და-ტიპოგრაფიული. Jpg"

Nom de la sortie: "-E1-83-A1-E1-83-90-E1-83-91-E1-83-94-E1-83-AD-E1-83-93-E1-83-98 - E1- 83-93-E1-83-90 - E1-83-A2-E1-83-98-E1-83-9E-E1-83-9D-E1-83-92-E1-83-A0-E1-83 -90-E1-83-A4-E1-83-98-E1-83-A3-E1-83-9A-E1-83-98.jpg "

C'est mieux comme ça qu'une erreur 404.

J'espère que c'était utile.

Carl.

37

SOLUTION 1 - simple et efficace

$file_name = preg_replace( '/[^a-z0-9]+/', '-', strtolower( $url ) );

  • strtolower () garantit que le nom du fichier est en minuscule (car la casse n'a pas d'importance dans l'URL, mais dans le nom du fichier NTFS)
  • [^a-z0-9]+ Assurera, le nom du fichier ne conserve que des lettres et des chiffres
  • Remplacez les caractères non valides par '-' Pour que le nom du fichier soit lisible

Exemple:

URL:  http://stackoverflow.com/questions/2021624/string-sanitizer-for-filename
File: http-stackoverflow-com-questions-2021624-string-sanitizer-for-filename

SOLUTION 2 - pour les très longues URL

Vous voulez mettre en cache le contenu de l'URL et vous devez simplement avoir un nom de fichier unique. Je voudrais utiliser cette fonction:

$file_name = md5( strtolower( $url ) )

cela créera un nom de fichier avec une longueur fixe. Le hash MD5 est dans la plupart des cas assez unique pour ce type d’utilisation.

Exemple:

URL:  https://www.Amazon.com/Interstellar-Matthew-McConaughey/dp/B00TU9UFTS/ref=s9_nwrsa_gw_g318_i10_r?_encoding=UTF8&fpl=fresh&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=desktop-1&pf_rd_r=BS5M1H560SMAR2JDKYX3&pf_rd_r=BS5M1H560SMAR2JDKYX3&pf_rd_t=36701&pf_rd_p=6822bacc-d4f0-466d-83a8-2c5e1d703f8e&pf_rd_p=6822bacc-d4f0-466d-83a8-2c5e1d703f8e&pf_rd_i=desktop
File: 51301f3edb513f6543779c3a5433b01c
29
Philipp

Voici comment vous pouvez désinfecter un système de fichiers comme demandé

function filter_filename($name) {
    // remove illegal file system characters https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
    $name = str_replace(array_merge(
        array_map('chr', range(0, 31)),
        array('<', '>', ':', '"', '/', '\\', '|', '?', '*')
    ), '', $name);
    // maximise filename length to 255 bytes http://serverfault.com/a/9548/44086
    $ext = pathinfo($name, PATHINFO_EXTENSION);
    $name= mb_strcut(pathinfo($name, PATHINFO_FILENAME), 0, 255 - ($ext ? strlen($ext) + 1 : 0), mb_detect_encoding($name)) . ($ext ? '.' . $ext : '');
    return $name;
}

Tout le reste est autorisé dans un système de fichiers, la réponse à la question est donc parfaite ...

... mais il pourrait être dangereux d'autoriser par exemple les guillemets simples ' dans un nom de fichier si vous l'utilisez plus tard dans un contexte HTML non sécurisé, car ce nom de fichier absolument légal:

 ' onerror= 'alert(document.cookie).jpg

devient un trou XSS :

<img src='<? echo $image ?>' />
// output:
<img src=' ' onerror= 'alert(document.cookie)' />

À cause de cela, le logiciel populaire de CMS Wordpress le supprime et ils apprennent année par année à travers le chemin dur (nombreux rapports de bugs) qu'il est utile d'ajouter de plus en plus de caractères:

$special_chars = array("?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}", "%", "+", chr(0));
// ... a few rows later are whitespaces removed as well ...
preg_replace( '/[\r\n\t -]+/', '-', $filename )

Enfin, leur liste inclut maintenant la plupart des caractères qui font partie de la liste caractères redirigés et caractères non protégés par une URL .

Bien sûr, vous pouvez simplement encoder tous ces caractères sur une sortie HTML, mais la plupart des développeurs et moi-même suivons l'idiome "Mieux vaut prévenir que guérir" et les supprimer à l'avance.

Donc, finalement, je suggérerais d'utiliser ceci:

function filter_filename($filename, $beautify=true) {
    // sanitize filename
    $filename = preg_replace(
        '~
        [<>:"/\\|?*]|            # file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
        [\x00-\x1F]|             # control characters http://msdn.Microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
        [\x7F\xA0\xAD]|          # non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN
        [#\[\]@!$&\'()+,;=]|     # URI reserved https://tools.ietf.org/html/rfc3986#section-2.2
        [{}^\~`]                 # URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt
        ~x',
        '-', $filename);
    // avoids ".", ".." or ".hiddenFiles"
    $filename = ltrim($filename, '.-');
    // optional beautification
    if ($beautify) $filename = beautify_filename($filename);
    // maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
    $ext = pathinfo($filename, PATHINFO_EXTENSION);
    $filename = mb_strcut(pathinfo($filename, PATHINFO_FILENAME), 0, 255 - ($ext ? strlen($ext) + 1 : 0), mb_detect_encoding($filename)) . ($ext ? '.' . $ext : '');
    return $filename;
}

Tout ce qui ne pose pas de problème avec le système de fichiers doit faire partie d'une fonction supplémentaire:

function beautify_filename($filename) {
    // reduce consecutive characters
    $filename = preg_replace(array(
        // "file   name.Zip" becomes "file-name.Zip"
        '/ +/',
        // "file___name.Zip" becomes "file-name.Zip"
        '/_+/',
        // "file---name.Zip" becomes "file-name.Zip"
        '/-+/'
    ), '-', $filename);
    $filename = preg_replace(array(
        // "file--.--.-.--name.Zip" becomes "file.name.Zip"
        '/-*\.-*/',
        // "file...name..Zip" becomes "file.name.Zip"
        '/\.{2,}/'
    ), '.', $filename);
    // lowercase for windows/unix interoperability http://support.Microsoft.com/kb/100625
    $filename = mb_strtolower($filename, mb_detect_encoding($filename));
    // ".file-name.-" becomes "file-name"
    $filename = trim($filename, '.-');
    return $filename;
}

Et à ce stade, vous devez générer un nom de fichier si le résultat est vide et vous pouvez décider si vous souhaitez coder les caractères UTF-8. Mais vous n'en avez pas besoin, car UTF-8 est autorisé dans tous les systèmes de fichiers utilisés dans des contextes d'hébergement Web.

La seule chose que vous devez faire est d’utiliser urlencode() (comme vous le feriez avec toutes vos URL) pour que le nom de fichier საბეჭდი_მანქანა.jpg Devienne cette URL en tant que <img src> Ou <a href>: http://www.maxrev.de/html/img/%E1%83%A1%E1%83%90%90%E1%83%91%E1%83%94%E1 % 83% AD% E1% 83% 93% E1% 83% 98_% E1% 83% 9B% E1% 83% 90% E1% 83% 9C% E1% 83% A5% E1% 83% 90% E1% 83 % 9C% E1% 83% 90.jpg

Stackoverflow le fait, je peux donc poster ce lien comme un utilisateur le ferait:
http://www.maxrev.de/html/img/ საბეჭდი_. jpg

Donc, ceci est un nom de fichier légal complet et pas un problème comme @ SequenceDigitale.com mentionné dans sa réponse .

25
mgutt
preg_replace("[^\w\s\d\.\-_~,;:\[\]\(\]]", '', $file)

Ajouter/supprimer plus de caractères valides en fonction de ce qui est autorisé pour votre système.

Sinon, vous pouvez essayer de créer le fichier, puis de renvoyer une erreur si elle est mauvaise.

13
Tor Valamo

Tempnam () le fera pour vous.

http://us2.php.net/manual/en/function.tempnam.php

mais cela crée un nom entièrement nouveau.

Pour effacer une chaîne existante, limitez simplement ce que vos utilisateurs peuvent saisir et faites en lettres, chiffres, points, traits d'union et traits de soulignement, puis effacez-les avec une simple expression régulière. Vérifiez quels caractères doivent être évités ou vous pourriez obtenir de faux positifs.

$sanitized = preg_replace('/[^a-zA-Z0-9\-\._]/','', $filename);
13
Mark Moline

PHP fournit une fonction pour assainir un texte en différents formats

filter.filters.sanitize

Comment :

echo filter_var(
   "Lorem Ipsum has been the industry's",FILTER_SANITIZE_URL
); 

Blockquote LoremIpsumhasbeentheindustry's

8
120DEV

L'expression suivante crée une chaîne agréable, propre et utilisable:

/[^a-z0-9\._-]+/gi

Conversion facturation financière actuelle en facturation financière aujourd'hui

6
Sampson

En apportant un petit ajustement à la solution de Sean Vieira pour permettre les points simples, vous pouvez utiliser:

preg_replace("([^\w\s\d\.\-_~,;:\[\]\(\)]|[\.]{2,})", '', $file)
6
CarlJohnson

Celles-ci sont peut-être un peu lourdes, mais elles sont suffisamment flexibles pour transformer n'importe quelle chaîne en un nom de fichier ou nom de dossier "sûr" de style en (ou diable, même épuré, même si vous le pliez).

1) Construire un nom de fichier complet (avec un nom de repli dans le cas où l'entrée est totalement tronquée):

str_file($raw_string, $Word_separator, $file_extension, $fallback_name, $length);

2) Ou en utilisant simplement le filtre util sans construire un nom de fichier complet (le mode strict true ne permettra pas [] ou () dans le nom du fichier):

str_file_filter($string, $separator, $strict, $length);

3) Et voici ces fonctions:

// Returns filesystem-safe string after cleaning, filtering, and trimming input
function str_file_filter(
    $str,
    $sep = '_',
    $strict = false,
    $trim = 248) {

    $str = strip_tags(htmlspecialchars_decode(strtolower($str))); // lowercase -> decode -> strip tags
    $str = str_replace("%20", ' ', $str); // convert rogue %20s into spaces
    $str = preg_replace("/%[a-z0-9]{1,2}/i", '', $str); // remove hexy things
    $str = str_replace("&nbsp;", ' ', $str); // convert all nbsp into space
    $str = preg_replace("/&#?[a-z0-9]{2,8};/i", '', $str); // remove the other non-tag things
    $str = preg_replace("/\s+/", $sep, $str); // filter multiple spaces
    $str = preg_replace("/\.+/", '.', $str); // filter multiple periods
    $str = preg_replace("/^\.+/", '', $str); // trim leading period

    if ($strict) {
        $str = preg_replace("/([^\w\d\\" . $sep . ".])/", '', $str); // only allow words and digits
    } else {
        $str = preg_replace("/([^\w\d\\" . $sep . "\[\]\(\).])/", '', $str); // allow words, digits, [], and ()
    }

    $str = preg_replace("/\\" . $sep . "+/", $sep, $str); // filter multiple separators
    $str = substr($str, 0, $trim); // trim filename to desired length, note 255 char limit on windows

    return $str;
}


// Returns full file name including fallback and extension
function str_file(
    $str,
    $sep = '_',
    $ext = '',
    $default = '',
    $trim = 248) {

    // Run $str and/or $ext through filters to clean up strings
    $str = str_file_filter($str, $sep);
    $ext = '.' . str_file_filter($ext, '', true);

    // Default file name in case all chars are trimmed from $str, then ensure there is an id at tail
    if (empty($str) && empty($default)) {
        $str = 'no_name__' . date('Y-m-d_H-m_A') . '__' . uniqid();
    } elseif (empty($str)) {
        $str = $default;
    }

    // Return completed string
    if (!empty($ext)) {
        return $str . $ext;
    } else {
        return $str;
    }
}

Donc, supposons que certaines entrées utilisateur soient: .....&lt;div&gt;&lt;/div&gt;<script></script>&amp; Weiß Göbel 中文百强网File name %20 %20 %21 %2C Décor \/. /. . z \... y \...... x ./ “This name” is & 462^^ not &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = that grrrreat -][09]()1234747) საბეჭდი-და-ტიპოგრაფიული

Et nous voulons le convertir en quelque chose de plus convivial pour créer un fichier tar.gz avec une longueur de nom de fichier de 255 caractères. Voici un exemple d'utilisation. Remarque: cet exemple inclut une extension tar.gz malformée en guise de validation technique. Vous devez néanmoins filtrer l'extension après que la chaîne a été créée en fonction de votre liste blanche.

$raw_str = '.....&lt;div&gt;&lt;/div&gt;<script></script>&amp; Weiß Göbel 中文百强网File name  %20   %20 %21 %2C Décor  \/.  /. .  z \... y \...... x ./  “This name” is & 462^^ not &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = that grrrreat -][09]()1234747) საბეჭდი-და-ტიპოგრაფიული';
$fallback_str = 'generated_' . date('Y-m-d_H-m_A');
$bad_extension = '....t&+++a()r.gz[]';

echo str_file($raw_str, '_', $bad_extension, $fallback_str);

Le résultat serait: _wei_gbel_file_name_dcor_._._._z_._y_._x_._this_name_is_462_not_that_grrrreat_][09]()1234747)_.tar.gz

Vous pouvez y jouer ici: https://3v4l.org/iSgi8

Ou un élément essentiel: https://Gist.github.com/dhaupin/b109d3a8464239b7754a

EDIT: filtre de script mis à jour pour &nbsp; Au lieu de l'espace, lien 3v4l mis à jour

2
dhaupin

Il semble que tout dépend de la question: est-il possible de créer un nom de fichier qui peut être utilisé pour pirater un serveur (ou causer un dommage quelconque)? Sinon, il semble que la solution simple soit d'essayer de créer le fichier là où il sera utilisé (en fin de compte, c'est le système d'exploitation de votre choix, sans aucun doute). Laissez le système d’exploitation le résoudre. S'il se plaint, signalez-le à l'utilisateur sous forme d'erreur de validation.

Cela présente l'avantage supplémentaire d'être portable de manière fiable, car tous les systèmes d'exploitation (j'en suis presque sûr) se plaindront si le nom du fichier n'est pas correctement formé pour ce système d'exploitation.

Si est possible de faire des choses néfastes avec un nom de fichier, certaines mesures peuvent éventuellement être appliquées avant de tester le nom de fichier sur le système d'exploitation résident - mesures moins compliqué qu'un "assainissement" complet du nom de fichier.

1
ReverseEMF

Le meilleur que je connaisse aujourd'hui est la méthode statique Strings :: webalize du framework Nette.

BTW, cela traduit tous les signes diacritiques à leur base .. š => s ü => u ß => ss etc.

Pour les noms de fichiers, vous devez ajouter un point "." paramètre de caractères autorisés.

/**
 * Converts to ASCII.
 * @param  string  UTF-8 encoding
 * @return string  ASCII
 */
public static function toAscii($s)
{
    static $transliterator = NULL;
    if ($transliterator === NULL && class_exists('Transliterator', FALSE)) {
        $transliterator = \Transliterator::create('Any-Latin; Latin-ASCII');
    }

    $s = preg_replace('#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{2FF}\x{370}-\x{10FFFF}]#u', '', $s);
    $s = strtr($s, '`\'"^~?', "\x01\x02\x03\x04\x05\x06");
    $s = str_replace(
        array("\xE2\x80\x9E", "\xE2\x80\x9C", "\xE2\x80\x9D", "\xE2\x80\x9A", "\xE2\x80\x98", "\xE2\x80\x99", "\xC2\xB0"),
        array("\x03", "\x03", "\x03", "\x02", "\x02", "\x02", "\x04"), $s
    );
    if ($transliterator !== NULL) {
        $s = $transliterator->transliterate($s);
    }
    if (ICONV_IMPL === 'glibc') {
        $s = str_replace(
            array("\xC2\xBB", "\xC2\xAB", "\xE2\x80\xA6", "\xE2\x84\xA2", "\xC2\xA9", "\xC2\xAE"),
            array('>>', '<<', '...', 'TM', '(c)', '(R)'), $s
        );
        $s = @iconv('UTF-8', 'WINDOWS-1250//TRANSLIT//IGNORE', $s); // intentionally @
        $s = strtr($s, "\xa5\xa3\xbc\x8c\xa7\x8a\xaa\x8d\x8f\x8e\xaf\xb9\xb3\xbe\x9c\x9a\xba\x9d\x9f\x9e"
            . "\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3"
            . "\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8"
            . "\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe"
            . "\x96\xa0\x8b\x97\x9b\xa6\xad\xb7",
            'ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt- <->|-.');
        $s = preg_replace('#[^\x00-\x7F]++#', '', $s);
    } else {
        $s = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); // intentionally @
    }
    $s = str_replace(array('`', "'", '"', '^', '~', '?'), '', $s);
    return strtr($s, "\x01\x02\x03\x04\x05\x06", '`\'"^~?');
}


/**
 * Converts to web safe characters [a-z0-9-] text.
 * @param  string  UTF-8 encoding
 * @param  string  allowed characters
 * @param  bool
 * @return string
 */
public static function webalize($s, $charlist = NULL, $lower = TRUE)
{
    $s = self::toAscii($s);
    if ($lower) {
        $s = strtolower($s);
    }
    $s = preg_replace('#[^a-z0-9' . preg_quote($charlist, '#') . ']+#i', '-', $s);
    $s = trim($s, '-');
    return $s;
}
1
DnD

/ et .. dans le nom de fichier fourni par l'utilisateur peut être dangereux. Donc, vous devriez vous en débarrasser par quelque chose comme:

$fname = str_replace('..', '', $fname);
$fname = str_replace('/',  '', $fname);
0
gameover

safe: remplace chaque séquence de NOT "a-zA-Z0-9_-" par un tiret; ajoutez vous-même une extension.

$name = preg_replace('/[^a-zA-Z0-9_-]+/', '-', strtolower($name)).$extension;
0
commonpike

une manière

$bad='/[\/:*?"<>|]/';
$string = 'fi?le*';

function sanitize($str,$pat)
{
    return preg_replace($pat,"",$str);

}
echo sanitize($string,$bad);
0
ghostdog74