Czy istnieje sposób na bezpieczną ścieżkę do pliku ciągów w języku C #?
94
Mój program pobierze z internetu dowolne ciągi znaków i użyje ich jako nazw plików. Czy istnieje prosty sposób na usunięcie złych znaków z tych ciągów, czy też muszę napisać do tego funkcję niestandardową?
Ugh, nienawidzę, kiedy ludzie próbują odgadnąć, które znaki są prawidłowe. Oprócz tego, że są całkowicie nieprzenośne (zawsze myśląc o Mono), oba wcześniejsze komentarze pominęły więcej 25 nieprawidłowych znaków.
'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
Wersja C #: foreach (var c in Path.GetInvalidFileNameChars ()) {fileName = fileName.Replace (c, '-'); }
jcollum
8
Jak to rozwiązanie radzi sobie z konfliktami nazw? Wygląda na to, że więcej niż jeden ciąg może pasować do jednej nazwy pliku (na przykład „Hell?” I „Hell *”). Jeśli możesz usunąć tylko obraźliwe znaki, to dobrze; w przeciwnym razie musisz uważać, aby radzić sobie z konfliktami nazw.
Stefano Ricciardi
2
A co z ograniczeniami długości nazwy (i ścieżki) w układzie plików? co z zastrzeżonymi nazwami plików (PRN CON)? Jeśli chcesz przechowywać dane i oryginalną nazwę, możesz użyć 2 plików z nazwami Guid: guid.txt i guid.dat
Jack
7
Jedna linijka, dla zabawy result = Path.GetInvalidFileNameChars (). Aggregate (result, (current, c) => current.Replace (c, '-'));
Paul Knopf,
1
@PaulKnopf, czy jesteś pewien, że JetBrain nie ma praw autorskich do tego kodu;)
Marcus
37
Aby usunąć nieprawidłowe znaki:
staticreadonlychar[] invalidFileNameChars = Path.GetInvalidFileNameChars();
// Builds a string out of valid charsvar validFilename = newstring(filename.Where(ch => !invalidFileNameChars.Contains(ch)).ToArray());
Aby zamienić nieprawidłowe znaki:
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());
Aby zamienić nieprawidłowe znaki (i uniknąć potencjalnego konfliktu nazw, takiego jak 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());
To pytanie było zadawane wiele razy przed i, jak wskazano wiele razy wcześniej, IO.Path.GetInvalidFileNameCharsnie jest wystarczające.
Po pierwsze, istnieje wiele nazw, takich jak PRN i CON, które są zarezerwowane i niedozwolone dla nazw plików. Istnieją inne nazwy, które nie są dozwolone tylko w folderze głównym. Nazwy kończące się kropką również są niedozwolone.
Po drugie, istnieje wiele ograniczeń długości. Przeczytaj pełną listę NTFS tutaj .
Po trzecie, możesz dołączyć do systemów plików, które mają inne ograniczenia. Na przykład nazwy plików ISO 9660 nie mogą zaczynać się od „-”, ale mogą go zawierać.
Po czwarte, co zrobisz, jeśli dwa procesy „arbitralnie” wybiorą tę samą nazwę?
Ogólnie rzecz biorąc, używanie nazw generowanych zewnętrznie dla nazw plików jest złym pomysłem. Sugeruję wygenerowanie własnych prywatnych nazw plików i wewnętrzne przechowywanie nazw czytelnych dla człowieka.
Chociaż jesteś technicznie dokładny, GetInvalidFileNameChars jest dobry dla 80% + sytuacji, w których będziesz go używać, dlatego jest to dobra odpowiedź. Twoja odpowiedź byłaby bardziej odpowiednia jako komentarz do zaakceptowanej odpowiedzi.
CubanX
4
Zgadzam się z DourHighArch. Zapisz plik wewnętrznie jako guid, odwołując się do „przyjaznej nazwy”, która jest przechowywana w bazie danych. Nie pozwól użytkownikom kontrolować twoich ścieżek w witrynie, ponieważ będą próbowali ukraść twój plik web.config. Jeśli włączysz przepisywanie adresów URL, aby uczynić je czystym, będzie działać tylko dla dopasowanych przyjaznych adresów URL w bazie danych.
rtpHarry
22
Zgadzam się z Grauenwolf i gorąco polecam Path.GetInvalidFileNameChars()
Dlaczego na świecie miałbyś używać Array.ForEachzamiast tylko foreachtutaj
BlueRaja - Danny Pflughoeft
9
Jeśli chcesz być jeszcze bardziej zwięzły / tajemniczy:Path.GetInvalidFileNameChars().Aggregate(file, (current, c) => current.Replace(c, '-'))
Michael Petito
@ BlueRaja-DannyPflughoeft Ponieważ chcesz spowolnić?
Jonathan Allen
@Johnathan Allen, dlaczego uważasz, że foreach jest szybsze niż Array.ForEach?
Ryan Buddicom
5
@rbuddicom Array.ForEach przyjmuje delegata, co oznacza, że musi wywołać funkcję, której nie można wstawić. W przypadku krótkich łańcuchów możesz spędzić więcej czasu na narzucie wywołań funkcji niż na rzeczywistą logikę. .NET Core poszukuje sposobów „de-wirtualizacji” połączeń, zmniejszając narzut.
Nie jestem pewien, jak obliczany jest wynik GetInvalidFileNameChars, ale „Get” sugeruje, że nie jest to trywialne, więc buforuję wyniki. Co więcej, to przechodzi przez ciąg wejściowy tylko raz, a nie wiele razy, tak jak powyższe rozwiązania, które iterują zestaw nieprawidłowych znaków, zastępując je pojedynczo w ciągu źródłowym. Podoba mi się również rozwiązania oparte na Gdzie, ale wolę zastępować nieprawidłowe znaki zamiast je usuwać. Wreszcie, moja zamiana to dokładnie jeden znak, aby uniknąć konwersji znaków na ciągi podczas iteracji po ciągu.
Mówię to wszystko bez profilowania - ten po prostu „poczuł się” miło. :)
w rzeczywistości \Wdopasowuje więcej niż nie-alfanumeryczne ( [^A-Za-z0-9_]). Wszystkie „słowne” znaki Unicode (русский 中文 ... itp.) Również nie zostaną zastąpione. Ale to dobra rzecz.
Ishmael
Jedynym minusem jest to, że to również usuwa, .więc musisz najpierw wyodrębnić rozszerzenie, a następnie dodać je ponownie.
Oto, co właśnie dodałem do klasy statycznej StringExtensions ( http://github.com/Zoomicon/ClipFlair ) ClipFlair (projekt Utils.Silverlight), w oparciu o informacje zebrane z linków do powiązanych pytań dotyczących stackoverflow opublikowanych przez Dour High Arch powyżej:
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;
}
Uważam, że używanie tego jest szybkie i łatwe do zrozumienia:
<Extension()>
Public Function MakeSafeFileName(FileName As String) As String
Return FileName.Where(Function(x) Not IO.Path.GetInvalidFileNameChars.Contains(x)).ToArray
End Function
To działa, ponieważ stringjest IEnumerablew postaci chartablicy i istnieje stringciąg konstruktor, że trwa chartablicy.
Z moich starszych projektów odnalazłem to rozwiązanie, które od ponad 2 lat działa doskonale. Zamieniam niedozwolone znaki na „!”, A następnie sprawdzam, czy nie ma podwójnych znaków !!, użyj własnego znaku.
publicstringGetSafeFilename(string filename)
{
string res = string.Join("!", filename.Split(Path.GetInvalidFileNameChars()));
while (res.IndexOf("!!") >= 0)
res = res.Replace("!!", "!");
return res;
}
Wiele odpowiedzi sugeruje użycie, Path.GetInvalidFileNameChars()co wydaje mi się złym rozwiązaniem. Zachęcam do korzystania z białej listy zamiast z czarnej listy, ponieważ hakerzy zawsze znajdą sposób na obejście tego.
Oto przykład kodu, którego możesz użyć:
string whitelist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.";
foreach (char c in filename)
{
if (!whitelist.Contains(c))
{
filename = filename.Replace(c, '-');
}
}
Odpowiedzi:
Ugh, nienawidzę, kiedy ludzie próbują odgadnąć, które znaki są prawidłowe. Oprócz tego, że są całkowicie nieprzenośne (zawsze myśląc o Mono), oba wcześniejsze komentarze pominęły więcej 25 nieprawidłowych znaków.
'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
źródło
Aby usunąć nieprawidłowe znaki:
static readonly char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); // Builds a string out of valid chars var validFilename = new string(filename.Where(ch => !invalidFileNameChars.Contains(ch)).ToArray());
Aby zamienić nieprawidłowe znaki:
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());
Aby zamienić nieprawidłowe znaki (i uniknąć potencjalnego konfliktu nazw, takiego jak 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());
źródło
To pytanie było zadawane wiele razy przed i, jak wskazano wiele razy wcześniej,
IO.Path.GetInvalidFileNameChars
nie jest wystarczające.Po pierwsze, istnieje wiele nazw, takich jak PRN i CON, które są zarezerwowane i niedozwolone dla nazw plików. Istnieją inne nazwy, które nie są dozwolone tylko w folderze głównym. Nazwy kończące się kropką również są niedozwolone.
Po drugie, istnieje wiele ograniczeń długości. Przeczytaj pełną listę NTFS tutaj .
Po trzecie, możesz dołączyć do systemów plików, które mają inne ograniczenia. Na przykład nazwy plików ISO 9660 nie mogą zaczynać się od „-”, ale mogą go zawierać.
Po czwarte, co zrobisz, jeśli dwa procesy „arbitralnie” wybiorą tę samą nazwę?
Ogólnie rzecz biorąc, używanie nazw generowanych zewnętrznie dla nazw plików jest złym pomysłem. Sugeruję wygenerowanie własnych prywatnych nazw plików i wewnętrzne przechowywanie nazw czytelnych dla człowieka.
źródło
Zgadzam się z Grauenwolf i gorąco polecam
Path.GetInvalidFileNameChars()
Oto mój wkład w C #:
string file = @"38?/.\}[+=n a882 a.a*/|n^%$ ad#(-))"; Array.ForEach(Path.GetInvalidFileNameChars(), c => file = file.Replace(c.ToString(), String.Empty));
ps - to jest bardziej tajemnicze niż powinno - starałem się być zwięzły.
źródło
Array.ForEach
zamiast tylkoforeach
tutajPath.GetInvalidFileNameChars().Aggregate(file, (current, c) => current.Replace(c, '-'))
Oto moja wersja:
static string GetSafeFileName(string name, char replace = '_') { char[] invalids = Path.GetInvalidFileNameChars(); return new string(name.Select(c => invalids.Contains(c) ? replace : c).ToArray()); }
Nie jestem pewien, jak obliczany jest wynik GetInvalidFileNameChars, ale „Get” sugeruje, że nie jest to trywialne, więc buforuję wyniki. Co więcej, to przechodzi przez ciąg wejściowy tylko raz, a nie wiele razy, tak jak powyższe rozwiązania, które iterują zestaw nieprawidłowych znaków, zastępując je pojedynczo w ciągu źródłowym. Podoba mi się również rozwiązania oparte na Gdzie, ale wolę zastępować nieprawidłowe znaki zamiast je usuwać. Wreszcie, moja zamiana to dokładnie jeden znak, aby uniknąć konwersji znaków na ciągi podczas iteracji po ciągu.
Mówię to wszystko bez profilowania - ten po prostu „poczuł się” miło. :)
źródło
new HashSet<char>(Path.GetInvalidFileNameChars())
aby uniknąć wyliczenia O (n) - mikro-optymalizacja.Oto funkcja, której teraz używam (dzięki jcollum za przykład w C #):
public static string MakeSafeFilename(string filename, char replaceChar) { foreach (char c in System.IO.Path.GetInvalidFileNameChars()) { filename = filename.Replace(c, replaceChar); } return filename; }
Dla wygody umieściłem to w klasie „Pomocnicy”.
źródło
Jeśli chcesz szybko usunąć wszystkie znaki specjalne, które są czasami bardziej czytelne dla użytkownika w nazwach plików, działa to ładnie:
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"
źródło
\W
dopasowuje więcej niż nie-alfanumeryczne ([^A-Za-z0-9_]
). Wszystkie „słowne” znaki Unicode (русский 中文 ... itp.) Również nie zostaną zastąpione. Ale to dobra rzecz..
więc musisz najpierw wyodrębnić rozszerzenie, a następnie dodać je ponownie.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); } }
źródło
Dlaczego nie przekonwertować ciągu na odpowiednik Base64 w następujący sposób:
string UnsafeFileName = "salmnas dlajhdla kjha;dmas'lkasn"; string SafeFileName = Convert.ToBase64String(Encoding.UTF8.GetBytes(UnsafeFileName));
Jeśli chcesz go przekonwertować, aby móc go przeczytać:
Użyłem tego do zapisania plików PNG o unikalnej nazwie z losowego opisu.
źródło
Oto, co właśnie dodałem do klasy statycznej StringExtensions ( http://github.com/Zoomicon/ClipFlair ) ClipFlair (projekt Utils.Silverlight), w oparciu o informacje zebrane z linków do powiązanych pytań dotyczących stackoverflow opublikowanych przez Dour High Arch powyżej:
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) }
źródło
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; }
źródło
Uważam, że używanie tego jest szybkie i łatwe do zrozumienia:
<Extension()> Public Function MakeSafeFileName(FileName As String) As String Return FileName.Where(Function(x) Not IO.Path.GetInvalidFileNameChars.Contains(x)).ToArray End Function
To działa, ponieważ
string
jestIEnumerable
w postacichar
tablicy i istniejestring
ciąg konstruktor, że trwachar
tablicy.źródło
Z moich starszych projektów odnalazłem to rozwiązanie, które od ponad 2 lat działa doskonale. Zamieniam niedozwolone znaki na „!”, A następnie sprawdzam, czy nie ma podwójnych znaków !!, użyj własnego znaku.
public string GetSafeFilename(string filename) { string res = string.Join("!", filename.Split(Path.GetInvalidFileNameChars())); while (res.IndexOf("!!") >= 0) res = res.Replace("!!", "!"); return res; }
źródło
Wiele odpowiedzi sugeruje użycie,
Path.GetInvalidFileNameChars()
co wydaje mi się złym rozwiązaniem. Zachęcam do korzystania z białej listy zamiast z czarnej listy, ponieważ hakerzy zawsze znajdą sposób na obejście tego.Oto przykład kodu, którego możesz użyć:
string whitelist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ."; foreach (char c in filename) { if (!whitelist.Contains(c)) { filename = filename.Replace(c, '-'); } }
źródło