2014-04-15 65 views
2

Haskell noob here。我有一個關於如何使用現有庫的具體問題,可能會導致Haskell正確使用的一些更基本的方面。如何覆蓋Codec.Archive.Tar中的函數

我正在學習Haskell,並且在學習的時候有一個小項目在工作。該腳本需要找到給定目錄中的所有tarball,並將它們並行打包。在這一點上,我正在研究拆包的基本功能。因此,使用Codec.Archive.Tar軟件包,我怎樣才能用完全合格的路徑覆蓋它對tarball的行爲?

下面是一些示例代碼:

module Main where 

import qualified Codec.Archive.Tar as Tar 
import qualified Codec.Compression.GZip as GZip 
import Control.Monad (liftM, unless) 
import qualified Data.ByteString.Lazy as BS 
import System.Directory (doesDirectoryExist, getDirectoryContents) 
import System.Exit (exitWith, ExitCode(..)) 
import System.FilePath.Posix (takeExtension) 

searchPath = "/home/someuser/tarball/dir" 

exit = exitWith ExitSuccess 
die = exitWith (ExitFailure 1) 

processFile :: String -> IO() 
processFile file = do 
    putStrLn $ "Unpacking " ++ file ++ " to " ++ searchPath 
    Tar.unpack searchPath . Tar.read . GZip.decompress =<< BS.readFile filePath 
    where filePath = searchPath ++ "/" ++ file 

main = do 
    dirExists <- doesDirectoryExist searchPath 
    unless dirExists $ (putStrLn $ "Error: Search path not found: " ++ searchPath) >> die 
    files <- targetFiles `liftM` getDirectoryContents searchPath 
    mapM_ processFile files 
    exit 
    where targetFiles = filter (\f -> f /= "." && f /= ".." && takeExtension f == ".tgz") 

當我用tar包的目錄中擠滿了這條命令:

tar czvPf myfile.tgz /tarball_testing/myfile 

我得到以下輸出:

Unpacking myfile.tgz to /tarball_testing 
unpacker.hs: Absolute file name in tar archive: "/tarball_testing/myfile" 

第二行是問題。閱讀文檔Codec.Archive.Tar我沒有看到一種方法來禁用此功能(對於我爲什麼要在tarball中使用完整路徑或者相關安全影響的討論沒有興趣)。

首先想到的是,我不知何故需要重寫該函數,但並不像「專業版Haskeller」那樣「感覺」。我可以在正確的方向得到一個指針嗎?

+0

從我的文檔掃描爲['tar'包](http://hackage.haskell.org/package/tar),它看起來像提供文件提供的唯一接口將拒絕絕對路徑。你可以用這個軟件包做任何事情。 – Carl

+0

可能有用的一件事是使用['mapEntries'](http://hackage.haskell.org/package/tar-0.3.1.0/docs/Codec-Archive-Tar.html#v:mapEntries)以及工具['Codec.Archive.Tar.Entry'](http://hackage.haskell.org/package/tar-0.3.1.0/docs/Codec-Archive-Tar-Entry.html)和'System.FilePath'來製作提取之前的相對入口路徑。 – duplode

回答

3

您不能monkey patch或以其他方式覆蓋Haskell模塊中的函數,因此沒有解決方法可以讓您避免庫的安全措施。但是,您可以執行的操作是使用Codec.Archive.Tar中的功能在解包之前修改tar入口路徑,以便它們不再是絕對的。具體而言,有一個mapEntriesNoFail函數類型

mapEntriesNoFail :: (Entry -> Entry) -> Entries e -> Entries e 

Entries是參數Tar.unpack的類型,而Entry是單個條目的類型。由於mapEntriesNoFail,我們的問題變成編寫Entry -> Entry函數來調整路徑。爲此,我們首先需要一些額外的進口:

import qualified Codec.Archive.Tar.Entry as Tar 
import System.FilePath.Posix (takeExtension, dropDrive, hasTrailingPathSeparator) 
import Data.Either (either) 

的功能可以是這樣的:

dropDriveFromEntry :: Tar.Entry -> Tar.Entry 
dropDriveFromEntry entry = 
    either (error "Resulting tar path is somehow too long") 
     (\tp -> entry { Tar.entryTarPath = tp }) 
     drivelessTarPath 
    where 
    tarPath = Tar.entryTarPath entry 
    path = Tar.fromTarPath tarPath 
    toTarPath' p = Tar.toTarPath (hasTrailingPathSeparator p) p 
    drivelessTarPath = toTarPath' $ dropDrive path 

這可能看起來有點囉嗦;然而,我們跳過的籃球場是爲了確保產生的焦油路徑是理智的。您可以在Codec.Archive.Tar.Entry文檔中瞭解焦油處理的詳細信息。這個定義中的關鍵函數是dropDrive,它使絕對路徑相對(在Linux中,它剝離絕對路徑的前導斜槓)。

值得花幾個關於either的使用。 toTarPath產生類型爲Either String TarPath的值以解釋失敗的可能性。特別是,如果提供的路徑太長,則轉換爲tar路徑會失敗。然而在我們的例子中,路徑不能太長,因爲它是一個已經存在於tar文件中的路徑,可能是刪除了前導斜槓。既然如此,它已經足夠消除Eithereither包裝,傳遞錯誤而不是函數來處理(不可能)Left的情況。

隨着dropDriveFromEntry在手中,我們只需在開箱之前將其映射到條目上。你的程序的相關行會變成:

Tar.unpack searchPath . Tar.mapEntriesNoFail dropDriveFromEntry 
     . Tar.read . GZip.decompress =<< BS.readFile filePath 

需要注意的是,如果有相關的錯誤被佔dropDriveFromEntry,我們將使它回到Either String TarPath,然後用mapEntries代替mapEntriesNoFail

通過這些更改,tar文件中的條目將被提取到/home/someuser/tarball/dir/tarball_testing/myfile。如果這不是您想要的,您可以修改dropDriveFromEntry,以便它執行任何所需的額外路徑處理。

PS:關於你的問題的備選標題,考慮到你對我們的懂事的小程序,我不認爲你應該擔心:)