140

我正在寻找一个 php 函数,它将清理字符串并使其准备好用于文件名。有谁知道好用的吗?

(我可以写一个,但我担心我会忽略一个字符!)

编辑:用于在 Windows NTFS 文件系统上保存文件。

4

18 回答 18

175

对 Tor Valamo 的解决方案进行小幅调整以解决 Dominic Rodger 注意到的问题,您可以使用:

// Remove anything which isn't a word, whitespace, number
// or any of the following caracters -_~,;[]().
// If you don't need to handle multi-byte characters
// you can use preg_replace rather than mb_ereg_replace
// Thanks @Łukasz Rysiak!
$file = mb_ereg_replace("([^\w\s\d\-_~,;\[\]\(\).])", '', $file);
// Remove any runs of periods (thanks falstro!)
$file = mb_ereg_replace("([\.]{2,})", '', $file);
于 2010-01-07T16:10:23.880 回答
71

这是您可以按照要求清理文件系统的文件名的方法

function filter_filename($name) {
    // remove illegal file system characters https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
    $name = str_replace(array_merge(
        array_map('chr', range(0, 31)),
        array('<', '>', ':', '"', '/', '\\', '|', '?', '*')
    ), '', $name);
    // maximise filename length to 255 bytes http://serverfault.com/a/9548/44086
    $ext = pathinfo($name, PATHINFO_EXTENSION);
    $name= mb_strcut(pathinfo($name, PATHINFO_FILENAME), 0, 255 - ($ext ? strlen($ext) + 1 : 0), mb_detect_encoding($name)) . ($ext ? '.' . $ext : '');
    return $name;
}

文件系统中允许其他所有内容,所以这个问题得到了完美的回答......

...但是如果您稍后在不安全的 HTML 上下文中使用它,例如允许在文件名中使用单引号可能会很危险,因为这个绝对合法的文件名:'

 ' onerror= 'alert(document.cookie).jpg

变成一个XSS 漏洞

<img src='<? echo $image ?>' />
// output:
<img src=' ' onerror= 'alert(document.cookie)' />

正因为如此,流行的 CMS 软件Wordpress将它们删除,但它们仅在一些更新后才涵盖所有相关字符:

$special_chars = array("?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}", "%", "+", chr(0));
// ... a few rows later are whitespaces removed as well ...
preg_replace( '/[\r\n\t -]+/', '-', $filename )

最后,他们的列表现在包括了URI 保留字符URL 不安全字符列表中的大部分字符。

当然,您可以简单地在 HTML 输出中对所有这些字符进行编码,但大多数开发人员和我也是如此,遵循成语“比抱歉更安全”并提前删除它们。

所以最后我建议使用这个:

function filter_filename($filename, $beautify=true) {
    // sanitize filename
    $filename = preg_replace(
        '~
        [<>:"/\\\|?*]|            # file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
        [\x00-\x1F]|             # control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
        [\x7F\xA0\xAD]|          # non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN
        [#\[\]@!$&\'()+,;=]|     # URI reserved https://www.rfc-editor.org/rfc/rfc3986#section-2.2
        [{}^\~`]                 # URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt
        ~x',
        '-', $filename);
    // avoids ".", ".." or ".hiddenFiles"
    $filename = ltrim($filename, '.-');
    // optional beautification
    if ($beautify) $filename = beautify_filename($filename);
    // maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
    $ext = pathinfo($filename, PATHINFO_EXTENSION);
    $filename = mb_strcut(pathinfo($filename, PATHINFO_FILENAME), 0, 255 - ($ext ? strlen($ext) + 1 : 0), mb_detect_encoding($filename)) . ($ext ? '.' . $ext : '');
    return $filename;
}

不会导致文件系统出现问题的所有其他内容都应该是附加功能的一部分:

function beautify_filename($filename) {
    // reduce consecutive characters
    $filename = preg_replace(array(
        // "file   name.zip" becomes "file-name.zip"
        '/ +/',
        // "file___name.zip" becomes "file-name.zip"
        '/_+/',
        // "file---name.zip" becomes "file-name.zip"
        '/-+/'
    ), '-', $filename);
    $filename = preg_replace(array(
        // "file--.--.-.--name.zip" becomes "file.name.zip"
        '/-*\.-*/',
        // "file...name..zip" becomes "file.name.zip"
        '/\.{2,}/'
    ), '.', $filename);
    // lowercase for windows/unix interoperability http://support.microsoft.com/kb/100625
    $filename = mb_strtolower($filename, mb_detect_encoding($filename));
    // ".file-name.-" becomes "file-name"
    $filename = trim($filename, '.-');
    return $filename;
}

此时如果结果为空,您需要生成一个文件名,您可以决定是否要对 UTF-8 字符进行编码。但是您不需要这样做,因为在 Web 托管上下文中使用的所有文件系统中都允许使用 UTF-8。

您唯一需要做的就是使用urlencode()(就像您希望对所有 URL 一样),因此文件名საბეჭდი_მანქანა.jpg成为此 URL 作为您的<img src><a href>: http: //www.maxrev.de/html/img/%E1%83% A1%E1%83%90%E1%83%91%E1%83%94%E1%83%AD%E1%83%93%E1%83%98_%E1%83%9B%E1%83%90% E1%83%9C%E1%83%A5%E1%83%90%E1%83%9C%E1%83%90.jpg

Stackoverflow 会这样做,所以我可以像用户一样发布此链接:http:
//www.maxrev.de/html/img/საბეჭდი_მანქანა.jpg

所以这是一个完整的合法文件名,而不是@SequenceDigitale.com 在他的回答中提到的问题

于 2017-02-06T00:02:54.030 回答
48

解决方案 1 - 简单有效

$file_name = preg_replace( '/[^a-z0-9]+/', '-', strtolower( $url ) );

  • strtolower() 保证文件名是小写的(因为大小写在 URL 中无关紧要,但在 NTFS 文件名中)
  • [^a-z0-9]+将确保,文件名只保留字母和数字
  • 替换无效字符以'-'保持文件名可读

例子:

URL:  http://stackoverflow.com/questions/2021624/string-sanitizer-for-filename
File: http-stackoverflow-com-questions-2021624-string-sanitizer-for-filename

解决方案 2 - 对于非常长的 URL

您想要缓存 URL 内容并且只需要具有唯一的文件名。我会使用这个功能:

$file_name = md5( strtolower( $url ) )

这将创建一个固定长度的文件名。在大多数情况下,MD5 散列对于这种用途来说是足够独特的。

例子:

URL:  https://www.amazon.com/Interstellar-Matthew-McConaughey/dp/B00TU9UFTS/ref=s9_nwrsa_gw_g318_i10_r?_encoding=UTF8&fpl=fresh&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=desktop-1&pf_rd_r=BS5M1H560SMAR2JDKYX3&pf_rd_r=BS5M1H560SMAR2JDKYX3&pf_rd_t=36701&pf_rd_p=6822bacc-d4f0-466d-83a8-2c5e1d703f8e&pf_rd_p=6822bacc-d4f0-466d-83a8-2c5e1d703f8e&pf_rd_i=desktop
File: 51301f3edb513f6543779c3a5433b01c
于 2017-03-20T15:56:45.927 回答
43

使用 rawurlencode() 怎么样? http://www.php.net/manual/en/function.rawurlencode.php

这是一个甚至可以清除中文字符的功能:

public static function normalizeString ($str = '')
{
    $str = strip_tags($str); 
    $str = preg_replace('/[\r\n\t ]+/', ' ', $str);
    $str = preg_replace('/[\"\*\/\:\<\>\?\'\|]+/', ' ', $str);
    $str = strtolower($str);
    $str = html_entity_decode( $str, ENT_QUOTES, "utf-8" );
    $str = htmlentities($str, ENT_QUOTES, "utf-8");
    $str = preg_replace("/(&)([a-z])([a-z]+;)/i", '$2', $str);
    $str = str_replace(' ', '-', $str);
    $str = rawurlencode($str);
    $str = str_replace('%', '-', $str);
    return $str;
}

这是解释

  1. 去除 HTML 标签
  2. 删除中断/制表符/回车
  3. 删除文件夹和文件名的非法字符
  4. 将字符串小写
  5. 通过将 Éàû 等外来重音转换为 html 实体来删除它,然后删除代码并保留字母。
  6. 用破折号替换空格
  7. 对可以通过前面步骤的特殊字符进行编码,并在服务器上输入冲突文件名。前任。"中文百强网"
  8. 将“%”替换为破折号,以确保在查询文件时浏览器不会重写文件的链接。

好的,一些文件名不会是相关的,但在大多数情况下它会起作用。

前任。原名:“საბეჭდი-და-ტიპოგრაფიული.jpg”

输出名称:“-E1-83-A1-E1-83-90-E1-83-91-E1-83-94-E1-83-AD-E1-83-93-E1-83-98--E1- 83-93-E1-83-90--E1-83-A2-E1-83-98-E1-83-9E-E1-83-9D-E1-83-92-E1-83-A0-E1-83 -90-E1-83-A4-E1-83-98-E1-83-A3-E1-83-9A-E1-83-98.jpg"

这比 404 错误要好。

希望这会有所帮助。

卡尔。

于 2013-09-26T03:17:37.130 回答
40

而不是担心忽略字符 - 使用您乐于使用的字符白名单怎么样?例如,您可以只允许良好的 ol' a-z0-9、和句点 ( )_的单个实例。.这显然比大多数文件系统更具限制性,但应该保证您的安全。

于 2010-01-07T16:02:45.280 回答
19

好吧, tempnam() 会为你做这件事。

http://us2.php.net/manual/en/function.tempnam.php

但这会创建一个全新的名称。

要清理现有字符串,只需限制用户可以输入的内容,使其成为字母、数字、句点、连字符和下划线,然后使用简单的正则表达式进行清理。检查哪些字符需要转义,否则您可能会得到误报。

$sanitized = preg_replace('/[^a-zA-Z0-9\-\._]/','', $filename);
于 2010-01-07T16:04:25.013 回答
14
preg_replace("[^\w\s\d\.\-_~,;:\[\]\(\]]", '', $file)

根据系统允许的内容添加/删除更多有效字符。

或者,您可以尝试创建文件,然后在文件错误时返回错误。

于 2010-01-07T16:02:08.577 回答
12

安全:将 NOT "a-zA-Z0-9_-" 的每个序列替换为破折号;自己添加一个扩展。

$name = preg_replace('/[^a-zA-Z0-9_-]+/', '-', strtolower($name)).'.'.$extension;

所以一个PDF叫做

"This is a grüte test_service +/-30 thing"

变成

"This-is-a-gr-te-test_service-30-thing.pdf"
于 2019-05-12T12:14:55.317 回答
10

PHP 提供了将文本清理为不同格式的功能

filter.filters.sanitize

如何 :

echo filter_var(
   "Lorem Ipsum has been the industry's",FILTER_SANITIZE_URL
); 

块引用LoremIpsumhasbeentheindustry's

于 2017-03-16T02:17:24.130 回答
7

对 Sean Vieira 的解决方案进行小幅调整以允许使用单点,您可以使用:

preg_replace("([^\w\s\d\.\-_~,;:\[\]\(\)]|[\.]{2,})", '', $file)
于 2013-07-12T23:59:36.450 回答
6

以下表达式创建了一个漂亮、干净且可用的字符串:

/[^a-z0-9\._-]+/gi

今天的财务:计费变成今天的财务计费

于 2010-01-07T16:01:15.690 回答
2

这些可能有点重,但它们足够灵活,可以将任何字符串清理为“安全”en样式的文件名或文件夹名称(或者,如果你弯曲它,甚至可以擦洗 slug 和其他东西)。

1)建立一个完整的文件名(在输入被完全截断的情况下使用备用名称):

str_file($raw_string, $word_separator, $file_extension, $fallback_name, $length);

2)或仅使用过滤器实用程序而不构建完整的文件名(严格模式true将不允许文件名中的 [] 或 ()):

str_file_filter($string, $separator, $strict, $length);

3) 以下是这些功能:

// Returns filesystem-safe string after cleaning, filtering, and trimming input
function str_file_filter(
    $str,
    $sep = '_',
    $strict = false,
    $trim = 248) {

    $str = strip_tags(htmlspecialchars_decode(strtolower($str))); // lowercase -> decode -> strip tags
    $str = str_replace("%20", ' ', $str); // convert rogue %20s into spaces
    $str = preg_replace("/%[a-z0-9]{1,2}/i", '', $str); // remove hexy things
    $str = str_replace("&nbsp;", ' ', $str); // convert all nbsp into space
    $str = preg_replace("/&#?[a-z0-9]{2,8};/i", '', $str); // remove the other non-tag things
    $str = preg_replace("/\s+/", $sep, $str); // filter multiple spaces
    $str = preg_replace("/\.+/", '.', $str); // filter multiple periods
    $str = preg_replace("/^\.+/", '', $str); // trim leading period

    if ($strict) {
        $str = preg_replace("/([^\w\d\\" . $sep . ".])/", '', $str); // only allow words and digits
    } else {
        $str = preg_replace("/([^\w\d\\" . $sep . "\[\]\(\).])/", '', $str); // allow words, digits, [], and ()
    }

    $str = preg_replace("/\\" . $sep . "+/", $sep, $str); // filter multiple separators
    $str = substr($str, 0, $trim); // trim filename to desired length, note 255 char limit on windows

    return $str;
}


// Returns full file name including fallback and extension
function str_file(
    $str,
    $sep = '_',
    $ext = '',
    $default = '',
    $trim = 248) {

    // Run $str and/or $ext through filters to clean up strings
    $str = str_file_filter($str, $sep);
    $ext = '.' . str_file_filter($ext, '', true);

    // Default file name in case all chars are trimmed from $str, then ensure there is an id at tail
    if (empty($str) && empty($default)) {
        $str = 'no_name__' . date('Y-m-d_H-m_A') . '__' . uniqid();
    } elseif (empty($str)) {
        $str = $default;
    }

    // Return completed string
    if (!empty($ext)) {
        return $str . $ext;
    } else {
        return $str;
    }
}

所以假设一些用户输入是:.....&lt;div&gt;&lt;/div&gt;<script></script>&amp; Weiß Göbel 中文百强网File name %20 %20 %21 %2C Décor \/. /. . z \... y \...... x ./ “This name” is & 462^^ not &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = that grrrreat -][09]()1234747) საბეჭდი-და-ტიპოგრაფიული

我们想把它转换成更友好的东西来制作一个文件名长度为 255 个字符的 tar.gz。这是一个示例用法。注意:此示例包含一个格式错误的 tar.gz 扩展作为概念证明,在针对白名单构建字符串后,您仍应过滤 ext。

$raw_str = '.....&lt;div&gt;&lt;/div&gt;<script></script>&amp; Weiß Göbel 中文百强网File name  %20   %20 %21 %2C Décor  \/.  /. .  z \... y \...... x ./  “This name” is & 462^^ not &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = that grrrreat -][09]()1234747) საბეჭდი-და-ტიპოგრაფიული';
$fallback_str = 'generated_' . date('Y-m-d_H-m_A');
$bad_extension = '....t&+++a()r.gz[]';

echo str_file($raw_str, '_', $bad_extension, $fallback_str);

输出将是:_wei_gbel_file_name_dcor_._._._z_._y_._x_._this_name_is_462_not_that_grrrreat_][09]()1234747)_.tar.gz

你可以在这里玩:https ://3v4l.org/iSgi8

或要点:https ://gist.github.com/dhaupin/b109d3a8464239b7754a

编辑: 更新了脚本过滤器&nbsp;而不是空间,更新了 3v4l 链接

于 2016-01-20T19:23:21.063 回答
1

我今天知道的最好的是来自 Nette 框架的静态方法Strings::webalize 。

顺便说一句,这会将所有变音符号转换为它们的基本符号。š=>s ü=>u ß=>ss 等。

对于文件名,您必须添加点“。” 到允许的字符参数。

/**
 * Converts to ASCII.
 * @param  string  UTF-8 encoding
 * @return string  ASCII
 */
public static function toAscii($s)
{
    static $transliterator = NULL;
    if ($transliterator === NULL && class_exists('Transliterator', FALSE)) {
        $transliterator = \Transliterator::create('Any-Latin; Latin-ASCII');
    }

    $s = preg_replace('#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{2FF}\x{370}-\x{10FFFF}]#u', '', $s);
    $s = strtr($s, '`\'"^~?', "\x01\x02\x03\x04\x05\x06");
    $s = str_replace(
        array("\xE2\x80\x9E", "\xE2\x80\x9C", "\xE2\x80\x9D", "\xE2\x80\x9A", "\xE2\x80\x98", "\xE2\x80\x99", "\xC2\xB0"),
        array("\x03", "\x03", "\x03", "\x02", "\x02", "\x02", "\x04"), $s
    );
    if ($transliterator !== NULL) {
        $s = $transliterator->transliterate($s);
    }
    if (ICONV_IMPL === 'glibc') {
        $s = str_replace(
            array("\xC2\xBB", "\xC2\xAB", "\xE2\x80\xA6", "\xE2\x84\xA2", "\xC2\xA9", "\xC2\xAE"),
            array('>>', '<<', '...', 'TM', '(c)', '(R)'), $s
        );
        $s = @iconv('UTF-8', 'WINDOWS-1250//TRANSLIT//IGNORE', $s); // intentionally @
        $s = strtr($s, "\xa5\xa3\xbc\x8c\xa7\x8a\xaa\x8d\x8f\x8e\xaf\xb9\xb3\xbe\x9c\x9a\xba\x9d\x9f\x9e"
            . "\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3"
            . "\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8"
            . "\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe"
            . "\x96\xa0\x8b\x97\x9b\xa6\xad\xb7",
            'ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt- <->|-.');
        $s = preg_replace('#[^\x00-\x7F]++#', '', $s);
    } else {
        $s = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); // intentionally @
    }
    $s = str_replace(array('`', "'", '"', '^', '~', '?'), '', $s);
    return strtr($s, "\x01\x02\x03\x04\x05\x06", '`\'"^~?');
}


/**
 * Converts to web safe characters [a-z0-9-] text.
 * @param  string  UTF-8 encoding
 * @param  string  allowed characters
 * @param  bool
 * @return string
 */
public static function webalize($s, $charlist = NULL, $lower = TRUE)
{
    $s = self::toAscii($s);
    if ($lower) {
        $s = strtolower($s);
    }
    $s = preg_replace('#[^a-z0-9' . preg_quote($charlist, '#') . ']+#i', '-', $s);
    $s = trim($s, '-');
    return $s;
}
于 2016-09-14T05:52:41.910 回答
1

似乎这一切都取决于一个问题,是否有可能创建一个可用于入侵服务器(或造成其他一些损害)的文件名。如果不是,那么简单的答案似乎是尝试在最终使用的任何地方创建文件(因为这将是选择的操作系统,毫无疑问)。让操作系统来解决它。如果它抱怨,则将该抱怨作为验证错误返回给用户。

这具有可靠便携的额外好处,因为如果文件名没有为该操作系统正确形成,所有(我很确定)操作系统都会抱怨。

如果可以用文件名做邪恶的事情,也许可以在驻留操作系统上测试文件名之前应用一些措施——这些措施比文件名的完整“卫生”简单。

于 2017-10-27T17:01:32.287 回答
0

/并且..在用户提供的文件名中可能是有害的。因此,您应该通过以下方式摆脱这些:

$fname = str_replace('..', '', $fname);
$fname = str_replace('/',  '', $fname);
于 2010-01-07T16:04:56.493 回答
0

单程

$bad='/[\/:*?"<>|]/';
$string = 'fi?le*';

function sanitize($str,$pat)
{
    return preg_replace($pat,"",$str);

}
echo sanitize($string,$bad);
于 2010-01-08T00:56:09.117 回答
0
function sanitize_file_name($file_name) { 
 // case of multiple dots
  $explode_file_name =explode('.', $file_name);
  $extension =array_pop($explode_file_name);
  $file_name_without_ext=substr($file_name, 0, strrpos( $file_name, '.') );    
  // replace special characters
  $file_name_without_ext = preg_quote($file_name_without_ext);
  $file_name_without_ext = preg_replace('/[^a-zA-Z0-9\\_]/', '_', $file_name_without_ext);
  $file_name=$file_name_without_ext . '.' . $extension;    
  return $file_name;
}
于 2021-12-17T11:13:57.573 回答
-4

$fname = str_replace('/','',$fname);

由于用户可能会使用斜线来分隔两个单词,因此最好用破折号代替 NULL

于 2013-04-04T22:02:04.077 回答