消毒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的一些观察
- 在你的模式结尾“u”意味着模式 ,而不是它匹配的文本将被解释为UTF-8(我认为你假设后者?)。
- \ w匹配下划线字符。 您专门将其包含在文件中,导致您不希望在URL中使用这些文件,但是在具有URL的代码中将允许包含下划线。
- 包含“外国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("~", "`", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "=", "+", "[", "{", "]", "}", "\\", "|", ";", ":", "\"", "'", "‘", "’", "“", "”", "–", "—", "—", "–", ",", "<", ".", ">", "/", "?"); $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.txt
和My_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.
-
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(\'~&([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); }