Existe uma maneira de tornar o caminho do arquivo de strings seguro em c #?


Respostas:


171

Ugh, odeio quando as pessoas tentam adivinhar quais caracteres são válidos. Além de serem completamente não portáteis (sempre pensando em Mono), ambos os comentários anteriores perderam mais 25 caracteres invá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

83
A versão C #: foreach (var c in Path.GetInvalidFileNameChars ()) {fileName = fileName.Replace (c, '-'); }
jcollum

8
Como esta solução lidaria com conflitos de nomes? Parece que mais de uma string pode corresponder a um único nome de arquivo ("Inferno?" E "Inferno *", por exemplo). Se você está ok para remover apenas caracteres ofensivos, tudo bem; caso contrário, você precisa ter cuidado ao lidar com conflitos de nomes.
Stefano Ricciardi

2
e quanto aos limites de tamanho do nome (e caminho) do sistema de arquivos? e os nomes de arquivo reservados (PRN CON)? Se precisar armazenar os dados e o nome original, você pode usar 2 arquivos com nomes de Guid: guid.txt e guid.dat
Jack

6
Uma linha, para diversão resultado = Path.GetInvalidFileNameChars (). Agregar (resultado, (corrente, c) => corrente.Replace (c, '-'));
Paul Knopf

1
@PaulKnopf, você tem certeza de que o JetBrain não possui direitos autorais sobre esse código;)
Marcus

36

Para retirar caracteres invá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 substituir caracteres invá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 substituir caracteres inválidos (e evitar conflito de nome em potencial, como Inferno * vs Inferno $):

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

Esta pergunta já foi feita muitas vezes antes e, como apontado muitas vezes antes, IO.Path.GetInvalidFileNameCharsnão é adequada.

Primeiro, existem muitos nomes como PRN e CON que são reservados e não são permitidos para nomes de arquivos. Existem outros nomes não permitidos apenas na pasta raiz. Nomes que terminam em ponto também não são permitidos.

Em segundo lugar, há uma variedade de limitações de comprimento. Leia a lista completa de NTFS aqui .

Terceiro, você pode anexar a sistemas de arquivos que possuem outras limitações. Por exemplo, os nomes de arquivo ISO 9660 não podem começar com "-", mas podem contê-lo.

Quarto, o que você faria se dois processos escolhessem "arbitrariamente" o mesmo nome?

Em geral, usar nomes gerados externamente para nomes de arquivos é uma má ideia. Eu sugiro gerar seus próprios nomes de arquivo privados e armazenar nomes legíveis internamente.


13
Embora você seja tecnicamente preciso, GetInvalidFileNameChars é bom para 80% + das situações em que você o usaria, portanto, é uma boa resposta. Sua resposta teria sido mais apropriada como um comentário à resposta aceita, eu acho.
CubanX

4
Eu concordo com DourHighArch. Salve o arquivo internamente como um guia, comparando-o com o "nome amigável" que está armazenado em um banco de dados. Não deixe que os usuários controlem seus caminhos no site ou eles tentarão roubar seu web.config. Se você incorporar a reescrita de url para torná-la limpa, ela só funcionará para urls amigáveis ​​correspondentes no banco de dados.
rtpHarry

22

Eu concordo com Grauenwolf e recomendo fortemente o Path.GetInvalidFileNameChars()

Aqui está minha contribuição C #:

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

ps - isso é mais enigmático do que deveria ser - eu estava tentando ser conciso.


3
Por que no mundo você usaria em Array.ForEachvez de apenas foreachaqui
BlueRaja - Danny Pflughoeft

9
Se você quiser ser ainda mais conciso / enigmático:Path.GetInvalidFileNameChars().Aggregate(file, (current, c) => current.Replace(c, '-'))
Michael Petito

@ BlueRaja-DannyPflughoeft Porque você quer torná-lo mais lento?
Jonathan Allen

@Johnathan Allen, o que o faz pensar que foreach é mais rápido do que Array.ForEach?
Ryan Buddicom

5
@rbuddicom Array.ForEach recebe um delegado, o que significa que ele precisa invocar uma função que não pode ser embutida. Para strings curtas, você pode acabar gastando mais tempo na sobrecarga da chamada de função do que na lógica real. O .NET Core está procurando maneiras de "desvirtualizar" as chamadas, reduzindo a sobrecarga.
Jonathan Allen

13

Aqui está minha versão:

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

Não tenho certeza de como o resultado de GetInvalidFileNameChars é calculado, mas o "Get" sugere que não é trivial, por isso coloco os resultados em cache. Além disso, isso só percorre a string de entrada uma vez em vez de várias vezes, como as soluções acima que iteram sobre o conjunto de caracteres inválidos, substituindo-os na string de origem, um de cada vez. Além disso, gosto das soluções baseadas em Where, mas prefiro substituir caracteres inválidos em vez de removê-los. Por fim, minha substituição é exatamente um caractere para evitar a conversão de caracteres em strings à medida que faço a iteração na string.

Eu digo tudo isso sem fazer o perfil - este apenas "pareceu" bom para mim. :)


1
Você poderia fazer new HashSet<char>(Path.GetInvalidFileNameChars())para evitar a enumeração O (n) - micro-otimização.
TrueWill

12

Esta é a função que estou usando agora (obrigado jcollum pelo exemplo C #):

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

Acabei de colocar isso em uma classe de "Ajudantes" por conveniência.


7

Se você quiser remover rapidamente todos os caracteres especiais, o que às vezes é mais legível pelo usuário para nomes de arquivo, isso funciona bem:

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
na verdade, \Wcorresponde a mais do que não-alfa-numéricos ( [^A-Za-z0-9_]). Todos os caracteres de 'palavra' Unicode (русский 中文 ..., etc.) também não serão substituídos. Mas isso é uma coisa boa.
Ishmael

A única desvantagem é que isso também remove, .então você precisa extrair a extensão primeiro e adicioná-la novamente depois.
incrível

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

Por que não converter a string em um equivalente em Base64 como este:

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

Se você quiser convertê-lo de volta para que possa lê-lo:

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

Usei isso para salvar arquivos PNG com um nome exclusivo de uma descrição aleatória.


5

Aqui está o que eu adicionei para (de ClipFlair http://github.com/Zoomicon/ClipFlair ) StringExtensions classe estática (projeto Utils.Silverlight), com base em informações recolhidas a partir dos links para outras questões relacionadas stackoverflow postados por Dour High Arch acima:

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

Acho que usar isso é rápido e 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

Isso funciona porque a stringé IEnumerableuma charmatriz e há uma stringstring de construtor que recebe uma charmatriz.


1

Em meus projetos mais antigos, encontrei essa solução, que está funcionando perfeitamente há 2 anos. Estou substituindo chars ilegais por "!", E então checo por !! 's, use seu próprio char.

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

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

        return res;
    }

0

Muitas respostas sugerem o uso, o Path.GetInvalidFileNameChars()que parece uma solução ruim para mim. Eu encorajo você a usar a lista de permissões em vez de lista negra porque os hackers sempre encontrarão uma maneira de contornar isso.

Aqui está um exemplo de código que você pode usar:

    string whitelist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.";
    foreach (char c in filename)
    {
        if (!whitelist.Contains(c))
        {
            filename = filename.Replace(c, '-');
        }
    }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.