C #에서 문자열 파일 경로를 안전하게 만드는 방법이 있습니까?


답변:


171

어, 사람들이 어떤 문자가 유효한지 추측하려고 할 때 싫어요. 완전히 이식 할 수없는 것 외에도 (항상 Mono에 대해 생각 함) 이전 주석 모두 25 개 이상의 유효하지 않은 문자를 놓쳤습니다.

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

8
이 솔루션은 이름 충돌을 어떻게 처리합니까? 둘 이상의 문자열이 단일 파일 이름 (예 : "Hell?"및 "Hell *")과 일치 할 수있는 것 같습니다. 문제가되는 문자 만 제거해도 괜찮다면 괜찮습니다. 그렇지 않으면 이름 충돌을 처리 할 때주의해야합니다.
스테파노 Ricciardi은

2
파일 시스템의 이름 (및 경로) 길이 제한은 어떻습니까? 예약 된 파일 이름 (PRN CON)은 어떻습니까? 데이터와 원래 이름을 저장해야하는 경우 Guid 이름이있는 2 개의 파일을 사용할 수 있습니다. guid.txt 및 guid.dat
Jack

6
재미있는 결과를위한 하나의 라이너 = Path.GetInvalidFileNameChars (). Aggregate (result, (current, c) => current.Replace (c, '-'));
Paul Knopf 2013 년

1
@PaulKnopf, JetBrain이 해당 코드에 대한 저작권이 없다고 확신하십니까?)
Marcus

36

유효하지 않은 문자를 제거하려면 :

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

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

유효하지 않은 문자를 바꾸려면 :

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

유효하지 않은 문자를 바꾸려면 (그리고 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

이 질문은 이전에 여러 요청되었으며 이전에 여러 지적했듯이 IO.Path.GetInvalidFileNameChars적절하지 않습니다.

첫째, PRN 및 CON과 같이 예약되어 있고 파일 이름에 허용되지 않는 이름이 많이 있습니다. 루트 폴더에만 허용되지 않는 다른 이름이 있습니다. 마침표로 끝나는 이름도 허용되지 않습니다.

둘째, 다양한 길이 제한이 있습니다. 여기 에서 NTFS에 대한 전체 목록을 읽어보십시오 .

셋째, 다른 제한이있는 파일 시스템에 연결할 수 있습니다. 예를 들어 ISO 9660 파일 이름은 "-"로 시작할 수 없지만 포함 할 수 있습니다.

넷째, 두 프로세스가 "임의로"같은 이름을 선택하면 어떻게합니까?

일반적으로 파일 이름에 대해 외부에서 생성 된 이름을 사용하는 것은 좋지 않습니다. 자신의 개인 파일 이름을 생성하고 사람이 읽을 수있는 이름을 내부적으로 저장하는 것이 좋습니다.


13
기술적으로는 정확하지만 GetInvalidFileNameChars는 사용하는 상황의 80 % 이상에 적합하므로 좋은 대답입니다. 귀하의 답변은 내가 생각하는 수락 된 답변에 대한 의견으로 더 적절했을 것입니다.
CubanX 2011 년

4
DourHighArch에 동의합니다. 파일을 내부적으로 guid로 저장하고 데이터베이스에 저장된 "친숙한 이름"에 대해 참조하십시오. 사용자가 웹 사이트에서 경로를 제어하도록 허용하지 마십시오. 그렇지 않으면 web.config를 훔치려 고 할 것입니다. URL 재 작성을 통합하여 깔끔하게 만들면 데이터베이스에서 일치하는 친숙한 URL에 대해서만 작동합니다.
rtpHarry

22

나는 Grauenwolf에 동의하며 Path.GetInvalidFileNameChars()

내 C # 기여는 다음과 같습니다.

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

추신-이건 당연한 것보다 더 은밀합니다-저는 간결하게하려고했습니다.


3
왜 세계의 것 당신이 사용하는 Array.ForEach단지 대신 foreach여기
BlueRaja - 대니 Pflughoeft

9
더 간결하고 / 암호화하고 Path.GetInvalidFileNameChars().Aggregate(file, (current, c) => current.Replace(c, '-'))
싶다면

@ BlueRaja-DannyPflughoeft 느리게 만들고 싶기 때문에?
Jonathan Allen

@Johnathan Allen, foreach가 Array.ForEach보다 빠르다고 생각하는 이유는 무엇입니까?
Ryan Buddicom 2014

5
@rbuddicom Array.ForEach는 델리게이트를 취합니다. 즉, 인라인 될 수없는 함수를 호출해야합니다. 짧은 문자열의 경우 실제 논리보다 함수 호출 오버 헤드에 더 많은 시간을 소비 할 수 있습니다. .NET Core는 호출을 "비 가상화"하여 오버 헤드를 줄이는 방법을 찾고 있습니다.
Jonathan Allen

13

내 버전은 다음과 같습니다.

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

GetInvalidFileNameChars의 결과가 어떻게 계산되는지 잘 모르겠지만 "Get"은 그것이 사소하지 않다고 제안하므로 결과를 캐시합니다. 또한 이것은 잘못된 문자 집합을 반복하는 위의 솔루션과 같이 여러 번 대신 한 번만 입력 문자열을 탐색하여 소스 문자열에서 한 번에 하나씩 대체합니다. 또한 Where 기반 솔루션을 좋아하지만 유효하지 않은 문자를 제거하는 대신 대체하는 것을 선호합니다. 마지막으로, 문자열을 반복 할 때 문자를 문자열로 변환하지 않도록 정확히 한 문자를 대체합니다.

나는 프로파일 링을하지 않고 모든 것을 말한다. 이것은 나에게 단지 "느낌"이었다. :)


1
new HashSet<char>(Path.GetInvalidFileNameChars())O (n) 열거 (미세 최적화)를 피할 수 있습니다.
TrueWill

12

지금 사용하고있는 함수는 다음과 같습니다 (C # 예제의 jcollum에게 감사드립니다).

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

편의를 위해 "Helpers"클래스에 넣었습니다.


7

파일 이름에 대해 사용자가 더 쉽게 읽을 수있는 모든 특수 문자를 신속하게 제거하려면 다음과 같이 잘 작동합니다.

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
실제로 \W는 알파벳이 아닌 숫자 ( [^A-Za-z0-9_]) 보다 더 많이 일치 합니다. 모든 유니 코드 '단어'문자 (русский 中文 ... 등)도 대체되지 않습니다. 그러나 이것은 좋은 것입니다.
Ishmael

유일한 단점은 이것이 또한 제거 .되므로 먼저 확장을 추출하고 나중에 다시 추가해야한다는 것입니다.
경외

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

문자열을 다음과 같이 Base64로 변환하지 않는 이유는 무엇입니까?

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

읽을 수 있도록 다시 변환하려면 :

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

무작위 설명에서 고유 한 이름으로 PNG 파일을 저장하는 데 사용했습니다.


5

위의 Dour High Arch가 게시 한 관련 스택 오버플로 질문에 대한 링크에서 수집 한 정보를 기반으로 ClipFlair의 ( http://github.com/Zoomicon/ClipFlair ) StringExtensions 정적 클래스 (Utils.Silverlight 프로젝트)에 방금 추가 한 내용은 다음과 같습니다 .

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

이 방법을 사용하면 빠르고 쉽게 이해할 수 있습니다.

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

a가 있기 때문 작품 stringIEnumerableA와 char배열하고있다 string소요 생성자 문자열 char배열.


1

이전 프로젝트에서 2 년 동안 완벽하게 작동하는이 솔루션을 찾았습니다. 불법 문자를 "!"로 바꾸고 이중 !!를 확인하고 자신의 문자를 사용합니다.

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

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

        return res;
    }

0

많은 사람들 Path.GetInvalidFileNameChars()이 나에게 나쁜 해결책처럼 보이는 사용 을 제안합니다 . 해커는 항상이를 우회 할 방법을 찾기 때문에 블랙리스트 대신 화이트리스트를 사용하는 것이 좋습니다.

다음은 사용할 수있는 코드의 예입니다.

    string whitelist = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.";
    foreach (char c in filename)
    {
        if (!whitelist.Contains(c))
        {
            filename = filename.Replace(c, '-');
        }
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.