2008-10-22 329 views
0

我試圖找出將用戶上傳的文件存儲在文件系統中的最佳方法。這些文件的範圍從個人文件到wiki文件。當然,DB會指向那些我還沒弄明白的文件。如何將上傳的文件存儲在文件系統中?

基本要求:

  • 童話體面的安全,使人們無法猜測文件名 (Picture001.jpg,Picture002.jpg, Music001.mp3是一個很大的不)
  • 方便地備份& Mirrorable(我更喜歡這種方式,所以我不需要每一次都需要備份整個硬盤,我喜歡只備份最新產品的想法,但我可以靈活選擇這裏的選項。)
  • 如果需要,可擴展到多臺服務器上的數百萬個文件

回答

3

一種技術是將數據存儲在以其內容的散列(SHA1)命名的文件中。這是不容易猜測的,任何備份程序都應該能夠處理它,並且它很容易被分割(通過在一臺機器上存儲從0開始的散列,在下一個從1開始的散列等)。

數據庫將包含用戶分配的名稱和內容的SHA1哈希之間的映射。

+0

哈希內容?不想那樣做,太耗費資源... – KristoferA 2008-10-22 10:25:34

1

SHA1散列的文件名+鹽(或者,如果需要的話,可以使文件內容更容易檢測到重複文件,但也會給服務器帶來更多壓力)。這可能需要進行一些調整纔是唯一的(即添加上傳的用戶ID或時間戳),而鹽則使其不可猜測。

文件夾結構然後是散列的部分。

例如,如果哈希是「2fd4e1c67a2d28fced849ee1bb76e7391b93eb12」,那麼文件夾可以是:

/2 
/2/2f/ 
/2/2f/2fd/ 
/2/2f/2fd/2fd4e1c67a2d28fced849ee1bb76e7391b93eb12 

這是爲了防止大的文件夾(某些操作系統有麻煩enumarating文件夾與文件的一百萬,因此製作幾個子文件夾的部分散列,有多少層次?這取決於您期望的文件數量,但2或3通常是合理的。

0

僅就您的問題(安全性)的一個方面而言:將上傳的文件安全地存儲在文件系統中是爲了確保上傳的文件不在webroot中(即,您可以'通過URL直接訪問它們 - 你必須通過一個腳本)。

這使您可以完全控制人們可以下載(安全)的內容並允許諸如日誌記錄之類的內容。當然,你必須確保腳本本身是安全的,但這意味着只有你允許的人才能夠下載某些文件。

3

文件名指南,每個文件夾中不超過幾千個文件/文件夾自動擴展文件夾層次結構。備份新文件是通過備份新文件夾完成的。

你還沒有註明你使用的是什麼環境和/或編程語言,但是這是一個C#/。淨/ Windows示例:

using System; 
using System.IO; 
using System.Xml.Serialization; 

/// <summary> 
/// Class for generating storage structure and file names for document storage. 
/// Copyright (c) 2008, Huagati Systems Co.,Ltd. 
/// </summary> 

public class DocumentStorage 
{ 
    private static StorageDirectory _StorageDirectory = null; 

    public static string GetNewUNCPath() 
    { 
     string storageDirectory = GetStorageDirectory(); 
     if (!storageDirectory.EndsWith("\\")) 
     { 
      storageDirectory += "\\"; 
     } 
     return storageDirectory + GuidEx.NewSeqGuid().ToString() + ".data"; 
    } 

    public static void SaveDocumentInfo(string documentPath, Document documentInfo) 
    { 
     //the filestream object don't like NTFS streams so this is disabled for now... 
     return; 

     //stores a document object in a separate "docinfo" stream attached to the file it belongs to 
     //XmlSerializer ser = new XmlSerializer(typeof(Document)); 
     //string infoStream = documentPath + ":docinfo"; 
     //FileStream fs = new FileStream(infoStream, FileMode.Create); 
     //ser.Serialize(fs, documentInfo); 
     //fs.Flush(); 
     //fs.Close(); 
    } 

    private static string GetStorageDirectory() 
    { 
     string storageRoot = ConfigSettings.DocumentStorageRoot; 
     if (!storageRoot.EndsWith("\\")) 
     { 
      storageRoot += "\\"; 
     } 

     //get storage directory if not set 
     if (_StorageDirectory == null) 
     { 
      _StorageDirectory = new StorageDirectory(); 
      lock (_StorageDirectory) 
      { 
       string path = ConfigSettings.ReadSettingString("CurrentDocumentStoragePath"); 
       if (path == null) 
       { 
        //no storage tree created yet, create first set of subfolders 
        path = CreateStorageDirectory(storageRoot, 1); 
        _StorageDirectory.FullPath = path.Substring(storageRoot.Length); 
        ConfigSettings.WriteSettingString("CurrentDocumentStoragePath", _StorageDirectory.FullPath); 
       } 
       else 
       { 
        _StorageDirectory.FullPath = path; 
       } 
      } 
     } 

     int fileCount = (new DirectoryInfo(storageRoot + _StorageDirectory.FullPath)).GetFiles().Length; 
     if (fileCount > ConfigSettings.FolderContentLimitFiles) 
     { 
      //if the directory has exceeded number of files per directory, create a new one... 
      lock (_StorageDirectory) 
      { 
       string path = GetNewStorageFolder(storageRoot + _StorageDirectory.FullPath, ConfigSettings.DocumentStorageDepth); 
       _StorageDirectory.FullPath = path.Substring(storageRoot.Length); 
       ConfigSettings.WriteSettingString("CurrentDocumentStoragePath", _StorageDirectory.FullPath); 
      } 
     } 

     return storageRoot + _StorageDirectory.FullPath; 
    } 

    private static string GetNewStorageFolder(string currentPath, int currentDepth) 
    { 
     string parentFolder = currentPath.Substring(0, currentPath.LastIndexOf("\\")); 
     int parentFolderFolderCount = (new DirectoryInfo(parentFolder)).GetDirectories().Length; 
     if (parentFolderFolderCount < ConfigSettings.FolderContentLimitFolders) 
     { 
      return CreateStorageDirectory(parentFolder, currentDepth); 
     } 
     else 
     { 
      return GetNewStorageFolder(parentFolder, currentDepth - 1); 
     } 
    } 

    private static string CreateStorageDirectory(string currentDir, int currentDepth) 
    { 
     string storageDirectory = null; 
     string directoryName = GuidEx.NewSeqGuid().ToString(); 
     if (!currentDir.EndsWith("\\")) 
     { 
      currentDir += "\\"; 
     } 
     Directory.CreateDirectory(currentDir + directoryName); 

     if (currentDepth < ConfigSettings.DocumentStorageDepth) 
     { 
      storageDirectory = CreateStorageDirectory(currentDir + directoryName, currentDepth + 1); 
     } 
     else 
     { 
      storageDirectory = currentDir + directoryName; 
     } 
     return storageDirectory; 
    } 

    private class StorageDirectory 
    { 
     public string DirectoryName { get; set; } 
     public StorageDirectory ParentDirectory { get; set; } 
     public string FullPath 
     { 
      get 
      { 
       if (ParentDirectory != null) 
       { 
        return ParentDirectory.FullPath + "\\" + DirectoryName; 
       } 
       else 
       { 
        return DirectoryName; 
       } 
      } 
      set 
      { 
       if (value.Contains("\\")) 
       { 
        DirectoryName = value.Substring(value.LastIndexOf("\\") + 1); 
        ParentDirectory = new StorageDirectory { FullPath = value.Substring(0, value.LastIndexOf("\\")) }; 
       } 
       else 
       { 
        DirectoryName = value; 
       } 
      } 
     } 
    } 
} 
0

擴展在菲爾聖的回答,安全的另一個方面是使用一個單獨的域名上傳的文件(instante,維基百科使用upload.wikimedia.org),並確保該網域無法讀取任何網站的Cookie。這樣可以防止用戶通過腳本上傳HTML文件來竊取用戶的會話cookie(只需設置Content-Type標頭是不夠的,因爲some browsers已知可以忽略它並根據文件內容進行猜測;它也可以是嵌入到其他類型的文件中,因此檢查HTML並禁止它不是微不足道的)。

相關問題