Gibt es eine Möglichkeit, den Dateipfad von Zeichenfolgen in c # sicher zu machen?

92

Mein Programm nimmt beliebige Zeichenfolgen aus dem Internet und verwendet sie für Dateinamen. Gibt es eine einfache Möglichkeit, die fehlerhaften Zeichen aus diesen Zeichenfolgen zu entfernen, oder muss ich dafür eine benutzerdefinierte Funktion schreiben?

Martin Doms
quelle
Mögliches Duplikat von Safe / Allowed Dateiname Cleaner für .NET
N8allan

Antworten:

171

Ugh, ich hasse es, wenn Leute versuchen zu erraten, welche Zeichen gültig sind. Beide früheren Kommentare waren nicht vollständig portierbar (ich dachte immer an Mono), aber es fehlten mehr als 25 ungültige Zeichen.

'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
quelle
83
Die C # -Version: foreach (var c in Path.GetInvalidFileNameChars ()) {fileName = fileName.Replace (c, '-'); }
JCollum
8
Wie würde diese Lösung mit Namenskonflikten umgehen? Es scheint, dass mehr als eine Zeichenfolge mit einem einzelnen Dateinamen übereinstimmen kann (z. B. "Hell?" Und "Hell *"). Wenn Sie in Ordnung sind, nur beleidigende Zeichen zu entfernen, ist das in Ordnung. Andernfalls müssen Sie vorsichtig sein, um mit Namenskonflikten umzugehen.
Stefano Ricciardi
2
Was ist mit den Grenzen der Länge des Namens (und des Pfads) des Dateisystems? Was ist mit reservierten Dateinamen (PRN CON)? Wenn Sie die Daten und den ursprünglichen Namen speichern müssen, können Sie 2 Dateien mit Guid-Namen verwenden: guid.txt und guid.dat
Jack
6
Ein Liner, zum Spaß result = Path.GetInvalidFileNameChars (). Aggregate (result, (current, c) => current.Replace (c, '-'));
Paul Knopf
1
@PaulKnopf, sind Sie sicher, dass JetBrain kein Copyright für diesen Code hat;)
Marcus
36

So entfernen Sie ungültige Zeichen:

static readonly char[] invalidFileNameChars = Path.GetInvalidFileNameChars();

// Builds a string out of valid chars
var validFilename = new string(filename.Where(ch => !invalidFileNameChars.Contains(ch)).ToArray());

So ersetzen Sie ungültige Zeichen:

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

So ersetzen Sie ungültige Zeichen (und vermeiden potenzielle Namenskonflikte wie 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());
Eichhörnchen
quelle
33

Diese Frage wurde gefragt , viele Male vor und, wie oft darauf hingewiesen , vor, IO.Path.GetInvalidFileNameCharsnicht ausreichend ist.

Erstens gibt es viele Namen wie PRN und CON, die reserviert und für Dateinamen nicht zulässig sind. Es gibt andere Namen, die nicht nur im Stammordner zulässig sind. Namen, die in einem Punkt enden, sind ebenfalls nicht zulässig.

Zweitens gibt es eine Vielzahl von Längenbeschränkungen. Lesen Sie die vollständige Liste für NTFS hier .

Drittens können Sie an Dateisysteme mit anderen Einschränkungen anhängen. Beispielsweise können ISO 9660-Dateinamen nicht mit "-" beginnen, sondern diese enthalten.

Viertens, was machen Sie, wenn zwei Prozesse "willkürlich" denselben Namen wählen?

Im Allgemeinen ist die Verwendung von extern generierten Namen für Dateinamen eine schlechte Idee. Ich empfehle, eigene private Dateinamen zu generieren und von Menschen lesbare Namen intern zu speichern.

Dour High Arch
quelle
13
Obwohl Sie technisch korrekt sind, ist GetInvalidFileNameChars für mehr als 80% der Situationen geeignet, in denen Sie es verwenden würden. Daher ist es eine gute Antwort. Ihre Antwort wäre meiner Meinung nach angemessener als Kommentar zu der akzeptierten Antwort gewesen.
CubanX
4
Ich stimme DourHighArch zu. Speichern Sie die Datei intern als Richtlinie und verweisen Sie auf den "Anzeigenamen", der in einer Datenbank gespeichert ist. Lassen Sie Benutzer Ihre Pfade auf der Website nicht kontrollieren, da sie sonst versuchen, Ihre web.config zu stehlen. Wenn Sie das Umschreiben von URLs einbinden, um es sauber zu machen, funktioniert es nur für übereinstimmende freundliche URLs in der Datenbank.
RTPHarry
22

Ich stimme Grauenwolf zu und kann das nur empfehlen Path.GetInvalidFileNameChars()

Hier ist mein C # -Beitrag:

string file = @"38?/.\}[+=n a882 a.a*/|n^%$ ad#(-))";
Array.ForEach(Path.GetInvalidFileNameChars(), 
      c => file = file.Replace(c.ToString(), String.Empty));

ps - das ist kryptischer als es sein sollte - ich habe versucht, prägnant zu sein.

Aaron Wagner
quelle
3
Warum in Array.ForEachforeach
aller
9
Wenn Sie noch prägnanter / kryptischer sein wollten:Path.GetInvalidFileNameChars().Aggregate(file, (current, c) => current.Replace(c, '-'))
Michael Petito
@ BlueRaja-DannyPflughoeft Weil du es langsamer machen willst?
Jonathan Allen
@ Johnathan Allen, warum denkst du, dass foreach schneller ist als Array.ForEach?
Ryan Buddicom
5
@rbuddicom Array.ForEach nimmt einen Delegaten auf, was bedeutet, dass eine Funktion aufgerufen werden muss, die nicht inline sein kann. Bei kurzen Zeichenfolgen kann es vorkommen, dass Sie mehr Zeit für den Funktionsaufruf als für die eigentliche Logik aufwenden. .NET Core sucht nach Möglichkeiten, Anrufe zu "de-virtualisieren" und so den Overhead zu reduzieren.
Jonathan Allen
13

Hier ist meine Version:

static string GetSafeFileName(string name, char replace = '_') {
  char[] invalids = Path.GetInvalidFileNameChars();
  return new string(name.Select(c => invalids.Contains(c) ? replace : c).ToArray());
}

Ich bin nicht sicher, wie das Ergebnis von GetInvalidFileNameChars berechnet wird, aber das "Get" legt nahe, dass es nicht trivial ist, also speichere ich die Ergebnisse zwischen. Außerdem wird die Eingabezeichenfolge nur einmal anstatt mehrmals durchlaufen, wie bei den obigen Lösungen, die über den Satz ungültiger Zeichen iterieren und diese einzeln in der Quellzeichenfolge ersetzen. Ich mag auch die Where-basierten Lösungen, aber ich ziehe es vor, ungültige Zeichen zu ersetzen, anstatt sie zu entfernen. Schließlich ist mein Ersatz genau ein Zeichen, um zu vermeiden, dass Zeichen in Zeichenfolgen konvertiert werden, wenn ich über die Zeichenfolge iteriere.

Ich sage alles, ohne das Profiling zu machen - dieses "fühlte" sich einfach gut für mich an. :)

csells
quelle
1
Sie können new HashSet<char>(Path.GetInvalidFileNameChars())O (n) -Aufzählung vermeiden - Mikrooptimierung.
TrueWill
11

Hier ist die Funktion, die ich jetzt verwende (danke jcollum für das C # -Beispiel):

public static string MakeSafeFilename(string filename, char replaceChar)
{
    foreach (char c in System.IO.Path.GetInvalidFileNameChars())
    {
        filename = filename.Replace(c, replaceChar);
    }
    return filename;
}

Ich habe dies der Einfachheit halber in eine "Helfer" -Klasse eingeordnet.

Sidewinderguy
quelle
7

Wenn Sie schnell alle Sonderzeichen entfernen möchten, die manchmal für Dateinamen besser lesbar sind, funktioniert dies gut:

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
quelle
1
Entspricht tatsächlich \Wmehr als Nicht-Alphanumerik ( [^A-Za-z0-9_]). Alle Unicode-Wortzeichen (русский 中文 ... usw.) werden ebenfalls nicht ersetzt. Aber das ist eine gute Sache.
Ishmael
Der einzige Nachteil ist, dass dies auch entfernt wird, .sodass Sie die Erweiterung zuerst extrahieren und anschließend erneut hinzufügen müssen.
Ehrfurcht
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
quelle
5

Warum konvertieren Sie den String nicht wie folgt in ein Base64-Äquivalent:

string UnsafeFileName = "salmnas dlajhdla kjha;dmas'lkasn";
string SafeFileName = Convert.ToBase64String(Encoding.UTF8.GetBytes(UnsafeFileName));

Wenn Sie es zurückkonvertieren möchten, damit Sie es lesen können:

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

Ich habe dies verwendet, um PNG-Dateien mit einem eindeutigen Namen aus einer zufälligen Beschreibung zu speichern.

Bart Vanseer
quelle
5

Folgendes habe ich gerade zu der statischen Klasse StringExtensions (Utils.Silverlight-Projekt) von ClipFlair ( http://github.com/Zoomicon/ClipFlair ) hinzugefügt.

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
quelle
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
quelle
1

Ich finde es schnell und einfach zu verstehen:

<Extension()>
Public Function MakeSafeFileName(FileName As String) As String
    Return FileName.Where(Function(x) Not IO.Path.GetInvalidFileNameChars.Contains(x)).ToArray
End Function

Dies funktioniert , weil eine stringist IEnumerableals charArray und es gibt einen stringKonstruktor String, der eine nimmt charArray.

cjbarth
quelle
1

Aus meinen älteren Projekten habe ich diese Lösung gefunden, die seit über 2 Jahren einwandfrei funktioniert. Ich ersetze illegale Zeichen durch "!" Und überprüfe dann, ob es sich um doppelte Zeichen handelt. Verwenden Sie Ihr eigenes Zeichen.

    public string GetSafeFilename(string filename)
    {
        string res = string.Join("!", filename.Split(Path.GetInvalidFileNameChars()));

        while (res.IndexOf("!!") >= 0)
            res = res.Replace("!!", "!");

        return res;
    }
Roni Tovi
quelle
0

Viele Antworten schlagen vor, dies zu verwenden, Path.GetInvalidFileNameChars()was mir als schlechte Lösung erscheint. Ich empfehle Ihnen, Whitelisting anstelle von Blacklisting zu verwenden, da Hacker immer einen Weg finden, es irgendwann zu umgehen.

Hier ist ein Beispiel für Code, den Sie verwenden können:

    string whitelist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.";
    foreach (char c in filename)
    {
        if (!whitelist.Contains(c))
        {
            filename = filename.Replace(c, '-');
        }
    }
AnonBird
quelle