php - Sanitizando cadenas para hacer que la URL y el nombre de archivo sean seguros?
filenames sanitization (23)
Estoy tratando de encontrar una función que haga un buen trabajo de desinfección de ciertas cadenas para que sean seguras de usar en la URL (como una publicación posterior) y también seguras de usar como nombres de archivo. Por ejemplo, cuando alguien carga un archivo, quiero asegurarme de eliminar todos los caracteres peligrosos del nombre.
Hasta ahora, se me ocurrió la siguiente función, que espero resuelva este problema y también permita datos extranjeros UTF-8.
/**
* 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'');
}
¿Alguien tiene datos de muestra complicados que pueda ejecutar contra esto, o conozca una forma mejor de proteger nuestras aplicaciones de nombres incorrectos?
$ is-filename permite algunos caracteres adicionales como archivos temp vim
actualización: eliminó el personaje estrella ya que no podía pensar en un uso válido
Solution #1: You have ability to install PHP extensions on server (hosting)
For transliteration of "almost every single language on the planet Earth" to ASCII characters.
Install PHP Intl extension first. This is command for Debian (Ubuntu):
sudo aptitude install php5-intl
This is my fileName function (create test.php and paste there following code):
<!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>
This line is core:
// transliterate
$clean = transliterator_transliterate(''Any-Latin;Latin-ASCII;'', $clean);
Answer based on this post .
Solution #2: You don''t have ability to install PHP extensions on server (hosting)
Pretty good job is done in transliteration module for CMS Drupal. It supports almost every single language on the planet Earth. I suggest to check plugin repository if you want to have really complete solution sanitizing strings.
Algunas observaciones sobre su solución:
- ''u'' al final de su patrón significa que el patrón , y no el texto que coincide, se interpretará como UTF-8 (¿supongo que asumió lo último?).
- / w coincide con el carácter de subrayado. Específicamente lo incluye para los archivos que llevan a la suposición de que no los quiere en las URL, pero en el código que tiene las URL se le permitirá incluir un guión bajo.
- La inclusión de "UTF-8 foráneo" parece ser dependiente de la configuración regional. No está claro si esta es la configuración regional del servidor o cliente. De los documentos de PHP:
Un carácter de "palabra" es cualquier letra o dígito o el carácter de guión bajo, es decir, cualquier carácter que pueda ser parte de una "palabra" de Perl. La definición de letras y dígitos está controlada por las tablas de caracteres de PCRE, y puede variar si se lleva a cabo una coincidencia específica de la configuración regional. Por ejemplo, en la configuración regional "fr" (francés), algunos códigos de caracteres superiores a 128 se utilizan para letras acentuadas, y éstas se corresponden con / w.
Creando la babosa
Probablemente no deberías incluir caracteres acentuados, etc. en tu publicación posterior ya que, técnicamente, deberían estar codificados porcentualmente (por reglas de codificación de URL) para que tengas URLs que parecen feas.
Entonces, si yo fuera tú, después de minicar, convertiría cualquier carácter ''especial'' a su equivalente (por ejemplo, é -> e) y reemplazaría los caracteres que no son [az] por ''-'', lo que limitaría a las ejecuciones de un solo ''-'' como lo has hecho Hay una implementación de la conversión de caracteres especiales aquí: https://web.archive.org/web/20130208144021/http://neo22s.com/slug
Sanitización en general
OWASP tiene una implementación PHP de su Enterprise Security API que, entre otras cosas, incluye métodos para codificar y decodificar de forma segura las entradas y salidas en su aplicación.
La interfaz del codificador proporciona:
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-ESAPI https://www.owasp.org/index.php/Category:OWASP_Enterprise_Security_API
Dependiendo de cómo lo usará, es posible que desee agregar un límite de longitud para proteger contra desbordamientos de búfer.
En términos de carga de archivos, sería más seguro evitar que el usuario controle el nombre del archivo. Como ya se ha insinuado, almacene el nombre de archivo canonicalizado en una base de datos junto con un nombre elegido al azar y exclusivo que usará como el nombre de archivo real.
Usando OWASP ESAPI, estos nombres podrían generarse así:
$userFilename = ESAPI::getEncoder()->canonicalize($input_string);
$safeFilename = ESAPI::getRandomizer()->getRandomFilename();
Puede agregar una marca de tiempo al $ safeFilename para ayudar a garantizar que el nombre de archivo generado aleatoriamente sea único sin siquiera verificar si hay un archivo existente.
En términos de codificación para URL, y nuevamente usando ESAPI:
$safeForURL = ESAPI::getEncoder()->encodeForURL($input_string);
Este método realiza canonicalización antes de codificar la cadena y maneja todas las codificaciones de caracteres.
Encontré esta función más grande en el código 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;
}
y este en el código wordpress
/**
* 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);
}
Actualización de septiembre de 2012
Alix Axel ha hecho un trabajo increíble en esta área. Su marco de trabajo de phynics incluye varios filtros de texto y transformaciones geniales.
Esta es una buena manera de asegurar un nombre de archivo de carga:
$file_name = trim(basename(stripslashes($name)), "./x00../x20");
Esto debería hacer que tus nombres de archivo sean seguros ...
$string = preg_replace(array(''//s/'', ''//.[/.]+/'', ''/[^/w_/./-]/''), array(''_'', ''.'', ''''), $string);
y una solución más profunda a esto es:
// 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);
Esto supone que quieres un punto en el nombre del archivo. si quieres que se transfiera a minúsculas, solo usa
$clean_name = strtolower($clean_name);
para la última línea
Me he adaptado de otra fuente y he añadido un par extra, tal vez un poco exagerado
/**
* 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;
}
No creo que tener una lista de caracteres para eliminar sea seguro. Prefiero usar lo siguiente:
Para nombres de archivo: use una identificación interna o un hash del contenido del archivo. Guarde el nombre del documento en una base de datos. De esta manera puede conservar el nombre de archivo original y aún así encontrar el archivo.
Para los parámetros url: use urlencode()
para codificar cualquier carácter especial.
Prueba esto:
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
Basado en la respuesta seleccionada en este hilo: Nombre de usuario amigable con URL en PHP?
Recomiendo * URLify para PHP (más de 480 estrellas en Github) - "el puerto PHP de URLify.js del proyecto Django. Transcribe caracteres que no son ASCII para su uso en URLs".
Uso básico:
Para generar slugs para 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"
?>
Para generar babosas para nombres de archivo:
<?php
echo URLify::filter (''фото.jpg'', 60, "", true);
// "foto.jpg"
?>
* Ninguna de las otras sugerencias coincide con mis criterios:
- Debe ser instalable vía compositor
- No debería depender de iconv ya que se comporta de manera diferente en diferentes sistemas
- Debe ser extensible para permitir reemplazos y reemplazos de caracteres personalizados
- Popular (por ejemplo, muchas estrellas en Github)
- Tiene pruebas
Como beneficio adicional, URLify también elimina ciertas palabras y quita todos los caracteres que no se transcriben.
Aquí hay un caso de prueba con toneladas de caracteres extranjeros que se transcriben correctamente usando URLify: https://gist.github.com/motin/a65e6c1cc303e46900d10894bf2da87f
Siempre pensé que Kohana hizo un trabajo bastante bueno .
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);
}
El útil UTF8::transliterate_to_ascii()
convertirá cosas como ñ => n.
Por supuesto, puede reemplazar las otras cosas UTF8::*
con funciones mb_ *.
Ya hay varias soluciones para esta pregunta, pero he leído y probado la mayor parte del código aquí y terminé con esta solución que es una combinación de lo que aprendí aquí:
La función
La función se incluye aquí en un paquete Symfony2 pero se puede extraer para utilizarlo como PHP simple , solo tiene una dependencia con la función iconv
que debe habilitarse:
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://.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;
}
}
La unidad prueba
Lo que es interesante es que he creado pruebas de PHPUnit, primero para probar casos extremos y para que pueda verificar si se ajustan a sus necesidades: (Si encuentra un error, puede agregar un caso de prueba)
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'');
}
}
The test results: (checked on Ubuntu with PHP 5.3.2 and MacOsX with 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)
y esta es la versión de 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));
}
Aquí está la implementación 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));
}
Y la dependencia 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;
}
Esta no es exactamente una respuesta, ya que no proporciona ninguna solución (¡aún!), Pero es demasiado grande para caber en un comentario ...
Hice algunas pruebas (con respecto a los nombres de los archivos) en Windows 7 y Ubuntu 12.04 y descubrí que:
1. PHP no puede manejar nombres de archivo que no sean ASCII
Aunque tanto Windows como Ubuntu pueden manejar nombres de archivos Unicode (incluso RTL como parece), PHP 5.3 requiere ataques para manejar incluso el viejo ISO-8859-1, por lo que es mejor mantenerlo como ASCII solo por seguridad.
2. La duración del nombre de archivo importa (especialmente en Windows)
En Ubuntu, la longitud máxima que puede tener un nombre de archivo (incluida la extensión) es 255 (sin incluir la ruta):
/var/www/uploads/123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345/
Sin embargo, en Windows 7 (NTFS) la longitud máxima que puede tener un nombre de archivo depende de su ruta absoluta:
(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 dice que:
NTFS permite que cada componente de ruta (directorio o nombre de archivo) tenga 255 caracteres de longitud.
Hasta donde yo sé (y prueba), esto está mal.
En total (barras de conteo), todos estos ejemplos tienen 259 caracteres, si tira el C:/
que da 256 caracteres (no 255 ?!). Los directorios se crearon utilizando el Explorador y notará que no puede usar todo el espacio disponible para el nombre del directorio. La razón para esto es permitir la creación de archivos utilizando la convención de nomenclatura de archivos 8.3 . Lo mismo ocurre con otras particiones.
Los archivos no necesitan reservar los requisitos de longitud de 8.3 por supuesto:
(255 chars) E:/12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901.txt
No puede crear más subdirectorios si la ruta absoluta del directorio principal tiene más de 242 caracteres, porque 256 = 242 + 1 + / + 8 + . + 3
256 = 242 + 1 + / + 8 + . + 3
. Con Windows Explorer, no puede crear otro directorio si el directorio principal tiene más de 233 caracteres (según la configuración regional del sistema), porque 256 = 233 + 10 + / + 8 + . + 3
256 = 233 + 10 + / + 8 + . + 3
; el 10
aquí es la longitud de la cadena New folder
.
El sistema de archivos de Windows plantea un problema desagradable si desea garantizar la interoperabilidad entre los sistemas de archivos.
3. Cuidado con los caracteres reservados y las palabras clave
Además de eliminar caracteres no ASCII, no imprimibles y de control , también debe volver a (colocar / mover):
"*/:<>?/|
Solo eliminar estos caracteres podría no ser la mejor idea, ya que el nombre del archivo podría perder parte de su significado. Creo que, al menos, múltiples ocurrencias de estos caracteres deberían ser reemplazadas por un solo guión bajo ( _
), o tal vez algo más representativo (esto es solo una idea):
-
"*?
->_
-
//|
->-
-
:
->[ ]-[ ]
-
<
->(
-
>
->)
También hay palabras clave especiales que deben evitarse (como NUL
), aunque no estoy seguro de cómo superar eso. Tal vez una lista negra con un nombre aleatorio alternativo sea un buen enfoque para resolverlo.
4. Sensibilidad a los casos
Esto no hace falta decirlo, pero si lo desea, asegúrese de que el archivo sea único en los diferentes sistemas operativos, debe transformar los nombres de los archivos en un caso normalizado, de esa forma my_file.txt
y My_File.txt
en Linux no se convertirán en el mismo archivo my_file.txt
en Windows.
5. Asegúrate de que sea único
Si el nombre del archivo ya existe, se debe agregar un identificador único a su nombre de archivo base.
Los identificadores únicos comunes incluyen la marca de tiempo UNIX, un resumen del contenido del archivo o una cadena aleatoria.
6. Archivos ocultos
El hecho de que se pueda nombrar no significa que deba ...
Los puntos suelen estar en la lista blanca en los nombres de los archivos, pero en Linux un archivo oculto se representa con un punto inicial.
7. Otras consideraciones
Si tiene que quitar algunos caracteres del nombre del archivo, la extensión suele ser más importante que el nombre base del archivo. Permitiendo una cantidad máxima considerable de caracteres para la extensión de archivo (8-16) uno debe quitar los caracteres del nombre base. También es importante tener en cuenta que, en el caso improbable de tener más de una extensión larga, como _.graphmlz.tag.gz
- _.graphmlz.tag
únicamente _
debe considerarse como el nombre base del archivo en este caso.
8. Recursos
Calibre maneja el nombre del archivo destruyéndose decentemente:
Página de Wikipedia sobre el nombre del archivo y el capítulo vinculado de Using Samba .
Si, por ejemplo, intentas crear un archivo que infringe cualquiera de las reglas 1/2/3, obtendrás un error muy útil:
Warning: touch(): Unable to create file ... because No error in ... on line ...
I have entry titles with all kinds of weird latin characters as well as some HTML tags that I needed to translate into a useful dash-delimited filename format. I combined @SoLoGHoST''s answer with a couple of items from @Xeoncross''s answer and customized a bit.
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;
}
I needed to manually add the em dash character (—) to the translation array. There may be others but so far my file names are looking good.
Asi que:
Part 1: My dad''s “Žurburts”?—they''re (not) the best!
becomes:
part-1-my-dads-zurburts-theyre-not-the-best
I just add ".html" to the returned string.
There is 2 good answers to slugfy your data, use it https://.com/a/3987966/971619 or it https://.com/a/7610586/971619
This is a good function:
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;
}
This is the code used by Prestashop to sanitize urls :
replaceAccentedChars
is used by
str2url
to remove diacritics
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;
}
This post seems to work the best among all that I have tied. http://gsynuh.com/php-string-filename-url-safe/205
why not simply use php''s urlencode
? it replaces "dangerous" characters with their hex representation for urls (ie %20
for a space)
// CLEAN ILLEGAL CHARACTERS
function clean_filename($source_file)
{
$search[] = " ";
$search[] = "&";
$search[] = "$";
$search[] = ",";
$search[] = "!";
$search[] = "@";
$search[] = "#";
$search[] = "^";
$search[] = "(";
$search[] = ")";
$search[] = "+";
$search[] = "=";
$search[] = "[";
$search[] = "]";
$replace[] = "_";
$replace[] = "and";
$replace[] = "S";
$replace[] = "_";
$replace[] = "";
$replace[] = "";
$replace[] = "";
$replace[] = "";
$replace[] = "";
$replace[] = "";
$replace[] = "";
$replace[] = "";
$replace[] = "";
$replace[] = "";
return str_replace($search,$replace,$source_file);
}