¿Hay alguna forma de hacer que la ruta de archivo de cadenas sea segura en c #?

Respuestas:

172

Uf, odio cuando la gente trata de adivinar qué caracteres son válidos. Además de ser completamente no portátil (siempre pensando en Mono), los dos comentarios anteriores perdieron más 25 caracteres no válidos.

'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
fuente
83
La versión de C #: foreach (var c en Path.GetInvalidFileNameChars ()) {fileName = fileName.Replace (c, '-'); }
jcollum
8
¿Cómo manejaría esta solución los conflictos de nombres? Parece que más de una cadena puede coincidir con un solo nombre de archivo ("¿Infierno?" E "Infierno *", por ejemplo). Si está bien, solo elimina los caracteres ofensivos, entonces está bien; de lo contrario, debe tener cuidado al manejar los conflictos de nombres.
Stefano Ricciardi
2
¿Qué pasa con los límites de longitud de nombre (y ruta) del sistema de archivos? ¿Qué pasa con los nombres de archivo reservados (PRN CON)? Si necesita almacenar los datos y el nombre original, puede usar 2 archivos con nombres de Guid: guid.txt y guid.dat
Jack
6
Un trazador de líneas, por diversión result = Path.GetInvalidFileNameChars (). Aggregate (result, (current, c) => current.Replace (c, '-'));
Paul Knopf
1
@PaulKnopf, ¿estás seguro de que JetBrain no tiene derechos de autor sobre ese código?)
Marcus
36

Para quitar caracteres no válidos:

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

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

Para reemplazar caracteres no válidos:

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

Para reemplazar caracteres no válidos (y evitar posibles conflictos de nombres como 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());
Ardilla
fuente
33

Esta pregunta se ha hecho muchas veces antes y, como se señaló muchas veces antes, IO.Path.GetInvalidFileNameCharsno es adecuada.

Primero, hay muchos nombres como PRN y CON que están reservados y no permitidos para nombres de archivos. Hay otros nombres que no se permiten solo en la carpeta raíz. Tampoco se permiten los nombres que terminan en un punto.

En segundo lugar, existe una variedad de limitaciones de longitud. Lea la lista completa de NTFS aquí .

En tercer lugar, puede adjuntar a sistemas de archivos que tienen otras limitaciones. Por ejemplo, los nombres de archivo ISO 9660 no pueden comenzar con "-" pero pueden contenerlo.

Cuarto, ¿qué se hace si dos procesos eligen "arbitrariamente" el mismo nombre?

En general, usar nombres generados externamente para nombres de archivos es una mala idea. Sugiero generar sus propios nombres de archivos privados y almacenar internamente nombres legibles por humanos.

Arco alto severo
fuente
13
Aunque es técnicamente preciso, GetInvalidFileNameChars es bueno para el 80% o más de las situaciones en las que lo usaría, por lo que es una buena respuesta. Creo que su respuesta hubiera sido más apropiada como comentario a la respuesta aceptada.
CubanX
4
Estoy de acuerdo con DourHighArch. Guarde el archivo internamente como una guía, refiérase al "nombre descriptivo" que está almacenado en una base de datos. No permita que los usuarios controlen sus rutas en el sitio web o intentarán robar su web.config. Si incorpora la reescritura de URL para que quede limpio, solo funcionará para URL compatibles que coincidan en la base de datos.
rtpHarry
22

Estoy de acuerdo con Grauenwolf y recomendaría encarecidamente el Path.GetInvalidFileNameChars()

Aquí está mi contribución de C #:

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

PD: esto es más críptico de lo que debería ser, estaba tratando de ser conciso.

Aaron Wagner
fuente
3
¿Por qué en el mundo Array.ForEachforeach
usarías en
9
Si quería ser aún más conciso / críptico:Path.GetInvalidFileNameChars().Aggregate(file, (current, c) => current.Replace(c, '-'))
Michael Petito
@ BlueRaja-DannyPflughoeft ¿Porque quieres hacerlo más lento?
Jonathan Allen
@Johnathan Allen, ¿qué te hace pensar que foreach es más rápido que Array.ForEach?
Ryan Buddicom
5
@rbuddicom Array.ForEach toma un delegado, lo que significa que necesita invocar una función que no se puede insertar. Para cadenas cortas, podría terminar gastando más tiempo en la sobrecarga de llamadas a funciones que en la lógica real. .NET Core está buscando formas de "desvirtualizar" las llamadas, reduciendo la sobrecarga.
Jonathan Allen
13

Esta es mi versión:

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

No estoy seguro de cómo se calcula el resultado de GetInvalidFileNameChars, pero "Get" sugiere que no es trivial, así que guardo los resultados en caché. Además, esto solo atraviesa la cadena de entrada una vez en lugar de varias veces, como las soluciones anteriores que iteran sobre el conjunto de caracteres no válidos, reemplazándolos en la cadena de origen uno a la vez. Además, me gustan las soluciones basadas en dónde, pero prefiero reemplazar los caracteres no válidos en lugar de eliminarlos. Finalmente, mi reemplazo es exactamente un carácter para evitar convertir caracteres en cadenas mientras itero sobre la cadena.

Digo todo eso sin hacer el perfil, este simplemente me "sintió" bien. :)

csells
fuente
1
Puede hacer new HashSet<char>(Path.GetInvalidFileNameChars())para evitar la enumeración O (n): microoptimización.
TrueWill
12

Aquí está la función que estoy usando ahora (gracias jcollum por el ejemplo de C #):

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

Solo puse esto en una clase de "Ayudantes" por conveniencia.

sidewinderguy
fuente
7

Si desea eliminar rápidamente todos los caracteres especiales, lo que a veces es más legible por el usuario para los nombres de archivo, esto funciona muy bien:

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
fuente
1
en realidad \Wcoincide con más que no alfanuméricos ( [^A-Za-z0-9_]). Todos los caracteres de 'palabra' Unicode (русский 中文 ..., etc.) tampoco serán reemplazados. Pero esto es bueno.
Ismael
El único inconveniente es que esto también se elimina, .por lo que primero debe extraer la extensión y agregarla nuevamente después.
asombro
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
fuente
5

¿Por qué no convertir la cadena a un equivalente Base64 como este?

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

Si desea volver a convertirlo para poder leerlo:

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

Usé esto para guardar archivos PNG con un nombre único a partir de una descripción aleatoria.

Bart Vanseer
fuente
5

Esto es lo que acabo de agregar a la clase estática StringExtensions de ClipFlair ( http://github.com/Zoomicon/ClipFlair ) (proyecto Utils.Silverlight), según la información recopilada de los enlaces a las preguntas relacionadas con stackoverflow publicadas por Dour High Arch arriba:

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

Encuentro que usar esto es rápido y fácil de entender:

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

Esto funciona porque a stringes IEnumerablecomo una charmatriz y hay una stringcadena de constructor que toma una charmatriz.

Cjbarth
fuente
1

De mis proyectos anteriores, encontré esta solución, que ha estado funcionando perfectamente durante 2 años. Estoy reemplazando los caracteres ilegales con "!", Y luego verifico si hay dobles !!, use su propio carácter.

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

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

        return res;
    }
Roni Tovi
fuente
0

Muchas respuestas sugieren usar lo Path.GetInvalidFileNameChars()que me parece una mala solución. Te animo a que utilices la lista blanca en lugar de la lista negra porque los piratas informáticos siempre encontrarán una manera de evitarlo.

Aquí hay un ejemplo de código que podría usar:

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