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

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

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.


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.


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


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


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"

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

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.


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

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

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.


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

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, '-');
        }
    }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.