มีวิธีทำให้ file-path ปลอดภัยใน c # หรือไม่?


94

โปรแกรมของฉันจะใช้สตริงโดยพลการจากอินเทอร์เน็ตและใช้เป็นชื่อไฟล์ มีวิธีง่ายๆในการลบอักขระที่ไม่ดีออกจากสตริงเหล่านี้หรือฉันต้องเขียนฟังก์ชันที่กำหนดเองสำหรับสิ่งนี้หรือไม่


อาจซ้ำกันได้ของSafe / Allowed filename Cleaner สำหรับ. NET
N8allan

คำตอบ:


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

84
เวอร์ชัน C #: foreach (var c ใน Path.GetInvalidFileNameChars ()) {fileName = fileName.Replace (c, '-'); }
jcollum

8
โซลูชันนี้จะจัดการกับความขัดแย้งของชื่ออย่างไร ดูเหมือนว่ามากกว่าหนึ่งสตริงสามารถจับคู่กับชื่อไฟล์เดียวได้ (เช่น "Hell?" และ "Hell *") หากคุณสามารถลบเฉพาะอักขระที่กระทำผิดได้ก็ใช้ได้ มิฉะนั้นคุณจะต้องระมัดระวังในการจัดการกับความขัดแย้งของชื่อ
Stefano Ricciardi

2
สิ่งที่เกี่ยวกับขีด จำกัด ของชื่อ (และเส้นทาง) ความยาวของ filesytem? ชื่อไฟล์ที่สงวนไว้ (PRN CON) ล่ะ? หากคุณต้องการจัดเก็บข้อมูลและชื่อเดิมคุณสามารถใช้ 2 ไฟล์ที่มี Guid names: guid.txt และ guid.dat
Jack

7
หนึ่งซับสำหรับผลลัพธ์ที่สนุกสนาน = Path.GetInvalidFileNameChars (). Aggregate (ผลลัพธ์, (ปัจจุบัน, c) => current.Replace (c, '-'));
Paul Knopf

1
@PaulKnopf คุณแน่ใจหรือว่า JetBrain ไม่มีลิขสิทธิ์ในรหัสนั้น;)
Marcus

38

ในการตัดอักขระที่ไม่ถูกต้อง:

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

34

คำถามนี้ถูกถามหลาย ครั้ง ก่อนหน้านี้และหลายครั้งก่อนหน้าIO.Path.GetInvalidFileNameCharsนี้ก็ยังไม่เพียงพอ

ประการแรกมีหลายชื่อเช่น PRN และ CON ที่สงวนไว้และไม่อนุญาตให้ใช้กับชื่อไฟล์ มีชื่ออื่นที่ไม่อนุญาตให้ใช้เฉพาะในโฟลเดอร์รูทเท่านั้น นอกจากนี้ยังไม่อนุญาตให้ใช้ชื่อที่ลงท้ายด้วยช่วงเวลา

ประการที่สองมีข้อ จำกัด ด้านความยาวที่หลากหลาย อ่านรายการเต็มรูปแบบสำหรับ NTFS ที่นี่

ประการที่สามคุณสามารถเชื่อมต่อกับระบบไฟล์ที่มีข้อ จำกัด อื่น ๆ ตัวอย่างเช่นชื่อไฟล์ ISO 9660 ไม่สามารถขึ้นต้นด้วย "-" แต่สามารถมีได้

ประการที่สี่คุณจะทำอย่างไรหากสองกระบวนการ "โดยพลการ" เลือกชื่อเดียวกัน

โดยทั่วไปการใช้ชื่อที่สร้างจากภายนอกสำหรับชื่อไฟล์เป็นความคิดที่ไม่ดี ฉันขอแนะนำให้สร้างชื่อไฟล์ส่วนตัวของคุณเองและจัดเก็บชื่อที่มนุษย์อ่านได้ภายใน


13
แม้ว่าคุณจะมีความแม่นยำในทางเทคนิค แต่ GetInvalidFileNameChars ก็ดีสำหรับ 80% + ของสถานการณ์ที่คุณใช้งานดังนั้นจึงเป็นคำตอบที่ดี คำตอบของคุณน่าจะเหมาะสมกว่าเมื่อเป็นความคิดเห็นสำหรับคำตอบที่ฉันคิดว่าเป็นที่ยอมรับ
CubanX

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

ps - นี่เป็นความลับมากกว่าที่ควรจะเป็น - ฉันพยายามจะรัดกุม


3
ทำไมในโลกคุณถึงใช้Array.ForEachแทนที่จะใช้foreachที่นี่
BlueRaja - Danny Pflughoeft

9
หากคุณต้องการกระชับ / คลุมเครือมากขึ้น:Path.GetInvalidFileNameChars().Aggregate(file, (current, c) => current.Replace(c, '-'))
Michael Petito

@ BlueRaja-DannyPflughoeft เพราะอยากทำให้ช้าลง?
Jonathan Allen

@Johnathan Allen อะไรทำให้คุณคิดว่า foreach เร็วกว่า ArrayForEach?
Ryan Buddicom

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-based แต่ฉันต้องการแทนที่ตัวอักษรที่ไม่ถูกต้องแทนที่จะลบออก สุดท้ายการแทนที่ของฉันเป็นเพียงอักขระเดียวเพื่อหลีกเลี่ยงการแปลงอักขระเป็นสตริงในขณะที่ฉันวนซ้ำบนสตริง

ฉันพูดทั้งหมดที่ไม่มีการทำโปรไฟล์ - อันนี้ "รู้สึก" ดีสำหรับฉัน :)


1
คุณสามารถทำได้new HashSet<char>(Path.GetInvalidFileNameChars())เพื่อหลีกเลี่ยงการแจงนับ O (n) - การเพิ่มประสิทธิภาพไมโคร
TrueWill

12

นี่คือฟังก์ชั่นที่ฉันใช้อยู่ตอนนี้ (ขอบคุณ jcollum สำหรับตัวอย่าง C #):

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

ฉันใส่สิ่งนี้ไว้ในชั้นเรียน "ผู้ช่วยเหลือ" เพื่อความสะดวก


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_]) อักขระ 'word' ของ Unicode ทั้งหมด (русский中文 ... ฯลฯ ) จะไม่ถูกแทนที่ด้วยเช่นกัน แต่นี่คือสิ่งที่ดี.
อิชมาเอล

ข้อเสียเพียงอย่างเดียวคือสิ่งนี้จะลบออกด้วย.ดังนั้นคุณต้องแยกส่วนขยายก่อนและเพิ่มอีกครั้งในภายหลัง
กลัว


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

นี่คือสิ่งที่ฉันเพิ่งเพิ่มลงในคลาสแบบคงที่ของ ClipFlair ( http://github.com/Zoomicon/ClipFlair ) StringExtensions (โครงการ Utils.Silverlight) ตามข้อมูลที่รวบรวมจากลิงก์ไปยังคำถาม stackoverflow ที่เกี่ยวข้องที่โพสต์โดย Dour High Arch ด้านบน:

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

นี้ทำงานได้เพราะstringเป็นIEnumerableเป็น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.