Есть ли способ сделать строковый путь к файлу безопасным в С #?
94
Моя программа будет брать произвольные строки из Интернета и использовать их для имен файлов. Есть ли простой способ удалить плохие символы из этих строк или мне нужно написать для этого специальную функцию?
Ух, ненавижу, когда люди пытаются угадать, какие символы действительны. Помимо того, что они полностью непереносимы (всегда думают о Mono), в обоих предыдущих комментариях пропущено более 25 недопустимых символов.
'Clean just a filenameDim filename AsString = "salmnas dlajhdla kjha;dmas'lkasn"ForEach c In IO.Path.GetInvalidFileNameChars
filename = filename.Replace(c, "")
Next'See also IO.Path.GetInvalidPathChars
Версия C #: foreach (var c в Path.GetInvalidFileNameChars ()) {fileName = fileName.Replace (c, '-'); }
jcollum
8
Как это решение справится с конфликтами имен? Кажется, что одному имени файла может соответствовать несколько строк (например, «Ад?» И «Ад *»). Если вы в порядке, удаляете только оскорбительные символы, тогда хорошо; в противном случае вы должны быть осторожны с конфликтами имен.
Стефано Риккарди,
2
как насчет ограничений файловой системы на длину имени (и пути)? как насчет зарезервированных имен файлов (PRN CON)? Если вам нужно сохранить данные и исходное имя, вы можете использовать 2 файла с именами Guid: guid.txt и guid.dat
Джек,
7
Один лайнер, для развлечения result = Path.GetInvalidFileNameChars (). Aggregate (result, (current, c) => current.Replace (c, '-'));
Paul Knopf
1
@PaulKnopf, вы уверены, что JetBrain не имеет авторских прав на этот код;)
Маркус
37
Чтобы удалить недопустимые символы:
staticreadonlychar[] invalidFileNameChars = Path.GetInvalidFileNameChars();
// Builds a string out of valid charsvar validFilename = newstring(filename.Where(ch => !invalidFileNameChars.Contains(ch)).ToArray());
Чтобы заменить недопустимые символы:
staticreadonlychar[] invalidFileNameChars = Path.GetInvalidFileNameChars();
// Builds a string out of valid chars and an _ for invalid onesvar validFilename = newstring(filename.Select(ch => invalidFileNameChars.Contains(ch) ? '_' : ch).ToArray());
Чтобы заменить недопустимые символы (и избежать потенциального конфликта имен, например Hell * vs Hell $):
staticreadonly IList<char> invalidFileNameChars = Path.GetInvalidFileNameChars();
// Builds a string out of valid chars and replaces invalid chars with a unique letter (Moves the Char into the letter range of unicode, starting at "A")var validFilename = newstring(filename.Select(ch => invalidFileNameChars.Contains(ch) ? Convert.ToChar(invalidFileNameChars.IndexOf(ch) + 65) : ch).ToArray());
Этот вопрос задавался много раз раньше, и, как уже неоднократно отмечалось, IO.Path.GetInvalidFileNameCharsон неадекватен.
Во-первых, есть много имен, таких как PRN и CON, которые зарезервированы и не допускаются для имен файлов. Есть другие имена, которые нельзя использовать только в корневой папке. Имена, оканчивающиеся на точку, также не допускаются.
Во-вторых, существует множество ограничений по длине. Полный список NTFS читайте здесь .
В-третьих, вы можете подключаться к файловым системам, которые имеют другие ограничения. Например, имена файлов ISO 9660 не могут начинаться с символа «-», но могут содержать его.
В-четвертых, что делать, если два процесса «произвольно» выбирают одно и то же имя?
В общем, использование имен файлов, сгенерированных извне, - плохая идея. Я предлагаю сгенерировать ваши собственные личные имена файлов и хранить внутри себя удобочитаемые имена.
Хотя вы технически точны, GetInvalidFileNameChars подходит для 80% + ситуаций, в которых вы бы его использовали, поэтому это хороший ответ. Думаю, ваш ответ был бы более подходящим в качестве комментария к принятому ответу.
CubanX
4
Я согласен с DourHighArch. Сохраните файл внутри как guid, сославшись на «понятное имя», которое хранится в базе данных. Не позволяйте пользователям контролировать ваши пути на веб-сайте, иначе они попытаются украсть ваш web.config. Если вы включите переопределение URL-адресов, чтобы очистить его, это будет работать только для совпадающих дружественных URL-адресов в базе данных.
rtpHarry
22
Я согласен с Грауэнвольфом и очень рекомендую Path.GetInvalidFileNameChars()
Почему в мире вы бы использовать Array.ForEachвместо того , чтобы просто foreachздесь
BlueRaja - Дэнни Pflughoeft
9
Если вы хотите быть еще более кратким / загадочным:Path.GetInvalidFileNameChars().Aggregate(file, (current, c) => current.Replace(c, '-'))
Майкл Петито
@ BlueRaja-DannyPflughoeft Потому что вы хотите сделать его медленнее?
Джонатан Аллен,
@ Джонатан Аллен, почему вы думаете, что foreach быстрее, чем Array.ForEach?
Ryan Buddicom
5
@rbuddicom Array.ForEach принимает делегата, что означает, что ему нужно вызвать функцию, которую нельзя встроить. Для коротких строк вы можете потратить больше времени на служебные вызовы функций, чем на фактическую логику. .NET Core ищет способы «де-виртуализации» вызовов, уменьшая накладные расходы.
Я не уверен, как рассчитывается результат GetInvalidFileNameChars, но «Get» предполагает, что это нетривиально, поэтому я кэширую результаты. Кроме того, при этом проходит входная строка только один раз, а не несколько раз, как в решениях выше, которые перебирают набор недопустимых символов, заменяя их в исходной строке по одному. Кроме того, мне нравятся решения на основе Where, но я предпочитаю заменять недопустимые символы вместо их удаления. Наконец, моя замена - это ровно один символ, чтобы избежать преобразования символов в строки, когда я перебираю строку.
Я говорю все это без профилирования - мне просто "показалось" приятным. :)
на самом деле \Wсоответствует большему количеству не буквенно-цифровых ( [^A-Za-z0-9_]). Все символы Unicode "word" (русский 中文 ... и т. Д.) Также не будут заменены. Но это хорошо.
Ishmael
Единственным недостатком является то, что это также удаляет, .поэтому вам нужно сначала извлечь расширение, а затем добавить его снова.
Вот что я только что добавил в статический класс StringExtensions ( http://github.com/Zoomicon/ClipFlair ) ClipFlair (проект Utils.Silverlight) на основе информации, собранной из ссылок на связанные вопросы о стеке, опубликованные Dour High Arch выше:
publicstaticstringReplaceInvalidFileNameChars(thisstring s, string replacement = "")
{
return Regex.Replace(s,
"[" + Regex.Escape(new String(System.IO.Path.GetInvalidPathChars())) + "]",
replacement, //can even use a replacement string of any length
RegexOptions.IgnoreCase);
//not using System.IO.Path.InvalidPathChars (deprecated insecure API)
}
privatevoidtextBoxFileName_KeyPress(object sender, KeyPressEventArgs e)
{
e.Handled = CheckFileNameSafeCharacters(e);
}
///<summary>/// This is a good function for making sure that a user who is naming a file uses proper characters///</summary>///<param name="e"></param>///<returns></returns>internalstaticboolCheckFileNameSafeCharacters(System.Windows.Forms.KeyPressEventArgs e)
{
if (e.KeyChar.Equals(24) ||
e.KeyChar.Equals(3) ||
e.KeyChar.Equals(22) ||
e.KeyChar.Equals(26) ||
e.KeyChar.Equals(25))//Control-X, C, V, Z and Yreturnfalse;
if (e.KeyChar.Equals('\b'))//backspacereturnfalse;
char[] charArray = Path.GetInvalidFileNameChars();
if (charArray.Contains(e.KeyChar))
returntrue;//Stop the character from being entered into the control since it is non-numericalelsereturnfalse;
}
<Extension()>
Public Function MakeSafeFileName(FileName As String) As String
Return FileName.Where(Function(x) Not IO.Path.GetInvalidFileNameChars.Contains(x)).ToArray
End Function
Это работает, потому что a stringпредставляет IEnumerableсобой charмассив, и есть stringстрока конструктора, которая принимает charмассив.
В своих старых проектах я нашел это решение, которое отлично работает более 2 лет. Я заменяю недопустимые символы на "!", А затем проверяю двойные !!, используйте свой собственный символ.
publicstringGetSafeFilename(string filename)
{
string res = string.Join("!", filename.Split(Path.GetInvalidFileNameChars()));
while (res.IndexOf("!!") >= 0)
res = res.Replace("!!", "!");
return res;
}
Многие ответы предлагают использовать, Path.GetInvalidFileNameChars()что мне кажется плохим решением. Я рекомендую вам использовать белый список вместо черного, потому что хакеры всегда найдут способ в конечном итоге обойти его.
Вот пример кода, который вы можете использовать:
string whitelist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.";
foreach (char c in filename)
{
if (!whitelist.Contains(c))
{
filename = filename.Replace(c, '-');
}
}
Ответы:
Ух, ненавижу, когда люди пытаются угадать, какие символы действительны. Помимо того, что они полностью непереносимы (всегда думают о Mono), в обоих предыдущих комментариях пропущено более 25 недопустимых символов.
'Clean just a filename Dim filename As String = "salmnas dlajhdla kjha;dmas'lkasn" For Each c In IO.Path.GetInvalidFileNameChars filename = filename.Replace(c, "") Next 'See also IO.Path.GetInvalidPathChars
источник
Чтобы удалить недопустимые символы:
static readonly char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); // Builds a string out of valid chars var validFilename = new string(filename.Where(ch => !invalidFileNameChars.Contains(ch)).ToArray());
Чтобы заменить недопустимые символы:
static readonly char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); // Builds a string out of valid chars and an _ for invalid ones var validFilename = new string(filename.Select(ch => invalidFileNameChars.Contains(ch) ? '_' : ch).ToArray());
Чтобы заменить недопустимые символы (и избежать потенциального конфликта имен, например Hell * vs Hell $):
static readonly IList<char> invalidFileNameChars = Path.GetInvalidFileNameChars(); // Builds a string out of valid chars and replaces invalid chars with a unique letter (Moves the Char into the letter range of unicode, starting at "A") var validFilename = new string(filename.Select(ch => invalidFileNameChars.Contains(ch) ? Convert.ToChar(invalidFileNameChars.IndexOf(ch) + 65) : ch).ToArray());
источник
Этот вопрос задавался много раз раньше, и, как уже неоднократно отмечалось,
IO.Path.GetInvalidFileNameChars
он неадекватен.Во-первых, есть много имен, таких как PRN и CON, которые зарезервированы и не допускаются для имен файлов. Есть другие имена, которые нельзя использовать только в корневой папке. Имена, оканчивающиеся на точку, также не допускаются.
Во-вторых, существует множество ограничений по длине. Полный список NTFS читайте здесь .
В-третьих, вы можете подключаться к файловым системам, которые имеют другие ограничения. Например, имена файлов ISO 9660 не могут начинаться с символа «-», но могут содержать его.
В-четвертых, что делать, если два процесса «произвольно» выбирают одно и то же имя?
В общем, использование имен файлов, сгенерированных извне, - плохая идея. Я предлагаю сгенерировать ваши собственные личные имена файлов и хранить внутри себя удобочитаемые имена.
источник
Я согласен с Грауэнвольфом и очень рекомендую
Path.GetInvalidFileNameChars()
Вот мой вклад в C #:
string file = @"38?/.\}[+=n a882 a.a*/|n^%$ ad#(-))"; Array.ForEach(Path.GetInvalidFileNameChars(), c => file = file.Replace(c.ToString(), String.Empty));
ps - это более загадочно, чем должно быть - я пытался быть кратким.
источник
Array.ForEach
вместо того , чтобы простоforeach
здесьPath.GetInvalidFileNameChars().Aggregate(file, (current, c) => current.Replace(c, '-'))
Вот моя версия:
static string GetSafeFileName(string name, char replace = '_') { char[] invalids = Path.GetInvalidFileNameChars(); return new string(name.Select(c => invalids.Contains(c) ? replace : c).ToArray()); }
Я не уверен, как рассчитывается результат GetInvalidFileNameChars, но «Get» предполагает, что это нетривиально, поэтому я кэширую результаты. Кроме того, при этом проходит входная строка только один раз, а не несколько раз, как в решениях выше, которые перебирают набор недопустимых символов, заменяя их в исходной строке по одному. Кроме того, мне нравятся решения на основе Where, но я предпочитаю заменять недопустимые символы вместо их удаления. Наконец, моя замена - это ровно один символ, чтобы избежать преобразования символов в строки, когда я перебираю строку.
Я говорю все это без профилирования - мне просто "показалось" приятным. :)
источник
new HashSet<char>(Path.GetInvalidFileNameChars())
чтобы избежать перечисления O (n) - микрооптимизация.Вот функция, которую я использую сейчас (спасибо jcollum за пример C #):
public static string MakeSafeFilename(string filename, char replaceChar) { foreach (char c in System.IO.Path.GetInvalidFileNameChars()) { filename = filename.Replace(c, replaceChar); } return filename; }
Я просто поместил это в класс «Помощники» для удобства.
источник
Если вы хотите быстро удалить все специальные символы, которые иногда более удобочитаемы для имен файлов, это отлично работает:
string myCrazyName = "q`w^e!r@t#y$u%i^o&p*a(s)d_f-g+h=j{k}l|z:x\"c<v>b?n[m]q\\w;e'r,t.y/u"; string safeName = Regex.Replace( myCrazyName, "\W", /*Matches any nonword character. Equivalent to '[^A-Za-z0-9_]'*/ "", RegexOptions.IgnoreCase); // safeName == "qwertyuiopasd_fghjklzxcvbnmqwertyu"
источник
\W
соответствует большему количеству не буквенно-цифровых ([^A-Za-z0-9_]
). Все символы Unicode "word" (русский 中文 ... и т. Д.) Также не будут заменены. Но это хорошо..
поэтому вам нужно сначала извлечь расширение, а затем добавить его снова.static class Utils { public static string MakeFileSystemSafe(this string s) { return new string(s.Where(IsFileSystemSafe).ToArray()); } public static bool IsFileSystemSafe(char c) { return !Path.GetInvalidFileNameChars().Contains(c); } }
источник
Почему бы не преобразовать строку в эквивалент Base64 следующим образом:
string UnsafeFileName = "salmnas dlajhdla kjha;dmas'lkasn"; string SafeFileName = Convert.ToBase64String(Encoding.UTF8.GetBytes(UnsafeFileName));
Если вы хотите преобразовать его обратно, чтобы вы могли его прочитать:
Я использовал это для сохранения файлов PNG с уникальным именем из случайного описания.
источник
Вот что я только что добавил в статический класс StringExtensions ( http://github.com/Zoomicon/ClipFlair ) ClipFlair (проект Utils.Silverlight) на основе информации, собранной из ссылок на связанные вопросы о стеке, опубликованные Dour High Arch выше:
public static string ReplaceInvalidFileNameChars(this string s, string replacement = "") { return Regex.Replace(s, "[" + Regex.Escape(new String(System.IO.Path.GetInvalidPathChars())) + "]", replacement, //can even use a replacement string of any length RegexOptions.IgnoreCase); //not using System.IO.Path.InvalidPathChars (deprecated insecure API) }
источник
private void textBoxFileName_KeyPress(object sender, KeyPressEventArgs e) { e.Handled = CheckFileNameSafeCharacters(e); } /// <summary> /// This is a good function for making sure that a user who is naming a file uses proper characters /// </summary> /// <param name="e"></param> /// <returns></returns> internal static bool CheckFileNameSafeCharacters(System.Windows.Forms.KeyPressEventArgs e) { if (e.KeyChar.Equals(24) || e.KeyChar.Equals(3) || e.KeyChar.Equals(22) || e.KeyChar.Equals(26) || e.KeyChar.Equals(25))//Control-X, C, V, Z and Y return false; if (e.KeyChar.Equals('\b'))//backspace return false; char[] charArray = Path.GetInvalidFileNameChars(); if (charArray.Contains(e.KeyChar)) return true;//Stop the character from being entered into the control since it is non-numerical else return false; }
источник
Я считаю, что это быстро и легко понять:
<Extension()> Public Function MakeSafeFileName(FileName As String) As String Return FileName.Where(Function(x) Not IO.Path.GetInvalidFileNameChars.Contains(x)).ToArray End Function
Это работает, потому что a
string
представляетIEnumerable
собойchar
массив, и естьstring
строка конструктора, которая принимаетchar
массив.источник
В своих старых проектах я нашел это решение, которое отлично работает более 2 лет. Я заменяю недопустимые символы на "!", А затем проверяю двойные !!, используйте свой собственный символ.
public string GetSafeFilename(string filename) { string res = string.Join("!", filename.Split(Path.GetInvalidFileNameChars())); while (res.IndexOf("!!") >= 0) res = res.Replace("!!", "!"); return res; }
источник
Многие ответы предлагают использовать,
Path.GetInvalidFileNameChars()
что мне кажется плохим решением. Я рекомендую вам использовать белый список вместо черного, потому что хакеры всегда найдут способ в конечном итоге обойти его.Вот пример кода, который вы можете использовать:
string whitelist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ."; foreach (char c in filename) { if (!whitelist.Contains(c)) { filename = filename.Replace(c, '-'); } }
источник