C'è un modo per rendere sicuro il percorso del file delle stringhe in c #?


Risposte:


172

Uffa, odio quando le persone cercano di indovinare quali caratteri sono validi. Oltre ad essere completamente non portabili (pensando sempre a Mono), entrambi i commenti precedenti mancavano di più di 25 caratteri non validi.

'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
La versione C #: foreach (var c in Path.GetInvalidFileNameChars ()) {fileName = fileName.Replace (c, '-'); }
jcollum

8
In che modo questa soluzione gestirà i conflitti di nome? Sembra che più di una stringa possa corrispondere a un singolo nome di file ("Hell?" E "Hell *" per esempio). Se stai bene rimuovendo solo i caratteri offensivi, allora va bene; altrimenti devi stare attento a gestire i conflitti di nome.
Stefano Ricciardi

2
che dire dei limiti della lunghezza del nome (e del percorso) del filesystem? che dire dei nomi di file riservati (PRN CON)? Se è necessario memorizzare i dati e il nome originale, è possibile utilizzare 2 file con nomi Guid: guid.txt e guid.dat
Jack

6
Una riga, per divertimento risultato = Path.GetInvalidFileNameChars (). Aggregate (result, (current, c) => current.Replace (c, '-'));
Paul Knopf

1
@PaulKnopf, sei sicuro che JetBrain non abbia il copyright di quel codice;)
Marcus

36

Per rimuovere i caratteri non validi:

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

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

Per sostituire i caratteri non validi:

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

Per sostituire i caratteri non validi (ed evitare potenziali conflitti di nome come 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());

33

Questa domanda è stata posta molte volte in passato e, come sottolineato molte volte, IO.Path.GetInvalidFileNameCharsnon è adeguata.

Innanzitutto, ci sono molti nomi come PRN e CON che sono riservati e non consentiti per i nomi di file. Ci sono altri nomi non consentiti solo nella cartella principale. Anche i nomi che terminano con un punto non sono consentiti.

In secondo luogo, ci sono una serie di limitazioni di lunghezza. Leggi l'elenco completo per NTFS qui .

Terzo, puoi collegarti a filesystem che hanno altre limitazioni. Ad esempio, i nomi di file ISO 9660 non possono iniziare con "-" ma possono contenerlo.

Quarto, cosa fare se due processi scelgono "arbitrariamente" lo stesso nome?

In generale, l'utilizzo di nomi generati esternamente per i nomi di file è una cattiva idea. Suggerisco di generare i propri nomi di file privati ​​e di memorizzare internamente i nomi leggibili dall'uomo.


13
Sebbene tu sia tecnicamente accurato, GetInvalidFileNameChars è buono per l'80% + delle situazioni in cui lo useresti, quindi è una buona risposta. La tua risposta sarebbe stata più appropriata come commento alla risposta accettata credo.
CubanX

4
Sono d'accordo con DourHighArch. Salvare il file internamente come GUID, riferendolo al "nome descrittivo" memorizzato in un database. Non lasciare che gli utenti controllino i tuoi percorsi sul sito web o cercheranno di rubare il tuo web.config. Se si incorpora la riscrittura dell'URL per renderlo pulito, funzionerà solo per gli URL amichevoli corrispondenti nel database.
rtpHarry

22

Sono d'accordo con Grauenwolf e consiglio vivamente il Path.GetInvalidFileNameChars()

Ecco il mio contributo in C #:

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

ps - questo è più criptico di quanto dovrebbe essere - stavo cercando di essere conciso.


3
Perché nel mondo Array.ForEachforeach
useresti

9
Se volevi essere ancora più conciso / criptico:Path.GetInvalidFileNameChars().Aggregate(file, (current, c) => current.Replace(c, '-'))
Michael Petito

@ BlueRaja-DannyPflughoeft Perché vuoi renderlo più lento?
Jonathan Allen

@Johnathan Allen, cosa ti fa pensare che foreach sia più veloce di Array.ForEach?
Ryan Buddicom

5
@rbuddicom Array.ForEach accetta un delegato, il che significa che deve richiamare una funzione che non può essere inline. Per stringhe brevi, potresti finire per dedicare più tempo al sovraccarico delle chiamate di funzione rispetto alla logica effettiva. .NET Core sta cercando modi per "de-virtualizzare" le chiamate, riducendo il sovraccarico.
Jonathan Allen

13

Ecco la mia versione:

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

Non sono sicuro di come viene calcolato il risultato di GetInvalidFileNameChars, ma "Get" suggerisce che non è banale, quindi memorizzo i risultati nella cache. Inoltre, questo attraversa la stringa di input solo una volta anziché più volte, come le soluzioni precedenti che ripetono l'insieme di caratteri non validi, sostituendoli nella stringa di origine uno alla volta. Inoltre, mi piacciono le soluzioni basate su dove, ma preferisco sostituire i caratteri non validi invece di rimuoverli. Infine, la mia sostituzione è esattamente un carattere per evitare di convertire i caratteri in stringhe mentre iterare sulla stringa.

Dico tutto ciò senza fare il profilo - questo mi "è sembrato" carino. :)


1
Si potrebbe fare new HashSet<char>(Path.GetInvalidFileNameChars())per evitare l'enumerazione O (n) - microottimizzazione.
TrueWill

12

Ecco la funzione che sto usando ora (grazie jcollum per l'esempio C #):

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

L'ho inserito in una classe "Aiutanti" per comodità.


7

Se vuoi rimuovere rapidamente tutti i caratteri speciali che a volte sono più leggibili dall'utente per i nomi dei file, questo funziona bene:

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
effettivamente \Wcorrisponde a più di non alfa-numerici ( [^A-Za-z0-9_]). Neanche tutti i caratteri "parola" Unicode (русский 中文 ..., ecc.) Verranno sostituiti. Ma questa è una buona cosa.
Ishmael

L'unico svantaggio è che anche questo rimuove, .quindi devi prima estrarre l'estensione e aggiungerla di nuovo dopo.
timore reverenziale

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

Perché non convertire la stringa in un equivalente Base64 come questo:

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

Se vuoi riconvertirlo in modo da poterlo leggere:

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

L'ho usato per salvare file PNG con un nome univoco da una descrizione casuale.


5

Ecco cosa ho appena aggiunto alla classe statica StringExtensions di ClipFlair ( http://github.com/Zoomicon/ClipFlair ) (progetto Utils.Silverlight), in base alle informazioni raccolte dai collegamenti alle domande di stackoverflow correlate pubblicate da Dour High Arch sopra:

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

Trovo che l'utilizzo di questo sia veloce e facile da capire:

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

Questo funziona perché a stringè IEnumerablecome un chararray e c'è una stringstringa di costruzione che accetta un chararray.


1

Dai miei vecchi progetti, ho trovato questa soluzione, che funziona perfettamente da oltre 2 anni. Sostituisco i caratteri illegali con "!", Quindi controllo i doppi !!, usa il tuo carattere.

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

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

        return res;
    }

0

Molte risposte suggeriscono di utilizzare il Path.GetInvalidFileNameChars()che mi sembra una cattiva soluzione. Ti incoraggio a utilizzare la whitelist anziché la blacklist perché gli hacker troveranno sempre un modo per aggirarla.

Ecco un esempio di codice che potresti usare:

    string whitelist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.";
    foreach (char c in filename)
    {
        if (!whitelist.Contains(c))
        {
            filename = filename.Replace(c, '-');
        }
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.