清理字符串以使它们的URL和文件名安全?


136

我正在尝试提出一个功能,该功能可以很好地清理某些字符串,以使它们可以安全地在URL中使用(如post slug),也可以安全地用作文件名。例如,当有人上传文件时,我要确保我从名称中删除所有危险字符。

到目前为止,我已经提出了以下功能,希望该功能可以解决此问题,并允许外来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文件

更新:删除了星形字符,因为我无法想到有效的用法


您最好删除[[w.-]以外的所有内容
elias 2010年

3
您可能会发现Normalizer及其相关评论很有用。
马特·吉布森

Answers:


57

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

  1. 模式结尾处的“ u”表示该模式(而不是它匹配的文本)将被解释为UTF-8(我想您假设是后者?)。
  2. \ w与下划线字符匹配。您特别将其包含在文件中,这会导致您假设您不希望它们出现在URL中,但是在代码中您拥有的URL将被允许包含下划线。
  3. 包含“外国UTF-8”似乎取决于语言环境。目前尚不清楚这是服务器还是客户端的语言环境。从PHP文档:

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

创建弹头

您可能不应该在帖子中添加带重音符号的字符,因为从技术上讲,应该对它们进行百分比编码(按照URL编码规则),这样您的URL看起来很难看。

因此,如果您是我,请在小写之后将所有“特殊”字符转换为它们的等价字符(例如é-> e),并用“-”替换非[az]字符,限于运行单个“-”如您所愿。这里有一个转换特殊字符的实现: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/ 类别:OWASP_Enterprise_Security_API


您对我对“ u”修饰符的假设是正确的-我认为这是针对文本的。我也忘记了\ w修饰符,包括下划线。我通常会将所有带重音符号的字符都转换为ASCII-但我希望这也适用于其他语言。我以为会有某种UTF-8安全方式,可以在网址段或文件名中使用语言的任何字符,以便甚至阿拉伯语标题也可以使用。毕竟,Linux支持UTF-8文件名,浏览器根据需要对HTML链接进行编码。非常感谢您在这里的投入。
Xeoncross

再三考虑,您实际上是对的,但这不仅仅是浏览器正确编码链接的问题。实现接近所需结果的最简单方法是将非ASCII字符映射到最接近的ASCII等价字符,然后在HTML正文中对链接进行URL编码。困难的方法是通过网络服务器,应用程序层(PHP),页面内容,网络浏览器,从数据存储区确保一致的UTF-8编码(对于某些中文,我认为是UTF-16,我认为有些中文是方言),而不是对您的网址进行urlencode(但仍会删除“多余的”字符)。这将为您提供不错的非编码链接和URL。
艾伦·唐纳利

好建议。我将尝试创建一个纯UTF-8环境。然后,从非ASCII语言中提取几个字符串,我将删除危险的字符(./ ;: etc ...)并创建文件,然后创建指向这些文件的HTML链接,以查看是否可以单击它们并查看是否全部作品。如果不是,那么我可能不得不回到(raw)?urlencode()以允许UTF-8。我将在这里发布结果。
Xeoncross

3
我创建了一个名为的文件สังเวช พระปกเกศกองบู๊กู้ขึ้นใหม่.txt,然后创建了一个UTF-8 HTML文件及其链接。令人惊讶的是,即使在Windows上,它也能正常工作!但是,后来我有了PHP file_put_contents('สังเวช พระปกเกศกองบู๊กู้ขึ้นใหม่.txt'),它无法从该字符串创建市集文件名。然后我尝试用创建它,fopen()并得到相同的文件名。因此,显然,PHP(至少在Windows上)无法创建UTF-8文件名。bugs.php.net/bug.php?id=46990&thanks=6
Xeoncross

1
我之所以能回答这个问题,是因为它使我思考得最多,并且还提供了一个我从未听说过的值得研究的项目的有用链接。一旦找到答案,我就会发布。
Xeoncross

87

我在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月更新

Alix Axel在这方面做了不可思议的工作。他的检查框架包括几个出色的文本过滤器和转换。


23
WordPress代码不可移植,因为它使用了apply_filters
Kevin Mark

1
需要注意的是WordPress的版本将替换/[\s-]+/-比第一个版本更好(只替换/\s+/),可连续导致多个破折号
Yotam奥马尔

仅供参考,可以在此处找到wordpress apply_filters 并在此处找到sanitize_file_name 。
埃里克

那多个空格呢?替换
长颈鹿杰弗里(Jeffrey the Giraffe)

8
$ anal变量对于强制选项听起来很吓人。
viljun

30

这应该使您的文件名安全...

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

最后一行。


1
仍然缺少一些捷克和斯洛伐克字符:'ľ' => 'l', 'Ľ' => 'L', 'č' => 'c', 'Č' => 'C', 'ť' => 't', 'Ť' => 'T', 'ň' => 'n', 'Ň' => 'N', 'ĺ' => 'l', 'Ĺ' => 'L', 'Ř' => 'R', 'ř' => 'r', 'ě' => 'e', 'Ě' => 'E', 'ů' => 'u', 'Ů' => 'U'
Jasom Dotnet 2015年

22

试试这个:

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友好用户名?


非常好-我从来没有见过没有翻译表(例如wordpress使用)完成此操作。但是,我认为此功能不足够,因为它只能翻译特殊字符,而不能删除危险字符。也许可以将其添加到上面的其中一个 ...
Xeoncross

4
哈!那个实体编码技巧真是太好了!虽然乍一看还不清楚这种方法是如何完成的。不过有一个问题。“Frédéric&Éric”不会变成“ Frederic amp Eric”吗?
艾伦·唐纳利

@AlanDonnelly:确实,我已经在原始答案中更新了该功能(请检查链接),trim()也应该为trim($string, '-')
Alix Axel

@ Xeoncross:最后preg_replace()应该删除所有危险字符。
Alix Axel

@AlixAxel,您到处都是,不是。我刚刚阅读了PHP AWS SDK,他们有一些UUID的代码。令人敬畏的规矩代码很难被击败。
Xeoncross

13

这还不是完全的答案,因为它还没有提供任何解决方案(但!),但是太大了,无法放入评论中……


我在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/

但是,在Windows 7(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个字符。

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

如果删除C:\了256个字符(而不是255个字符!),则所有这些示例的总数(包括斜杠)总计259个字符。使用资源管理器创建目录时,您会注意到它限制了自己使用目录名称的所有可用空间。这样做的原因是允许使用8.3文件命名约定来创建文件。其他分区也会发生相同的情况。

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

(255 chars) E:\12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901.txt

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

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

3.当心保留的字符和关键字

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

"*/:<>?\|

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

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

还有一些特殊的关键字应避免使用(例如NUL),尽管我不确定该如何克服。也许黑名单带有一个随机名称回退是解决它的好方法。

4.区分大小写

这应该不用说,但是如果您要确保跨不同操作系统的文件唯一性,则应将文件名转换为规范化的大小写,这样my_file.txtMy_File.txt在Linux上,两者就不会完全一样my_file.txt上,Windows上的文件就文件。

5.确保它是唯一的

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

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

6.隐藏文件

仅仅因为它可以命名并不意味着它应该...

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

7.其他注意事项

如果必须删除文件名的一些字符,则扩展名通常比文件的基本名称更重要。文件扩展名允许使用最大数量的字符(8-16),应该从基本名称中删除字符。同样重要的是要注意,在具有一个以上的长拓不大可能发生-如_.graphmlz.tag.gz- _.graphmlz.tag_应被视为在这种情况下,文件基本名称。

8.资源

口径相当不错地处理文件名处理:

有关使用Samba 进行文件名处理和链接的章节的Wikipedia页面


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

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

11

我一直认为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。

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


5

在文件上传方面,最安全的方法是阻止用户控制文件名。正如已经暗示的那样,将规范化的文件名以及随机选择的唯一名称(将用作实际文件名)存储在数据库中。

使用OWASP ESAPI,可以生成以下名称:

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

您可以在$ safeFilename后面附加一个时间戳,以确保随机生成的文件名是唯一的,甚至无需检查现有文件。

关于URL的编码,然后再次使用ESAPI:

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

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


当然,将文件名控制权从用户手中夺走也可以防止2次具有相同名称的上载。
CodeVirtuoso 2011年

5

我建议* 用于PHP的URLify(在Github上为480颗星以上) -“来自Django项目的URLify.js的PHP端口。对非ascii字符进行音译以用于URL”。

基本用法:

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


1
谢谢-看起来很适合我的目的。
大卫·古德温

5

我从另一个来源改编并添加了一些额外内容,也许有点过大

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

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

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

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

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

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

    return $uri;
}

5

这是来自Joomla 3.3.2的版本 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));
}

4

我认为没有要删除的字符列表是安全的。我宁愿使用以下内容:

对于文件名:使用内部ID或文件内容的哈希。将文档名称保存在数据库中。这样,您可以保留原始文件名并仍然找到文件。

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


1
我同意,这里列出的大多数方法都会删除已知的危险字符-我的方法会删除所有不是已知的安全字符。由于大多数系统都会对URL的末尾进行编码,因此我建议我们继续采用这种行之有效的方法,而不要使用记录在案的UTF-8不安全的 urlencode()。
Xeoncross

3

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


是的,测试mb_strlen()总是很重要的!
Xeoncross

3

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

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

我对此不太确定,因为.\x00..\x20可以简化为.\x00\x20
Xeoncross

@Xeoncross:我认为.\x00..\x20中移除了点之间每个字符\x00\x20,而.\x00\x20只应删除这些3个字节。
Alix Axel 2012年

此答案需要更多说明才能安全使用。网上没有太多有关charlist确切语法的信息。
Manuel Arwed Schmidt 2015年

3

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

2

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


2
不建议将%字符用于文件名,并且十六进制编码的字符在URL中看起来不太好。浏览器可以支持UTF-8字符串,对于非ascii语言而言,这种字符串更好,更容易。
Xeoncross

您可以执行urlencode,然后使用str_replace('%20','-',url)吗?
Francesco

2

已经为这个问题提供了几种解决方案,但是我已经在这里阅读并测试了大多数代码,最终得到了这个解决方案,该解决方案是我在这里学到的:

功能

该函数在此处捆绑在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测试,首先是测试边缘案例,因此您可以检查它是否符合您的需求:(如果发现错误,请随时添加测试案例)

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)

1
这主要假设基于拉丁语的输入。添加其他语言的更多UTF-8字符以查看您将在哪里遇到问题。
Xeoncross

@Xeoncross我同意,正如基督徒所说,必须保存一个ID或哈希值以及原始文件名。但是此功能提供了一种替代方法,因为您可以在清理过程失败时指定默认字符串。我已经为这种情况添加了单元测试。感谢您报告错误。
COil 2012年

2

我的条目标题包含各种奇怪的拉丁字符以及一些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;
}

我需要手动将破折号(-)添加到转换数组中。可能还有其他,但到目前为止,我的文件名看起来不错。

所以:

第1部分:我爸爸的“Žurburts”?-他们(不是)最好的!

变成:

第1部分,我的爸爸zurburts,他们不是最好的

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


1
仍然缺少一些捷克和斯洛伐克字符:'ľ' => 'l', 'Ľ' => 'L', 'č' => 'c', 'Č' => 'C', 'ť' => 't', 'Ť' => 'T', 'ň' => 'n', 'Ň' => 'N', 'ĺ' => 'l', 'Ĺ' => 'L', 'Ř' => 'R', 'ř' => 'r', 'ě' => 'e', 'Ě' => 'E', 'ů' => 'u', 'Ů' => 'U'
Jasom Dotnet

1
毫无疑问,还有更多。我实际上是在尝试找出是否存在包含字符组合的ISO-集。如果内容要求所有字符都一个,该如何“选择”一套?UTF-8我假设...
cbmtrx

我发现了如何使用一行PHP音译任何字符串$string = transliterator_transliterate('Any-Latin;Latin-ASCII;', $string);请参阅下面的答案或阅读链接的博客文章。
Jasom Dotnet

1
不,您读错了:如果可以在服务器(或主机)上安装PHP扩展:-)这是文章
Jasom Dotnet

1
知道了 感谢@JasomDotnet-我目前可以使用当前的解决方案,但是它的字符集有限,因此值得一试。
cbmtrx

2

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

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

  1. 首先安装PHP Intl扩展。这是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扩展

在此处输入图片说明

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



1

这是一个很好的功能:

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

看起来不好 \\s+表示反斜杠,后接一个或多个空格。那是什么意思 此外,它使用黑名单而不是白名单来忽略CMD,null或BEL
Xeoncross

还差 现在/blog/2014-02/just-in-time不允许使用类似的字符串。请使用上面经过测试的代码或使用phunctionPHP框架代码。
Xeoncross

那就对了。此功能仅适用于“即时”部分。对某些人可能有用。
joan16v

1
您可以更改正则表达式preg_replace('~[^\-\pL\pN\s]+~u', '-', $string)
Xeoncross

太棒了!我还添加了:string = trim($ string,“-”);
joan16v

0

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


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

} 
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.