web-dev-qa-db-fra.com

Désinfection des chaînes pour les rendre sûres en URL et en nom de fichier?

J'essaie de créer une fonction qui désinfecte certaines chaînes de manière à ce qu'elles puissent être utilisées en toute sécurité dans l'URL (comme un post slug) et également en tant que noms de fichiers. Par exemple, lorsque quelqu'un télécharge un fichier, je veux m'assurer que je supprime tous les caractères dangereux du nom.

Jusqu'ici, j'ai mis au point la fonction suivante qui, j'espère, résoudra ce problème et autorisera également les données UTF-8 étrangères.

/**
 * Convert a string to the file/URL safe "slug" form
 *
 * @param string $string the string to clean
 * @param bool $is_filename TRUE will allow additional filename characters
 * @return string
 */
function sanitize($string = '', $is_filename = FALSE)
{
 // Replace all weird characters with dashes
 $string = preg_replace('/[^\w\-'. ($is_filename ? '~_\.' : ''). ']+/u', '-', $string);

 // Only allow one dash separator at a time (and make string lowercase)
 return mb_strtolower(preg_replace('/--+/u', '-', $string), 'UTF-8');
}

Quelqu'un a-t-il des exemples de données délicats que je peux utiliser contre cela - ou connaissez-vous un meilleur moyen de protéger nos applications contre les mauvais noms?

$ is-nomfichier autorise quelques caractères supplémentaires, tels que les fichiers temp vim

update: supprime le caractère étoile car je ne pouvais pas penser à une utilisation valide

130
Xeoncross

Quelques observations sur votre solution:

  1. 'u' à la fin de votre modèle signifie que le pattern, et non le texte auquel il correspond, sera interprété comme UTF-8 (je présume que vous avez supposé ce dernier?). 
  2. \ w correspond au caractère de soulignement. Vous l'incluez spécifiquement pour les fichiers, ce qui laisse supposer que vous ne les voulez pas dans les URL, mais dans le code que vous avez, les URL seront autorisées à inclure un trait de soulignement.
  3. L'inclusion de "UTF-8 étranger" semble dépendre des paramètres régionaux. Il n'est pas clair s'il s'agit de l'environnement local du serveur ou du client. À partir de la documentation PHP:

Un caractère "mot" est une lettre ou un chiffre ou le caractère de soulignement, c'est-à-dire tout caractère pouvant faire partie d'un "mot" Perl. La définition des lettres et des chiffres est contrôlée par les tables de caractères de PCRE et peut varier si une correspondance spécifique aux paramètres régionaux est en cours. Par exemple, dans les paramètres régionaux "fr" (français), certains codes de caractère supérieurs à 128 sont utilisés pour les lettres accentuées, et ils correspondent à\w.

Créer la limace

Vous ne devriez probablement pas inclure de caractères accentués, etc., dans votre post slug car, techniquement, ils devraient être encodés en pourcentage (selon les règles de codage d'URL) afin que vous ayez des URLs laides.

Donc, si j'étais vous, après la minuscule, je convertirais les caractères "spéciaux" en leurs équivalents (par exemple, é -> e) et remplacerais les caractères non [az] par "-", en limitant les exécutions d'un seul "-". comme tu l'as fait. Il existe une implémentation de conversion de caractères spéciaux ici: https://web.archive.org/web/20130208144021/http://neo22s.com/slug

Désinfection en général

OWASP dispose d'une implémentation PHP de son API de sécurité d'entreprise, qui inclut, entre autres, des méthodes permettant de coder et de décoder en toute sécurité les entrées et les sorties dans votre application. 

L'interface de l'encodeur fournit:

canonicalize (string $input, [bool $strict = true])
decodeFromBase64 (string $input)
decodeFromURL (string $input)
encodeForBase64 (string $input, [bool $wrap = false])
encodeForCSS (string $input)
encodeForHTML (string $input)
encodeForHTMLAttribute (string $input)
encodeForJavaScript (string $input)
encodeForOS (Codec $codec, string $input)
encodeForSQL (Codec $codec, string $input)
encodeForURL (string $input)
encodeForVBScript (string $input)
encodeForXML (string $input)
encodeForXMLAttribute (string $input)
encodeForXPath (string $input)

https://github.com/OWASP/PHP-ESAPIhttps://www.owasp.org/index.php/Category:OWASP_Enterprise_Security_API

56
Alan Donnelly

J'ai trouvé cette fonction plus importante dans le code Chyrp :

/**
 * Function: sanitize
 * Returns a sanitized string, typically for URLs.
 *
 * Parameters:
 *     $string - The string to sanitize.
 *     $force_lowercase - Force the string to lowercase?
 *     $anal - If set to *true*, will remove all non-alphanumeric characters.
 */
function sanitize($string, $force_lowercase = true, $anal = false) {
    $strip = array("~", "`", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "=", "+", "[", "{", "]",
                   "}", "\\", "|", ";", ":", "\"", "'", "‘", "’", "“", "”", "–", "—",
                   "—", "–", ",", "<", ".", ">", "/", "?");
    $clean = trim(str_replace($strip, "", strip_tags($string)));
    $clean = preg_replace('/\s+/', "-", $clean);
    $clean = ($anal) ? preg_replace("/[^a-zA-Z0-9]/", "", $clean) : $clean ;
    return ($force_lowercase) ?
        (function_exists('mb_strtolower')) ?
            mb_strtolower($clean, 'UTF-8') :
            strtolower($clean) :
        $clean;
}

et celui-ci dans le wordpress code

/**
 * Sanitizes a filename replacing whitespace with dashes
 *
 * Removes special characters that are illegal in filenames on certain
 * operating systems and special characters requiring special escaping
 * to manipulate at the command line. Replaces spaces and consecutive
 * dashes with a single dash. Trim period, dash and underscore from beginning
 * and end of filename.
 *
 * @since 2.1.0
 *
 * @param string $filename The filename to be sanitized
 * @return string The sanitized filename
 */
function sanitize_file_name( $filename ) {
    $filename_raw = $filename;
    $special_chars = array("?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}");
    $special_chars = apply_filters('sanitize_file_name_chars', $special_chars, $filename_raw);
    $filename = str_replace($special_chars, '', $filename);
    $filename = preg_replace('/[\s-]+/', '-', $filename);
    $filename = trim($filename, '.-_');
    return apply_filters('sanitize_file_name', $filename, $filename_raw);
}

Mise à jour septembre 2012

Alix Axel a fait un travail incroyable dans ce domaine. Son framework phunction inclut plusieurs grands filtres et transformations de texte.

86
Xeoncross

Cela devrait sécuriser vos noms de fichiers ...

$string = preg_replace(array('/\s/', '/\.[\.]+/', '/[^\w_\.\-]/'), array('_', '.', ''), $string);

et une solution plus profonde à ceci est:

// Remove special accented characters - ie. sí.
$clean_name = strtr($string, array('Š' => 'S','Ž' => 'Z','š' => 's','ž' => 'z','Ÿ' => 'Y','À' => 'A','Á' => 'A','Â' => 'A','Ã' => 'A','Ä' => 'A','Å' => 'A','Ç' => 'C','È' => 'E','É' => 'E','Ê' => 'E','Ë' => 'E','Ì' => 'I','Í' => 'I','Î' => 'I','Ï' => 'I','Ñ' => 'N','Ò' => 'O','Ó' => 'O','Ô' => 'O','Õ' => 'O','Ö' => 'O','Ø' => 'O','Ù' => 'U','Ú' => 'U','Û' => 'U','Ü' => 'U','Ý' => 'Y','à' => 'a','á' => 'a','â' => 'a','ã' => 'a','ä' => 'a','å' => 'a','ç' => 'c','è' => 'e','é' => 'e','ê' => 'e','ë' => 'e','ì' => 'i','í' => 'i','î' => 'i','ï' => 'i','ñ' => 'n','ò' => 'o','ó' => 'o','ô' => 'o','õ' => 'o','ö' => 'o','ø' => 'o','ù' => 'u','ú' => 'u','û' => 'u','ü' => 'u','ý' => 'y','ÿ' => 'y'));
$clean_name = strtr($clean_name, array('Þ' => 'TH', 'þ' => 'th', 'Ð' => 'DH', 'ð' => 'dh', 'ß' => 'ss', 'Œ' => 'OE', 'œ' => 'oe', 'Æ' => 'AE', 'æ' => 'ae', 'µ' => 'u'));

$clean_name = preg_replace(array('/\s/', '/\.[\.]+/', '/[^\w_\.\-]/'), array('_', '.', ''), $clean_name);

Cela suppose que vous souhaitiez un point dans le nom du fichier . Si vous souhaitez le transférer en minuscule, utilisez simplement

$clean_name = strtolower($clean_name);

pour la dernière ligne.

30
SoLoGHoST

Essaye ça:

function normal_chars($string)
{
    $string = htmlentities($string, ENT_QUOTES, 'UTF-8');
    $string = preg_replace('~&([a-z]{1,2})(acute|cedil|circ|Grave|lig|orn|ring|slash|th|tilde|uml);~i', '$1', $string);
    $string = html_entity_decode($string, ENT_QUOTES, 'UTF-8');
    $string = preg_replace(array('~[^0-9a-z]~i', '~[ -]+~'), ' ', $string);

    return trim($string, ' -');
}

Examples:

echo normal_chars('Álix----_Ãxel!?!?'); // Alix Axel
echo normal_chars('áéíóúÁÉÍÓÚ'); // aeiouAEIOU
echo normal_chars('üÿÄËÏÖÜŸåÅ'); // uyAEIOUYaA

Basé sur la réponse sélectionnée dans ce fil de discussion: URL Friendly Nom d'utilisateur en PHP?

21
John Conde

Ce n'est pas vraiment une réponse car cela ne fournit pas de solution (encore!), Mais c'est trop gros pour tenir sur un commentaire ...


J'ai fait quelques tests (concernant les noms de fichiers) sur Windows 7 et Ubuntu 12.04 et ce que j'ai découvert est que:

1. PHP Impossible de gérer les noms de fichiers non-ASCII

Bien que Windows et Ubuntu puissent tous deux gérer les noms de fichiers Unicode (même ceux RTL comme il semble), PHP 5.3 nécessite des hacks pour gérer même l'ancien ISO-8859-1, il est donc préférable de le conserver ASCII uniquement. Pour la sécurité.

2. La longueur du nom de fichier est importante (spécialement sous Windows)

Sur Ubuntu, la longueur maximale d'un nom de fichier (extension incluse) est de 255 (chemin exclu):

/var/www/uploads/123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345/

Cependant, sous Windows 7 (NTFS), la longueur maximale d’un nom de fichier dépend de son chemin absolu:

(0 + 0 + 244 + 11 chars) C:\1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234\1234567.txt
(0 + 3 + 240 + 11 chars) C:\123\123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\1234567.txt
(3 + 3 + 236 + 11 chars) C:\123\456\12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456\1234567.txt

_ { Wikipedia } _ dit que:

NTFS autorise chaque composant de chemin (répertoire ou nom de fichier) à 255 Longs caractères.

Au meilleur de ma connaissance (et de mes tests), c'est faux.

Au total (en comptant les barres obliques), tous ces exemples ont 259 caractères, si vous supprimez le C:\ qui donne 256 caractères (et non 255?!). Les répertoires ont été créés à l'aide de l'explorateur et vous remarquerez qu'il se limite à utiliser tout l'espace disponible pour le nom du répertoire. La raison en est d’autoriser la création de fichiers en utilisant la convention de dénomination de fichier 8.3 . La même chose se passe pour les autres partitions.

Les fichiers n'ont pas besoin de réserver les exigences de longueur 8.3:

(255 chars) E:\12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901.txt

Vous ne pouvez plus créer de sous-répertoires si le chemin absolu du répertoire parent comporte plus de 242 caractères, car 256 = 242 + 1 + \ + 8 + . + 3. À l'aide de l'Explorateur Windows, vous ne pouvez pas créer un autre répertoire si le répertoire parent comporte plus de 233 caractères (selon les paramètres régionaux du système), car 256 = 233 + 10 + \ + 8 + . + 3; le 10 ici est la longueur de la chaîne New folder.

Le système de fichiers Windows pose un problème désagréable si vous souhaitez assurer l'interopérabilité entre les systèmes de fichiers.

3. Méfiez-vous des caractères réservés et des mots clés

En plus de supprimer les caractères non-ASCII, non imprimables et les caractères de contrôle, vous devez également indiquer (placer/déplacer):

"*/:<>?\|

La suppression de ces caractères n'est peut-être pas la meilleure idée, car le nom du fichier peut perdre une partie de sa signification. Je pense qu'au minimum, plusieurs occurrences de ces caractères devraient être remplacées par un simple soulignement (_), ou peut-être quelque chose de plus représentatif (ce n'est qu'une idée):

  • "*? -> _
  • /\| -> -
  • : -> [ ]-[ ]
  • < -> (
  • > -> )

Il y a aussi mots clés spéciaux à éviter (comme NUL), bien que je ne sois pas sûr de la façon de surmonter cela. Peut-être une liste noire avec un repli de nom aléatoire serait une bonne approche pour la résoudre.

4. Sensibilité à la casse

Cela devrait aller de soi, mais si vous voulez garantir l'unicité des fichiers sur différents systèmes d'exploitation, vous devez transformer les noms de fichiers en un cas normalisé. Ainsi, my_file.txt et My_File.txt sous Linux ne deviendront pas tous deux le même fichier my_file.txt sous Windows.

5. Assurez-vous que c'est unique

Si le nom de fichier existe déjà, un identificateur unique doit être ajouté à son nom de fichier de base.

Les identifiants uniques courants incluent l'horodatage UNIX, un résumé du contenu du fichier ou une chaîne aléatoire.

6. Fichiers cachés

Ce n'est pas parce que ça peut être nommé que ça devrait ...

Les points sont généralement inscrits sur la liste blanche dans les noms de fichiers, mais sous Linux, un fichier caché est représenté par un point en tête.

7. Autres considérations

Si vous devez supprimer certains caractères du nom de fichier, l'extension est généralement plus importante que le nom de base du fichier. En autorisant un nombre maximal considérable de caractères pour l'extension de fichier } (8-16), il convient de supprimer les caractères du nom de base. Il est également important de noter que dans l'éventualité peu probable d'une extension longue - telle que _.graphmlz.tag.gz - _.graphmlz.tag uniquement _, elle devrait être considérée comme le nom de base du fichier.

8. Ressources

Calibre gère le nom de fichier en manipulant assez décemment:

_ { Page Wikipedia sur les noms de fichiers malmenés } et lié chapitre tiré de Utilisation de Samba .


Si, par exemple, vous essayez de créer un fichier qui enfreint l'une des règles 1/2/3, vous obtiendrez une erreur très utile:

Warning: touch(): Unable to create file ... because No error in ... on line ...
13
Alix Axel

J'ai toujours pensé Kohana a fait du bon travail .

public static function title($title, $separator = '-', $ascii_only = FALSE)
{
if ($ascii_only === TRUE)
{
// Transliterate non-ASCII characters
$title = UTF8::transliterate_to_ascii($title);

// Remove all characters that are not the separator, a-z, 0-9, or whitespace
$title = preg_replace('![^'.preg_quote($separator).'a-z0-9\s]+!', '', strtolower($title));
}
else
{
// Remove all characters that are not the separator, letters, numbers, or whitespace
$title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', UTF8::strtolower($title));
}

// Replace all separator characters and whitespace by a single separator
$title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title);

// Trim separators from the beginning and end
return trim($title, $separator);
}

Le nom pratique UTF8::transliterate_to_ascii() va transformer des choses comme ñ => n.

Bien sûr, vous pouvez remplacer les autres éléments UTF8::* par des fonctions mb_ *.

11
alex

et ceci est la version Joomla 3.3.2 de JFile::makeSafe($file)

public static function makeSafe($file)
{
    // Remove any trailing dots, as those aren't ever valid file names.
    $file = rtrim($file, '.');

    $regex = array('#(\.){2,}#', '#[^A-Za-z0-9\.\_\- ]#', '#^\.#');

    return trim(preg_replace($regex, '', $file));
}
5
cedric.walter

J'ai adapté d'une autre source et ajouté un couple supplémentaire, peut-être un peu exagéré

/**
 * Convert a string into a url safe address.
 *
 * @param string $unformatted
 * @return string
 */
public function formatURL($unformatted) {

    $url = strtolower(trim($unformatted));

    //replace accent characters, forien languages
    $search = array('À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'ß', 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'ÿ', 'Ā', 'ā', 'Ă', 'ă', 'Ą', 'ą', 'Ć', 'ć', 'Ĉ', 'ĉ', 'Ċ', 'ċ', 'Č', 'č', 'Ď', 'ď', 'Đ', 'đ', 'Ē', 'ē', 'Ĕ', 'ĕ', 'Ė', 'ė', 'Ę', 'ę', 'Ě', 'ě', 'Ĝ', 'ĝ', 'Ğ', 'ğ', 'Ġ', 'ġ', 'Ģ', 'ģ', 'Ĥ', 'ĥ', 'Ħ', 'ħ', 'Ĩ', 'ĩ', 'Ī', 'ī', 'Ĭ', 'ĭ', 'Į', 'į', 'İ', 'ı', 'IJ', 'ij', 'Ĵ', 'ĵ', 'Ķ', 'ķ', 'Ĺ', 'ĺ', 'Ļ', 'ļ', 'Ľ', 'ľ', 'Ŀ', 'ŀ', 'Ł', 'ł', 'Ń', 'ń', 'Ņ', 'ņ', 'Ň', 'ň', 'ʼn', 'Ō', 'ō', 'Ŏ', 'ŏ', 'Ő', 'ő', 'Œ', 'œ', 'Ŕ', 'ŕ', 'Ŗ', 'ŗ', 'Ř', 'ř', 'Ś', 'ś', 'Ŝ', 'ŝ', 'Ş', 'ş', 'Š', 'š', 'Ţ', 'ţ', 'Ť', 'ť', 'Ŧ', 'ŧ', 'Ũ', 'ũ', 'Ū', 'ū', 'Ŭ', 'ŭ', 'Ů', 'ů', 'Ű', 'ű', 'Ų', 'ų', 'Ŵ', 'ŵ', 'Ŷ', 'ŷ', 'Ÿ', 'Ź', 'ź', 'Ż', 'ż', 'Ž', 'ž', 'ſ', 'ƒ', 'Ơ', 'ơ', 'Ư', 'ư', 'Ǎ', 'ǎ', 'Ǐ', 'ǐ', 'Ǒ', 'ǒ', 'Ǔ', 'ǔ', 'Ǖ', 'ǖ', 'Ǘ', 'ǘ', 'Ǚ', 'ǚ', 'Ǜ', 'ǜ', 'Ǻ', 'ǻ', 'Ǽ', 'ǽ', 'Ǿ', 'ǿ'); 
    $replace = array('A', 'A', 'A', 'A', 'A', 'A', 'AE', 'C', 'E', 'E', 'E', 'E', 'I', 'I', 'I', 'I', 'D', 'N', 'O', 'O', 'O', 'O', 'O', 'O', 'U', 'U', 'U', 'U', 'Y', 's', 'a', 'a', 'a', 'a', 'a', 'a', 'ae', 'c', 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i', 'n', 'o', 'o', 'o', 'o', 'o', 'o', 'u', 'u', 'u', 'u', 'y', 'y', 'A', 'a', 'A', 'a', 'A', 'a', 'C', 'c', 'C', 'c', 'C', 'c', 'C', 'c', 'D', 'd', 'D', 'd', 'E', 'e', 'E', 'e', 'E', 'e', 'E', 'e', 'E', 'e', 'G', 'g', 'G', 'g', 'G', 'g', 'G', 'g', 'H', 'h', 'H', 'h', 'I', 'i', 'I', 'i', 'I', 'i', 'I', 'i', 'I', 'i', 'IJ', 'ij', 'J', 'j', 'K', 'k', 'L', 'l', 'L', 'l', 'L', 'l', 'L', 'l', 'l', 'l', 'N', 'n', 'N', 'n', 'N', 'n', 'n', 'O', 'o', 'O', 'o', 'O', 'o', 'OE', 'oe', 'R', 'r', 'R', 'r', 'R', 'r', 'S', 's', 'S', 's', 'S', 's', 'S', 's', 'T', 't', 'T', 't', 'T', 't', 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', 'W', 'w', 'Y', 'y', 'Y', 'Z', 'z', 'Z', 'z', 'Z', 'z', 's', 'f', 'O', 'o', 'U', 'u', 'A', 'a', 'I', 'i', 'O', 'o', 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', 'U', 'u', 'A', 'a', 'AE', 'ae', 'O', 'o'); 
    $url = str_replace($search, $replace, $url);

    //replace common characters
    $search = array('&', '£', '$'); 
    $replace = array('and', 'pounds', 'dollars'); 
    $url= str_replace($search, $replace, $url);

    // remove - for spaces and union characters
    $find = array(' ', '&', '\r\n', '\n', '+', ',', '//');
    $url = str_replace($find, '-', $url);

    //delete and replace rest of special chars
    $find = array('/[^a-z0-9\-<>]/', '/[\-]+/', '/<[^>]*>/');
    $replace = array('', '-', '');
    $uri = preg_replace($find, $replace, $url);

    return $uri;
}
5
John Magnolia

En ce qui concerne les téléchargements de fichiers, il serait plus sûr d'empêcher l'utilisateur de contrôler le nom du fichier. Comme il a déjà été suggéré, stockez le nom de fichier canonisé dans une base de données avec un nom choisi au hasard et unique que vous utiliserez comme nom de fichier réel.

En utilisant OWASP ESAPI, ces noms pourraient être générés ainsi:

$userFilename   = ESAPI::getEncoder()->canonicalize($input_string);
$safeFilename   = ESAPI::getRandomizer()->getRandomFilename();

Vous pouvez ajouter un horodatage à $ safeFilename pour vous assurer que le nom de fichier généré de manière aléatoire est unique sans même rechercher un fichier existant.

En termes d'encodage pour l'URL, et encore en utilisant ESAPI:

$safeForURL     = ESAPI::getEncoder()->encodeForURL($input_string);

Cette méthode effectue la canonisation avant d’encoder la chaîne et gérera tous les encodages de caractères.

5
jah

Je recommande * URLify pour PHP (plus de 480 étoiles sur Github) - "le port PHP de URLify.js du projet Django. Convertit les caractères non ascii pour les utiliser dans les URL".

Utilisation de base:

Pour générer des slugs pour les URL:

<?php

echo URLify::filter (' J\'étudie le français ');
// "jetudie-le-francais"

echo URLify::filter ('Lo siento, no hablo español.');
// "lo-siento-no-hablo-espanol"

?>

Pour générer des slugs pour les noms de fichiers:

<?php

echo URLify::filter ('фото.jpg', 60, "", true);
// "foto.jpg"

?>

* Aucune des autres suggestions ne correspond à mes critères:

  • Devrait être installable via le compositeur
  • Ne doit pas dépendre de iconv car il se comporte différemment selon les systèmes.
  • Doit être extensible pour permettre les remplacements et les remplacements de caractères personnalisés
  • Populaire (par exemple beaucoup d'étoiles sur Github)
  • A des tests

En bonus, URLify supprime également certains mots et supprime tous les caractères non translittérés. 

Voici un cas de test avec des tonnes de caractères étrangers correctement translittérés en utilisant URLify: https://Gist.github.com/motin/a65e6c1cc303e46900d10894bf2da87f

4
Motin

Je ne pense pas qu’avoir une liste de caractères à supprimer soit sans danger. Je préférerais utiliser ce qui suit:

Pour les noms de fichiers: utilisez un identifiant interne ou un hachage du contenu du fichier. Enregistrez le nom du document dans une base de données. De cette façon, vous pouvez conserver le nom de fichier d'origine et toujours trouver le fichier.

Pour les paramètres d'url: Utilisez urlencode() pour coder les caractères spéciaux.

4
ZeissS

Voici l'implémentation de CodeIgniter.

/**
 * Sanitize Filename
 *
 * @param   string  $str        Input file name
 * @param   bool    $relative_path  Whether to preserve paths
 * @return  string
 */
public function sanitize_filename($str, $relative_path = FALSE)
{
    $bad = array(
        '../', '<!--', '-->', '<', '>',
        "'", '"', '&', '$', '#',
        '{', '}', '[', ']', '=',
        ';', '?', '%20', '%22',
        '%3c',      // <
        '%253c',    // <
        '%3e',      // >
        '%0e',      // >
        '%28',      // (
        '%29',      // )
        '%2528',    // (
        '%26',      // &
        '%24',      // $
        '%3f',      // ?
        '%3b',      // ;
        '%3d'       // =
    );

    if ( ! $relative_path)
    {
        $bad[] = './';
        $bad[] = '/';
    }

    $str = remove_invisible_characters($str, FALSE);
    return stripslashes(str_replace($bad, '', $str));
}

Et la dépendance remove_invisible_characters.

function remove_invisible_characters($str, $url_encoded = TRUE)
{
    $non_displayables = array();

    // every control character except newline (dec 10),
    // carriage return (dec 13) and horizontal tab (dec 09)
    if ($url_encoded)
    {
        $non_displayables[] = '/%0[0-8bcef]/';  // url encoded 00-08, 11, 12, 14, 15
        $non_displayables[] = '/%1[0-9a-f]/';   // url encoded 16-31
    }

    $non_displayables[] = '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S';   // 00-08, 11, 12, 14-31, 127

    do
    {
        $str = preg_replace($non_displayables, '', $str, -1, $count);
    }
    while ($count);

    return $str;
}
3
Kevin Mark

Selon l'utilisation que vous comptez en faire, vous voudrez peut-être ajouter une limite de longueur pour vous protéger contre les dépassements de mémoire tampon.

3
Tgr

Plusieurs solutions ont déjà été fournies pour cette question, mais j'ai lu et testé la plupart du code ici et je me suis retrouvé avec cette solution, qui est un mélange de ce que j'ai appris ici:

La fonction

La fonction est intégrée ici dans un ensemble Symfony2 mais elle peut être extraite pour être utilisée comme plain PHP , elle ne possède qu'une dépendance avec la fonction iconv qui doit être activée:

Filesystem.php :

<?php

namespace COil\Bundle\COilCoreBundle\Component\HttpKernel\Util;

use Symfony\Component\HttpKernel\Util\Filesystem as BaseFilesystem;

/**
 * Extends the Symfony filesystem object.
 */
class Filesystem extends BaseFilesystem
{
    /**
     * Make a filename safe to use in any function. (Accents, spaces, special chars...)
     * The iconv function must be activated.
     *
     * @param string  $fileName       The filename to sanitize (with or without extension)
     * @param string  $defaultIfEmpty The default string returned for a non valid filename (only special chars or separators)
     * @param string  $separator      The default separator
     * @param boolean $lowerCase      Tells if the string must converted to lower case
     *
     * @author COil <https://github.com/COil>
     * @see    http://stackoverflow.com/questions/2668854/sanitizing-strings-to-make-them-url-and-filename-safe
     *
     * @return string
     */
    public function sanitizeFilename($fileName, $defaultIfEmpty = 'default', $separator = '_', $lowerCase = true)
    {
    // Gather file informations and store its extension
    $fileInfos = pathinfo($fileName);
    $fileExt   = array_key_exists('extension', $fileInfos) ? '.'. strtolower($fileInfos['extension']) : '';

    // Removes accents
    $fileName = @iconv('UTF-8', 'us-ascii//TRANSLIT', $fileInfos['filename']);

    // Removes all characters that are not separators, letters, numbers, dots or whitespaces
    $fileName = preg_replace("/[^ a-zA-Z". preg_quote($separator). "\d\.\s]/", '', $lowerCase ? strtolower($fileName) : $fileName);

    // Replaces all successive separators into a single one
    $fileName = preg_replace('!['. preg_quote($separator).'\s]+!u', $separator, $fileName);

    // Trim beginning and ending seperators
    $fileName = trim($fileName, $separator);

    // If empty use the default string
    if (empty($fileName)) {
        $fileName = $defaultIfEmpty;
    }

    return $fileName. $fileExt;
    }
}

Les tests unitaires

Ce qui est intéressant, c’est que j’ai créé des tests PHPUnit, tout d’abord pour tester les cas Edge et vous permettre de vérifier si cela répond à vos besoins:

FilesystemTest.php :

<?php

namespace COil\Bundle\COilCoreBundle\Tests\Unit\Helper;

use COil\Bundle\COilCoreBundle\Component\HttpKernel\Util\Filesystem;

/**
 * Test the Filesystem custom class.
 */
class FilesystemTest extends \PHPUnit_Framework_TestCase
{
    /**
     * test sanitizeFilename()
     */
    public function testFilesystem()
    {
    $fs = new Filesystem();

    $this->assertEquals('logo_orange.gif', $fs->sanitizeFilename('--logö  _  __   ___   ora@@ñ--~gé--.gif'), '::sanitizeFilename() handles complex filename with specials chars');
    $this->assertEquals('coilstack', $fs->sanitizeFilename('cOiLsTaCk'), '::sanitizeFilename() converts all characters to lower case');
    $this->assertEquals('cOiLsTaCk', $fs->sanitizeFilename('cOiLsTaCk', 'default', '_', false), '::sanitizeFilename() lower case can be desactivated, passing false as the 4th argument');
    $this->assertEquals('coil_stack', $fs->sanitizeFilename('coil stack'), '::sanitizeFilename() convert a white space to a separator');
    $this->assertEquals('coil-stack', $fs->sanitizeFilename('coil stack', 'default', '-'), '::sanitizeFilename() can use a different separator as the 3rd argument');
    $this->assertEquals('coil_stack', $fs->sanitizeFilename('coil          stack'), '::sanitizeFilename() removes successive white spaces to a single separator');
    $this->assertEquals('coil_stack', $fs->sanitizeFilename('       coil stack'), '::sanitizeFilename() removes spaces at the beginning of the string');
    $this->assertEquals('coil_stack', $fs->sanitizeFilename('coil   stack         '), '::sanitizeFilename() removes spaces at the end of the string');
    $this->assertEquals('coilstack', $fs->sanitizeFilename('coil,,,,,,stack'), '::sanitizeFilename() removes non-ASCII characters');
    $this->assertEquals('coil_stack', $fs->sanitizeFilename('coil_stack  '), '::sanitizeFilename() keeps separators');
    $this->assertEquals('coil_stack', $fs->sanitizeFilename(' coil________stack'), '::sanitizeFilename() converts successive separators into a single one');
    $this->assertEquals('coil_stack.gif', $fs->sanitizeFilename('cOil Stack.GiF'), '::sanitizeFilename() lower case filename and extension');
    $this->assertEquals('copy_of_coil.stack.exe', $fs->sanitizeFilename('Copy of coil.stack.exe'), '::sanitizeFilename() keeps dots before the extension');
    $this->assertEquals('default.doc', $fs->sanitizeFilename('____________.doc'), '::sanitizeFilename() returns a default file name if filename only contains special chars');
    $this->assertEquals('default.docx', $fs->sanitizeFilename('     ___ -  --_     __%%%%__¨¨¨***____      .docx'), '::sanitizeFilename() returns a default file name if filename only contains special chars');
    $this->assertEquals('logo_edition_1314352521.jpg', $fs->sanitizeFilename('logo_edition_1314352521.jpg'), '::sanitizeFilename() returns the filename untouched if it does not need to be modified');
    $userId = Rand(1, 10);
    $this->assertEquals('user_doc_'. $userId. '.doc', $fs->sanitizeFilename('亐亐亐亐亐.doc', 'user_doc_'. $userId), '::sanitizeFilename() returns the default string (the 2nd argument) if it can\'t be sanitized');
    }
}

Les résultats du test: (vérifié sur Ubuntu avec PHP 5.3.2 et MacOsX avec PHP 5.3.17:

All tests pass:

phpunit -c app/ src/COil/Bundle/COilCoreBundle/Tests/Unit/Helper/FilesystemTest.php
PHPUnit 3.6.10 by Sebastian Bergmann.

Configuration read from /var/www/strangebuzz.com/app/phpunit.xml.dist

.

Time: 0 seconds, Memory: 5.75Mb

OK (1 test, 17 assertions)
2
COil

J'ai des titres d'entrées avec toutes sortes de caractères latins étranges, ainsi que des balises HTML que je devais traduire en un format de fichier utile délimité par des tirets. J'ai combiné la réponse de @ SoLoGHoST avec quelques éléments de la réponse de @ Xeoncross et je l'ai personnalisé un peu.

    function sanitize($string,$force_lowercase=true) {
    //Clean up titles for filenames
    $clean = strip_tags($string);
    $clean = strtr($clean, array('Š' => 'S','Ž' => 'Z','š' => 's','ž' => 'z','Ÿ' => 'Y','À' => 'A','Á' => 'A','Â' => 'A','Ã' => 'A','Ä' => 'A','Å' => 'A','Ç' => 'C','È' => 'E','É' => 'E','Ê' => 'E','Ë' => 'E','Ì' => 'I','Í' => 'I','Î' => 'I','Ï' => 'I','Ñ' => 'N','Ò' => 'O','Ó' => 'O','Ô' => 'O','Õ' => 'O','Ö' => 'O','Ø' => 'O','Ù' => 'U','Ú' => 'U','Û' => 'U','Ü' => 'U','Ý' => 'Y','à' => 'a','á' => 'a','â' => 'a','ã' => 'a','ä' => 'a','å' => 'a','ç' => 'c','è' => 'e','é' => 'e','ê' => 'e','ë' => 'e','ì' => 'i','í' => 'i','î' => 'i','ï' => 'i','ñ' => 'n','ò' => 'o','ó' => 'o','ô' => 'o','õ' => 'o','ö' => 'o','ø' => 'o','ù' => 'u','ú' => 'u','û' => 'u','ü' => 'u','ý' => 'y','ÿ' => 'y'));
    $clean = strtr($clean, array('Þ' => 'TH', 'þ' => 'th', 'Ð' => 'DH', 'ð' => 'dh', 'ß' => 'ss', 'Œ' => 'OE', 'œ' => 'oe', 'Æ' => 'AE', 'æ' => 'ae', 'µ' => 'u','—' => '-'));
    $clean = str_replace("--", "-", preg_replace("/[^a-z0-9-]/i", "", preg_replace(array('/\s/', '/[^\w-\.\-]/'), array('-', ''), $clean)));

    return ($force_lowercase) ?
        (function_exists('mb_strtolower')) ?
            mb_strtolower($clean, 'UTF-8') :
            strtolower($clean) :
        $clean;
}

J'avais besoin d'ajouter manuellement le caractère de tiret em (-) au tableau de traduction. Il y en a peut-être d'autres, mais jusqu'à présent, mes noms de fichiers sont bons.

Alors:

Partie 1: Les “Žurburts” de mon père? Ils ne sont pas les meilleurs!

devient:

part-1-my-dads-zurburts-theyre-not-the-best

Je viens d'ajouter ".html" à la chaîne retournée.

2
cbmtrx

C'est un bon moyen de sécuriser un nom de fichier de téléchargement:

$file_name = trim(basename(stripslashes($name)), ".\x00..\x20");
2
games

Ce message semble fonctionner le meilleur parmi tout ce que j'ai lié. http://gsynuh.com/php-string-filename-url-safe/205

1
ktamlyn

Solution n ° 1: Vous avez la possibilité d'installer des extensions PHP sur le serveur (hébergement).

Pour la translittération de "presque toutes les langues de la planète Terre" en caractères ASCII.

  1. Installez d'abord PHP Intl . C'est la commande pour Debian (Ubuntu): Sudo aptitude install php5-intl

  2. Ceci est ma fonction nomfichier (créez test.php et collez-y le code suivant):

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Test</title>
</head>
<body>
<?php

function pr($string) {
  print '<hr>';
  print '"' . fileName($string) . '"';
  print '<br>';
  print '"' . $string . '"';
}

function fileName($string) {
  // remove html tags
  $clean = strip_tags($string);
  // transliterate
  $clean = transliterator_transliterate('Any-Latin;Latin-ASCII;', $clean);
  // remove non-number and non-letter characters
  $clean = str_replace('--', '-', preg_replace('/[^a-z0-9-\_]/i', '', preg_replace(array(
    '/\s/', 
    '/[^\w-\.\-]/'
  ), array(
    '_', 
    ''
  ), $clean)));
  // replace '-' for '_'
  $clean = strtr($clean, array(
    '-' => '_'
  ));
  // remove double '__'
  $positionInString = stripos($clean, '__');
  while ($positionInString !== false) {
    $clean = str_replace('__', '_', $clean);
    $positionInString = stripos($clean, '__');
  }
  // remove '_' from the end and beginning of the string
  $clean = rtrim(ltrim($clean, '_'), '_');
  // lowercase the string
  return strtolower($clean);
}
pr('_replace(\'~&([a-z]{1,2})(ac134/56f4315981743 8765475[]lt7ňl2ú5äňú138yé73ťž7ýľute|');
pr(htmlspecialchars('<script>alert(\'hacked\')</script>'));
pr('Álix----_Ãxel!?!?');
pr('áéíóúÁÉÍÓÚ');
pr('üÿÄËÏÖÜ.ŸåÅ');
pr('nie4č a a§ôňäääaš');
pr('Мао Цзэдун');
pr('毛泽东');
pr('ماو تسي تونغ');
pr('مائو تسه‌تونگ');
pr('מאו דזה-דונג');
pr('მაო ძედუნი');
pr('Mao Trạch Đông');
pr('毛澤東');
pr('เหมา เจ๋อตง');
?>
</body>
</html>

Cette ligne est fondamentale:

  // transliterate
  $clean = transliterator_transliterate('Any-Latin;Latin-ASCII;', $clean);

Réponse basée sur this post .

Solution n ° 2: vous n'avez pas la possibilité d'installer des extensions PHP sur le serveur (hébergement)

enter image description here

Le module transliteration de CMS Drupal est très utile. Il prend en charge presque toutes les langues de la planète Terre. Je suggère de vérifier le plugin repository si vous voulez avoir une chaîne de désinfection de solution vraiment complète.

1
Jasom Dotnet

C'est une bonne fonction:

public function getFriendlyURL($string) {
    setlocale(LC_CTYPE, 'en_US.UTF8');
    $string = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $string);
    $string = preg_replace('~[^\-\pL\pN\s]+~u', '-', $string);
    $string = str_replace(' ', '-', $string);
    $string = trim($string, "-");
    $string = strtolower($string);
    return $string;
} 
1
joan16v

pourquoi ne pas simplement utiliser php's urlencode ? il remplace les caractères "dangereux" par leur représentation hexadécimale pour les URL (par exemple, %20 pour un espace)

1
knittl

C'est le code utilisé par Prestashop pour assainir les URL:

replaceAccentedChars

est utilisé par 

str2url

supprimer les signes diacritiques

function replaceAccentedChars($str)
{
    $patterns = array(
        /* Lowercase */
        '/[\x{0105}\x{00E0}\x{00E1}\x{00E2}\x{00E3}\x{00E4}\x{00E5}]/u',
        '/[\x{00E7}\x{010D}\x{0107}]/u',
        '/[\x{010F}]/u',
        '/[\x{00E8}\x{00E9}\x{00EA}\x{00EB}\x{011B}\x{0119}]/u',
        '/[\x{00EC}\x{00ED}\x{00EE}\x{00EF}]/u',
        '/[\x{0142}\x{013E}\x{013A}]/u',
        '/[\x{00F1}\x{0148}]/u',
        '/[\x{00F2}\x{00F3}\x{00F4}\x{00F5}\x{00F6}\x{00F8}]/u',
        '/[\x{0159}\x{0155}]/u',
        '/[\x{015B}\x{0161}]/u',
        '/[\x{00DF}]/u',
        '/[\x{0165}]/u',
        '/[\x{00F9}\x{00FA}\x{00FB}\x{00FC}\x{016F}]/u',
        '/[\x{00FD}\x{00FF}]/u',
        '/[\x{017C}\x{017A}\x{017E}]/u',
        '/[\x{00E6}]/u',
        '/[\x{0153}]/u',

        /* Uppercase */
        '/[\x{0104}\x{00C0}\x{00C1}\x{00C2}\x{00C3}\x{00C4}\x{00C5}]/u',
        '/[\x{00C7}\x{010C}\x{0106}]/u',
        '/[\x{010E}]/u',
        '/[\x{00C8}\x{00C9}\x{00CA}\x{00CB}\x{011A}\x{0118}]/u',
        '/[\x{0141}\x{013D}\x{0139}]/u',
        '/[\x{00D1}\x{0147}]/u',
        '/[\x{00D3}]/u',
        '/[\x{0158}\x{0154}]/u',
        '/[\x{015A}\x{0160}]/u',
        '/[\x{0164}]/u',
        '/[\x{00D9}\x{00DA}\x{00DB}\x{00DC}\x{016E}]/u',
        '/[\x{017B}\x{0179}\x{017D}]/u',
        '/[\x{00C6}]/u',
        '/[\x{0152}]/u');

    $replacements = array(
            'a', 'c', 'd', 'e', 'i', 'l', 'n', 'o', 'r', 's', 'ss', 't', 'u', 'y', 'z', 'ae', 'oe',
            'A', 'C', 'D', 'E', 'L', 'N', 'O', 'R', 'S', 'T', 'U', 'Z', 'AE', 'OE'
        );

    return preg_replace($patterns, $replacements, $str);
}

function str2url($str)
{
    if (function_exists('mb_strtolower'))
        $str = mb_strtolower($str, 'utf-8');

    $str = trim($str);
    if (!function_exists('mb_strtolower'))
        $str = replaceAccentedChars($str);

    // Remove all non-whitelist chars.
    $str = preg_replace('/[^a-zA-Z0-9\s\'\:\/\[\]-\pL]/u', '', $str);
    $str = preg_replace('/[\s\'\:\/\[\]-]+/', ' ', $str);
    $str = str_replace(array(' ', '/'), '-', $str);

    // If it was not possible to lowercase the string with mb_strtolower, we do it after the transformations.
    // This way we lose fewer special chars.
    if (!function_exists('mb_strtolower'))
        $str = strtolower($str);

    return $str;
}
0
Armel Larcier

Il existe deux bonnes réponses pour gérer vos données, utilisez-la https://stackoverflow.com/a/3987966/971619 ou it https://stackoverflow.com/a/7610586/971619

0
newway