消毒字符串,使其 URL 和文件名安全?

我正在尝试提出一个函数,做一个很好的工作,消毒某些字符串,使他们是安全的使用在网址(像一个职位蛞蝓) ,也安全地使用作为文件名。例如,当有人上传文件时,我希望确保从名称中删除所有危险字符。

到目前为止,我已经提出了以下函数,我希望解决这个问题,并允许外国 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 文件

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

195815 次浏览
// 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);


}

我在 奇普代码中找到了这个更大的函数:

/**
* 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月更新

Alix Axel 在这方面做了一些不可思议的工作,他的发音框架包括几个很棒的文本过滤器和转换。

我不认为有一个要删除的字符列表是安全的:

对于文件名: 使用文件内容的内部 ID 或散列。在数据库中保存文档名。这样,您可以保留原始文件名,同时仍然可以找到该文件。

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

试试这个:

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

根据这个线程中选择的答案: PHP 中友好用户名 URL Friendly Username? ?

对你的解决方案的一些观察:

  1. 模式末尾的“ u”意味着 模式,而不是它所匹配的文本将被解释为 UTF-8(我假设您假设是后者?).
  2. W 匹配下划线字符。您特别为文件包含它,这会导致假设您不希望它们出现在 URL 中,但是在代码中,您拥有的 URL 将被允许包含下划线。
  3. 包含“ foreign UTF-8”似乎是依赖于地区的。不清楚这是服务器还是客户机的区域设置。来自 PHP 文档:

“ word”字符是任何字母、数字或下划线字符,也就是说,可以是 Perl“ word”的一部分的任何字符。字母和数字的定义由 PCRE 的字符表控制,如果发生特定于区域设置的匹配,字母和数字的定义可能会有所不同。例如,在“ fr”(法语)区域设置中,一些大于128的字符代码用于重音字母,这些字符代码由 w 匹配。

制造子弹

您可能不应该包括重音等字符在您的文章蛞蝓,因为从技术上讲,他们应该百分比编码(每 URL 编码规则) ,所以您将有难看的网址。

因此,如果我是你,在小写之后,我会将任何“特殊”字符转换为它们的等效字符(例如 é-> e) ,并将非[ a-z ]字符替换为“-”,就像你所做的那样,仅限于运行一个“-”。这里有一个转换特殊字符的实现: https://web.archive.org/web/20130208144021/http://neo22s.com/slug

一般的消毒

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

编码器界面提供:

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

我一直认为 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, 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);
}

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

当然,您可以使用 mb _ * 函数替换其他 UTF8::*内容。

根据您使用它的方式,您可能需要添加一个长度限制,以防止缓冲区溢出。

就文件上传而言,防止用户控制文件名是最安全的。正如已经提到的,将规范化的文件名与随机选择的唯一名称一起存储在数据库中,您将使用这个名称作为实际的文件名。

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

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

您可以向 $safeFilename 追加一个时间戳,以帮助确保随机生成的文件名是唯一的,甚至不需要检查现有文件。

关于 URL 的编码,以及同样使用 ESAPI:

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

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

为什么不简单地使用 php 的 urlencode?它将“危险”字符替换为 urls 的十六进制表示形式(即空格的 %20)

这应该可以让你的文件名安全..。

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

更深层次的解决办法是:

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

最后一句台词。

我已经从另一个来源,并增加了一对夫妇额外的,也许有点过度杀伤力

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

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

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

这并不是一个确切的答案,因为它还没有提供任何解决方案但它太大了,不适合评论..。


我在 Windows 7和 Ubuntu 12.04上做了一些测试(关于文件名) ,我发现:

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

尽管 Windows 和 Ubuntu 都可以处理 Unicode 文件名(甚至看起来是 RTL 文件名) ,但 PHP 5.3甚至需要处理普通的老 ISO-8859-1文件,所以为了安全起见,最好保留 ASCII 文件。

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

在 Ubuntu 上,文件名的最大长度(包括扩展名)是255(不包括路径) :

/var/www/uploads/123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345/

然而,在 Windows7(NTFS)上,文件名的最大长度取决于它的绝对路径:

(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 允许 每个人路径组件(目录或文件名)为255 字符长。

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

如果您去掉给出256个字符(而不是255个? !)的 C:\,那么所有这些示例总共有259个字符(计算斜杠).使用资源管理器创建的目录,您会注意到它限制自己使用目录名的所有可用空间。这样做的原因是允许使用 8.3档案变数命名原则创建文件。其他分区也会发生同样的情况。

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

(255 chars) E:\12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901.txt

如果父目录的绝对路径超过242个字符,则不能再创建任何子目录,因为 256 = 242 + 1 + \ + 8 + . + 3。使用文件资源管理器,如果父目录有超过233个字符(取决于系统的区域设置) ,就不能创建另一个目录,因为 256 = 233 + 10 + \ + 8 + . + 3; 这里的 10是字符串 New folder的长度。

如果要确保文件系统之间的互操作性,Windows 文件系统会带来一个棘手的问题。

3. 注意保留字符和关键字

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

"*/:<>?\|

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

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

也有 应该避免的特殊关键字(像 NUL) ,虽然我不知道如何克服这一点。也许一个黑名单与随机名称后备将是一个很好的方法来解决这个问题。

4. 案例敏感性

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

5、确保独一无二

如果文件名已经存在,则对其基本文件名使用 唯一标识符

常见的唯一标识符包括 UNIX 时间戳、文件内容摘要或随机字符串。

6. 隐藏文件

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

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

7. 其他考虑

如果必须去掉文件名中的某些字符,扩展名通常比文件的基名更重要。如果允许使用 文件扩展名的最大字符数(8-16) ,则应从基本名称中去除字符。同样重要的是要注意,在不太可能有多个长扩展名的情况下——比如 _.graphmlz.tag.gz-_.graphmlz.tag,在这种情况下,只有 _应该被认为是文件基名。

8. 资源

Calibre 处理文件名的能力相当不错:

关于文件名的维基百科页面错位 并链接 使用 Samba 一章


例如,如果你试图创建一个违反1/2/3规则的文件,你会得到一个非常有用的错误:

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

下面是 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;
}

针对这个问题已经提供了几种解决方案,但是我已经阅读并测试了这里的大部分代码,最终得到了这个解决方案,它是我在这里学到的东西的混合体:

功能

这个函数绑定在一个 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;
}
}

单元测试

有趣的是,我已经创建了 PHPUnit 测试,首先是测试边缘用例,因此您可以检查它是否符合您的需要: (如果您发现一个 bug,请随意添加一个测试用例)

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

测试结果: (使用 PHP 5.3.2检查 Ubuntu,使用 PHP 5.3.17检查 MacOsX:

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)

这篇文章似乎是我所有文章中效果最好的

这是 Prestashop 用来清理网址的代码:

replaceAccentedChars

str2url

去除音调符号

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

有2个很好的答案懒惰你的数据,使用它的 https://stackoverflow.com/a/3987966/971619或它的 https://stackoverflow.com/a/7610586/971619

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

这是一个很好的功能:

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

我有各种奇怪的拉丁字符以及一些 HTML 标记的条目标题,我需要将它们转换成一种有用的以破折号分隔的文件名格式。我将@SoLoGHoST 的回答与@Xeoncross 的回答中的几个条目结合起来,并进行了一些定制。

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

我需要手动将 em 破折号字符(ー)添加到翻译数组中。可能还有其他人,但到目前为止,我的文件名看起来不错。

所以:

第一部分: 我爸爸的“ urburts”? ー它们(不)是最好的!

变成:

第一部分,我的爸爸,祖伯特,他们不是最好的

我只是将“ . html”添加到返回的字符串中。

解决方案 # 1: 您可以在服务器上安装 PHP 扩展(宿主)

将“地球上几乎每一种语言”音译为 ASCII 字符。

  1. 首先安装 PHP 国际版扩展。这是 Debian (Ubuntu)的命令: sudo aptitude install php5-intl

  2. 这是我的 fileName 函数(创建 test.php 并粘贴以下代码) :

<!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>

这句话的核心是:

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

基于 这篇文章的答案。

解决方案 # 2: 您无法在服务器(托管)上安装 PHP 扩展

enter image description here

在 CMS Drupal 的 音译模块中完成了相当不错的工作。它支持地球上几乎所有的语言。我建议检查插件 储存库,如果你想有真正完整的解决方案消毒字符串。

我推荐 * PHP 的 URLify (Github 上有480多颗星)-“ Django 项目的 URLify.js 的 PHP 端口。在 URL 中使用非 ascii 字符的音译”。

基本用法:

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


?>

为文件名生成蛞蝓:

<?php


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


?>

* 其他建议都不符合我的标准:

  • 应该可以通过作曲家安装
  • 不应该依赖 iconv,因为它在不同的系统上表现不同
  • 应该是可扩展的,以允许重写和自定义字符替换
  • 受欢迎(例如 Github 上的许多明星)
  • 有测试

作为额外的好处,URLify 还删除了某些单词,并去掉了所有不能音译的字符。

下面是一个使用 URLify: https://gist.github.com/motin/a65e6c1cc303e46900d10894bf2da87f正确音译大量外来字符的测试用例