Очиститель строки для имени файла

Я ищу функцию php, которая очистит строку и сделает ее готовой к использованию для имени файла. Кто-нибудь знает удобный?

(Я мог бы написать один, но я боюсь, что я пропущу характер!)

Редактировать: для сохранения файлов в файловой системе Windows NTFS.

19 ответов

Решение

Вместо того, чтобы беспокоиться о пропущенных символах - как насчет использования белого списка символов, который вы с удовольствием используете? Например, вы могли бы позволить просто хороший старый a-z, 0-9, _и один экземпляр периода (.). Это, очевидно, более ограничивает, чем большинство файловых систем, но должно держать вас в безопасности.

Сделав небольшую поправку к решению Tor Valamo, чтобы исправить проблему, замеченную Домиником Роджером, вы можете использовать:

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

Вот как вы можете продезинфицировать файловую систему, как и просили

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://tools.ietf.org/html/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. Но вам это не нужно, поскольку 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

Stackru делает это, поэтому я могу опубликовать эту ссылку так, как это сделал бы пользователь:
http://www.maxrev.de/html/img/ საბეჭდი_მანქანა. JPG

Так что это полное допустимое имя файла, а не проблема, как @SequenceDigitale.com упомянул в своем ответе.

РЕШЕНИЕ 1 - просто и эффективно

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

  • strtolower() гарантирует, что имя файла является строчным (так как регистр не имеет значения внутри URL, но в имени файла NTFS)
  • [^a-z0-9]+ обеспечит, имя файла содержит только буквы и цифры
  • Заменить недопустимые символы на '-' сохраняет имя файла читаемым

Пример:

URL:  http://stackru.com/questions/2021624/string-sanitizer-for-filename
File: http-stackru.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

Как насчет использования 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.

Надеюсь, это было полезно.

Карл.

Ну, tempnam() сделает это за вас.

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

но это создает совершенно новое имя.

Чтобы очистить существующую строку, просто ограничьте то, что ваши пользователи могут вводить, и введите в нее буквы, цифры, точку, дефис и подчеркивание, а затем очистите с помощью простого регулярного выражения. Проверьте, каких персонажей нужно экранировать, иначе вы можете получить ложные срабатывания.

$sanitized = preg_replace('/[^a-zA-Z0-9\-\._]/','', $filename);

Безопасно : заменить каждую последовательность НЕ "a-zA-Z0-9_-" на тире; добавьте расширение самостоятельно.

$name = preg_replace('/[^a-zA-Z0-9_-]+/', '-', strtolower($name)).'.'.$extension;
preg_replace("[^\w\s\d\.\-_~,;:\[\]\(\]]", '', $file)

Добавьте / удалите больше допустимых символов в зависимости от того, что разрешено для вашей системы.

В качестве альтернативы вы можете попытаться создать файл, а затем вернуть ошибку, если она плохая.

PHP предоставляет функцию для очистки текста в другой формат

filter.filters.sanitize

Как:

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

Blockquote LoremIpsumhasbeentheindustry's

Сделав небольшую корректировку решения Шона Виейры, чтобы учесть одиночные точки, вы можете использовать:

preg_replace("([^\w\s\d\.\-_~,;:\[\]\(\)]|[\.]{2,})", '', $file)

Следующее выражение создает красивую, чистую и пригодную для использования строку:

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

Превращение сегодняшнего финансового: биллинг в сегодняшний финансовый биллинг

Лучшее, что я знаю сегодня, это статический метод Strings::webalize из фреймворка Nette.

Кстати, это переводит все диакритические знаки в их основные.. š=>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;
}

Они могут быть немного тяжелыми, но они достаточно гибки, чтобы дезинфицировать любую строку в "сейф" en введите имя файла или имя папки (или, черт возьми, даже вычищенные слизни и прочее, если вы согнете его).

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) საბეჭდი-და-ტიპოგრაფიული

И мы хотим преобразовать его во что-то более дружелюбное, чтобы создать tar.gz с длиной имени файла 255 символов. Вот пример использования. Примечание: в этом примере в качестве доказательства концепции используется неверно сформированное расширение 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

Используйте это, чтобы принимать только слова (поддержка Unicode, например utf-8) и "." и "-" и "_" в строке:

      $sanitized = preg_replace('/[^\w\-\._]/u','', $filename);

Кажется, это все зависит от вопроса, возможно ли создать имя файла, которое можно использовать для взлома сервера (или нанести какой-то другой ущерб). Если нет, тогда кажется, что простой ответ - попытаться создать файл везде, где он, в конечном счете, будет использоваться (поскольку, без сомнения, это будет выбранная операционная система). Позвольте операционной системе разобраться. Если он жалуется, перенесите эту жалобу обратно пользователю как ошибка проверки.

Это дает дополнительное преимущество, заключающееся в надежности переносимости, поскольку все (я почти уверен) операционные системы будут жаловаться, если имя файла не сформировано должным образом для этой ОС.

Если с именем файла можно делать отвратительные вещи, возможно, существуют меры, которые можно применить перед проверкой имени файла в резидентной операционной системе - меры менее сложные, чем полная "санация" имени файла.

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

В одну сторону

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

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

}
echo sanitize($string,$bad);

/ а также .. В предоставленном пользователем имени файла может быть вредно. Таким образом, вы должны избавиться от них, например:

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

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

Поскольку пользователи могут использовать косую черту для разделения двух слов, лучше заменить тире вместо NULL

Другие вопросы по тегам