Czy istnieje sposób na bezpieczną ścieżkę do pliku ciągów w języku C #?

Odpowiedzi:

172

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
Jonathan Allen
źródło
83
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:

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());
Wiewiórka
źródło
34

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.

Dour High Arch
źródło
13
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()

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.

Aaron Wagner
źródło
3
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.
Jonathan Allen,
13

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. :)

csells
źródło
1
Możesz zrobić, new HashSet<char>(Path.GetInvalidFileNameChars())aby uniknąć wyliczenia O (n) - mikro-optymalizacja.
TrueWill
12

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”.

sidewinderguy
źródło
7

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"
Keith
źródło
1
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.
awe
5
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);
    }
}
Ronnie Overby
źródło
5

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ć:

UnsafeFileName = Encoding.UTF8.GetString(Convert.FromBase64String(SafeFileName));

Użyłem tego do zapisania plików PNG o unikalnej nazwie z losowego opisu.

Bart Vanseer
źródło
5

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)
}
George Birbilis
źródło
2
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;            
}
ecklerpa
źródło
1

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.

cjbarth
źródło
1

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;
    }
Roni Tovi
źródło
0

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, '-');
        }
    }
AnonBird
źródło