¿Hay alguna manera de hacer que las cadenas de ruta de archivo sean seguras en C #?

StackOverflow https://stackoverflow.com/questions/333175

  •  22-07-2019
  •  | 
  •  

Pregunta

Mi programa tomará cadenas arbitrarias de Internet y las usará para nombres de archivos. ¿Hay una manera simple de eliminar los caracteres incorrectos de estas cadenas o necesito escribir una función personalizada para esto?

¿Fue útil?

Solución

Ugh, odio cuando la gente trata de adivinar qué personajes son válidos. Además de ser completamente no portátil (siempre pensando en Mono), los dos comentarios anteriores omitieron más de 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

Otros consejos

Esta pregunta se ha hecho muchas veces antes y, como se señaló muchas veces antes, IO.Path.GetInvalidFileNameChars no es adecuado.

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

Segundo, hay una variedad de limitaciones de longitud. Lea la lista completa de NTFS aquí .

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

Cuarto, ¿qué haces si dos procesos "arbitrariamente"? elegir 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 nombres legibles por humanos internamente.

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

Estoy de acuerdo con Grauenwolf y recomiendo 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));

p.s. - esto es más críptico de lo que debería ser - estaba tratando de ser conciso.

Aquí está 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 el " Get " sugiere que no es trivial, así que guardo en caché los resultados. 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 Where, 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 a medida que itero sobre la cadena.

Digo todo eso sin hacer el perfilado, este simplemente "sintió". agradable para mí. :)

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

Acabo de poner esto en un "Ayudantes" clase por conveniencia.

Si desea eliminar rápidamente todos los caracteres especiales, que a veces es más legible para los nombres de archivo, esto funciona 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"
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);
    }
}

Esto es lo que acabo de añadir a ClipFlair's ( http://github.com/Zoomicon/ClipFlair ) Clase estática StringExtensions (proyecto Utils.Silverlight), basada en la información recopilada de los enlaces a las preguntas relacionadas de 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)
}

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

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

Si desea convertirlo de nuevo para poder leerlo:

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

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

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

Creo 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 una cadena es IEnumerable como una matriz char y hay una cadena constructora string que toma una matriz char .

Muchos responden sugiriendo usar Path.GetInvalidFileNameChars () , lo que me parece una mala solución. Le recomiendo que use la lista blanca en lugar de la lista negra porque los piratas informáticos siempre encontrarán una forma de evitarla.

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, '-');
        }
    }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top