消毒string,使他们的URL和文件名安全?

我试图想出一个function,对某些string进行消毒,以便它们可以安全地在URL中使用(比如post-slug),并且可以安全地用作文件名。 例如,当某人上传文件时,我想确保从名称中删除所有危险字符。

到目前为止,我已经想出了以下function,我希望能解决这个问题,并允许外国的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'); } 

有没有人有任何棘手的示例数据我可以对此运行 – 或知道一个更好的方式来保护我们的应用程序不好的名字?

$ is-filename允许一些额外的字符,如temp vim文件

更新:删除了星号,因为我想不出一个有效的用法

对你的解决scheme的一些观察

  1. 在你的模式结尾“u”意味着模式 ,而不是它匹配的文本将被解释为UTF-8(我认为你假设后者?)。
  2. \ w匹配下划线字符。 您专门将其包含在文件中,导致您不希望在URL中使用这些文件,但是在具有URL的代码中将允许包含下划线。
  3. 包含“外国UTF-8”似乎是由当地的依赖。 目前还不清楚这是否是服务器或客户端的语言环境。 从PHP文档:

“单词”字符是任何字母或数字或下划线字符,即可以是Perl“单词”的一部分的任何字符。 字母和数字的定义由PCRE的字符表来控制,并且如果发生特定于语言环境的匹配,则可能会有所不同。 例如,在“fr”(法语)语言环境中,一些大于128的字符代码用于重音字母,并且这些符号由\ w匹配。

创buildslu </s>

你可能不应该在你的post中包含重音等字符,因为在技术上,他们应该百分比编码(每个URL编码规则),所以你会看到丑陋的URL。

所以,如果我是你,在压缩之后,我会把任何“特殊”字符转换成它们的等价forms(如é – > e),并用' – 'replace非[az]字符,限制运行一个' – '正如你所做的那样。 这里有一个转换特殊字符的实现: https : //web.archive.org/web/20130208144021/http : //neo22s.com/slug

一般消毒

OWASP拥有其企业安全API的PHP实现,其中包括在应用程序中安全编码和解码input和输出的方法。

编码器界面提供:

 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

我在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("~", "`", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "=", "+", "[", "{", "]", "}", "\\", "|", ";", ":", "\"", "'", "&#8216;", "&#8217;", "&#8220;", "&#8221;", "&#8211;", "&#8212;", "—", "–", ",", "<", ".", ">", "/", "?"); $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; } 

而这个在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); } 

2012年9月更新

阿利克斯阿克塞尔在这方面做了一些令人难以置信的工作。 他的function框架包括几个伟大的文本filter和转换。

  • Unaccent
  • 金属块
  • 过滤

这应该使你的文件名安全…

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

而对此的更深层的解决scheme是:

 // 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); 

这假定你想在文件名中有一个点。 如果你想把它转换成小写,只需使用

 $clean_name = strtolower($clean_name); 

为最后一行。

尝试这个:

 function normal_chars($string) { $string = htmlentities($string, ENT_QUOTES, 'UTF-8'); $string = preg_replace('~&([az]{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 

根据在这个线程中select的答案: 在PHP中的URL友好的用户名?

这不完全是一个答案,因为它不提供任何解决scheme(还!),但它太大,不适合评论…


我在Windows 7和Ubuntu 12.04上做了一些testing(关于文件名),而我发现的是:

1. PHP不能处理非ASCII文件名

尽pipeWindows和Ubuntu都可以处理Unicode文件名(甚至是RTL文件),但PHP 5.3需要黑客来处理旧的ISO-8859-1,所以最好仅仅为了安全而保留ASCII码。

2.文件名的长度(特别是在Windows上)

在Ubuntu上,文件名可以有的最大长度(包括扩展名)是255(不包括path):

 /var/www/uploads/123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345/ 

但是,在Windows 7(NTFS)上,文件名的最大长度取决于它的绝对path:

 (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 

维基百科说:

NTFS允许每个path组件(目录或文件名)的长度为255个字符。

据我所知(和testing),这是错误的。

总的来说,所有这些例子都有259个字符,如果你去除了C:\ 256个字符(不是255?!)。 在使用资源pipe理器创build的目录中,您会注意到它限制自己无法使用目录名称的所有可用空间。 原因是允许使用8.3文件命名约定创build文件。 其他分区也会发生同样的情况。

文件不需要保留当然的8.3长度要求:

 (255 chars) E:\12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901.txt 

如果父目录的绝对path超过242个字符,则不能再创build子目录,因为256 = 242 + 1 + \ + 8 + . + 3 256 = 242 + 1 + \ + 8 + . + 3 。 使用Windows资源pipe理器,如果父目录的字符数超过233(取决于系统区域设置),则不能创build另一个目录,因为256 = 233 + 10 + \ + 8 + . + 3 256 = 233 + 10 + \ + 8 + . + 3 ; 这里的10是stringNew folder的长度。

如果你想确保文件系统之间的互操作性,Windows文件系统会造成一个令人讨厌的问题。

3.谨防保留的字符和关键字

除了删除非ASCII,不可打印和控制字符 ,您还需要重新(放置/移动):

 "*/:<>?\| 

只是删除这些字符可能不是最好的主意,因为文件名可能会失去一些意义。 我认为,至less这些angular色的多重出现应该用一个下划线( _ )来代替,或者更具代表性的东西(这只是一个想法):

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

还有一些特殊的关键字应该避免 (如NUL ),尽pipe我不知道如何克服这个问题。 也许一个随机的名字回退黑名单是一个很好的方法来解决它。

4.区分大小写

这应该不用说了,但是如果你想这样确保文件在不同操作系统中的唯一性,你应该把文件名转换成规范化的格式,这样Linux上的my_file.txtMy_File.txt不会变成相同的my_file.txt文件在Windows上。

5.确保它是唯一的

如果文件名已经存在, 则应在其基本文件名后附加一个唯一标识符 。

常用的唯一标识符包括UNIX时间戳,文件内容摘要或随机string。

6.隐藏文件

只是因为它可以被命名并不意味着它应该…

点通常是在文件名中列出的,但在Linux中,隐藏文件由前导点表示。

7.其他考虑

如果必须去除文件名的一些字符,扩展名通常比文件的基本名称更重要。 允许文件扩展名 (8-16)的相当多的最大字符数,应从基本名称中去除字符。 同样重要的是要注意,在不太可能发生多个扩展名的情况下,比如_.graphmlz.tag.gz_.graphmlz.tag在这种情况下只应该被视为文件基名。

8.资源

口径处理文件名称相当体面:

  • /src/calibre/utils/filenames.py
  • /src/calibre/library/save_to_disk.py

维基百科页面上的文件名称mangling和使用Samba链接的章节 。


例如,如果您尝试创build违反1/2/3规则的文件,您将得到一个非常有用的错误:

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

我一直认为Kohana做得很好 。

 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, az, 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); } 

方便的UTF8::transliterate_to_ascii()会变成像ñ=> n这样的东西。

当然,你可以用mb_ *函数replace其他UTF8::*东西。

在file upload方面,最安全的方法是防止用户控制文件名。 正如已经暗示的那样,将规范化的文件名与随机select的唯一名称一起存储在数据库中,您将使用该名称作为实际文件名。

使用OWASP ESAPI,可以这样生成这些名称:

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

你可以附加一个时间戳$ safeFilename,以帮助确保随机生成的文件名是唯一的,甚至没有检查现有的文件。

在URL编码方面,再次使用ESAPI:

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

此方法在对string进行编码之前执行规范化,并将处理所有字符编码。

我已经从另一个来源改编,并添加了一些额外的,也许有点矫枉过正

 /** * 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; } 

这是从JFile::makeSafe($file) Joomla 3.3.2版本

 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)); } 

我不认为有一个清除字符清单是安全的。 我宁愿使用以下内容:

对于文件名:使用内部ID或文件内容的散列。 将文档名称保存在数据库中。 这样你可以保留原来的文件名,并仍然可以find文件。

对于url参数:使用urlencode()来编码任何特殊字符。

根据您将如何使用它,您可能需要添加一个长度限制来防止缓冲区溢出。

这里是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)); } 

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; } 

我推荐PHP的URLify(Github上有480多个星号) – “Django项目中的URLify.js的PHP端口。在URL中使用非ASCII字符。

基本用法:

为url生成slug:

 <?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" ?> 

为文件名生成slu </s>声:

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

*没有其他的build议符合我的标准:

  • 应该可以通过composer php安装
  • 不应该依赖于iconv,因为它在不同的系统上performance不同
  • 应该可扩展以允许覆盖和自定义字符replace
  • stream行(例如Github上的许多明星)
  • 有testing

作为奖励,URLify也会删除某些字词,并删除所有没有音译的字符。

下面是一个testing用例,其中有大量的外国字符正确地使用URLify进行音译: https ://gist.github.com/motin/a65e6c1cc303e46900d10894bf2da87f

这是一个确保上传文件名的好方法:

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

已经有几个解决scheme提供了这个问题,但我已经阅读和testing了大部分的代码在这里,我结束了这个解决scheme,这是我在这里学到的:

function

这个函数捆绑在一个Symfony2包中,但是它可以被解压为普通的PHP ,它只与iconv函数有关,必须被启用:

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; } } 

unit testing

有趣的是,我已经创build了PHPUnittesting,首先testing边界案例,以便检查它是否符合您的需求:(如果发现错误,请随时添加testing用例)

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) 

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.

所以:

Part 1: My dad's “Žurburts”?—they're (not) the best!

变为:

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

I just add ".html" to the returned string.

why not simply use php's urlencode ? it replaces "dangerous" characters with their hex representation for urls (ie %20 for a space)

This post seems to work the best among all that I have tied. http://gsynuh.com/php-string-filename-url-safe/205

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; } 

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.

  1. Install PHP Intl extension first. This is command for Debian (Ubuntu): sudo aptitude install php5-intl

  2. 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(\'~&([az]{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č aa§ôňäääaš'); pr('Мао Цзэдун'); pr('毛泽东'); pr('ماو تسي تونغ'); pr('مائو تسه‌تونگ'); pr('מאו דזה-דונג'); pr('მაო ძედუნი'); pr('Mao Trạch Đông'); pr('毛澤東'); pr('เหมา เจ๋อตง'); ?> </body> </html> 

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; } 

There is 2 good answers to slugfy your data, use it https://stackoverflow.com/a/3987966/971619 or it https://stackoverflow.com/a/7610586/971619

 // 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); }