packages feed

zip-archive 0.4.2.2 → 0.4.3

raw patch · 4 files changed

+70/−76 lines, 4 filesdep ~binary

Dependency ranges changed: binary

Files

Main.hs view
@@ -16,7 +16,6 @@ import System.Directory import System.Console.GetOpt import Control.Monad ( when )-import Control.Applicative ( (<$>) ) import Data.Version ( showVersion ) import Paths_zip_archive ( version ) import Debug.Trace ( traceShowId )@@ -70,7 +69,9 @@   let cmd = case filter (`notElem` [Quiet, Help, Version, Debug]) opts of                   []    -> Recursive                   (x:_) -> x-  let (archivePath : files) = args+  (archivePath : files) <- case args of+      [] -> quit True "No archive path given"+      _ -> return args   exists <- doesFileExist archivePath   archive <- if exists                 then toArchive <$> B.readFile archivePath
changelog view
@@ -1,3 +1,12 @@+zip-archive 0.4.3++  * Improve code for retrieving compressed data of unknown length (#63).+    Do not assume we'll have the signature 0x08074b50 that is+    sometimes used for the data description, because it is not+    in the spec and is not always used.+  * Make some record fields strict.+  * Require binary >= 0.7.2, remove some CPP+ zip-archive 0.4.2.2    * Use `command -v` before trying `which` in the test suite (#62).
src/Codec/Archive/Zip.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE ViewPatterns #-} ------------------------------------------------------------------------@@ -100,6 +101,8 @@ import Data.Maybe (fromJust) #endif +import GHC.Int (Int64)+ -- from bytestring import qualified Data.ByteString as S import qualified Data.ByteString.Lazy as B@@ -131,7 +134,7 @@ data Archive = Archive                 { zEntries                :: [Entry]              -- ^ Files in zip archive                 , zSignature              :: Maybe B.ByteString   -- ^ Digital signature-                , zComment                :: B.ByteString         -- ^ Comment for whole zip archive+                , zComment                :: !B.ByteString        -- ^ Comment for whole zip archive                 } deriving (Read, Show)  instance Binary Archive where@@ -141,18 +144,18 @@ -- | Representation of an archived file, including content and metadata. data Entry = Entry                { eRelativePath            :: FilePath            -- ^ Relative path, using '/' as separator-               , eCompressionMethod       :: CompressionMethod   -- ^ Compression method-               , eEncryptionMethod        :: EncryptionMethod    -- ^ Encryption method-               , eLastModified            :: Integer             -- ^ Modification time (seconds since unix epoch)-               , eCRC32                   :: Word32              -- ^ CRC32 checksum-               , eCompressedSize          :: Word32              -- ^ Compressed size in bytes-               , eUncompressedSize        :: Word32              -- ^ Uncompressed size in bytes-               , eExtraField              :: B.ByteString        -- ^ Extra field - unused by this library-               , eFileComment             :: B.ByteString        -- ^ File comment - unused by this library-               , eVersionMadeBy           :: Word16              -- ^ Version made by field-               , eInternalFileAttributes  :: Word16              -- ^ Internal file attributes - unused by this library-               , eExternalFileAttributes  :: Word32              -- ^ External file attributes (system-dependent)-               , eCompressedData          :: B.ByteString        -- ^ Compressed contents of file+               , eCompressionMethod       :: !CompressionMethod   -- ^ Compression method+               , eEncryptionMethod        :: !EncryptionMethod    -- ^ Encryption method+               , eLastModified            :: !Integer             -- ^ Modification time (seconds since unix epoch)+               , eCRC32                   :: !Word32              -- ^ CRC32 checksum+               , eCompressedSize          :: !Word32              -- ^ Compressed size in bytes+               , eUncompressedSize        :: !Word32              -- ^ Uncompressed size in bytes+               , eExtraField              :: !B.ByteString        -- ^ Extra field - unused by this library+               , eFileComment             :: !B.ByteString        -- ^ File comment - unused by this library+               , eVersionMadeBy           :: !Word16              -- ^ Version made by field+               , eInternalFileAttributes  :: !Word16              -- ^ Internal file attributes - unused by this library+               , eExternalFileAttributes  :: !Word32              -- ^ External file attributes (system-dependent)+               , eCompressedData          :: !B.ByteString        -- ^ Compressed contents of file                } deriving (Read, Show, Eq)  -- | Compression methods.@@ -160,8 +163,8 @@                        | NoCompression                        deriving (Read, Show, Eq) -data EncryptionMethod = NoEncryption            -- ^ Entry is not encrypted-                      | PKWAREEncryption Word8  -- ^ Entry is encrypted with the traditional PKWARE encryption+data EncryptionMethod = NoEncryption             -- ^ Entry is not encrypted+                      | PKWAREEncryption !Word8  -- ^ Entry is encrypted with the traditional PKWARE encryption                       deriving (Read, Show, Eq)  -- | The way the password should be verified during entry decryption@@ -173,7 +176,7 @@ data ZipOption = OptRecursive               -- ^ Recurse into directories when adding files                | OptVerbose                 -- ^ Print information to stderr                | OptDestination FilePath    -- ^ Directory in which to extract-               | OptLocation FilePath Bool  -- ^ Where to place file when adding files and whether to append current path+               | OptLocation FilePath !Bool -- ^ Where to place file when adding files and whether to append current path                | OptPreserveSymbolicLinks   -- ^ Preserve symbolic links as such. This option is ignored on Windows.                deriving (Read, Show, Eq) @@ -202,13 +205,9 @@ -- With earlier versions, it will always return a Right value, -- raising an error if parsing fails. toArchiveOrFail :: B.ByteString -> Either String Archive-#if MIN_VERSION_binary(0,7,0) toArchiveOrFail bs = case decodeOrFail bs of                            Left (_,_,e)  -> Left e                            Right (_,_,x) -> Right x-#else-toArchiveOrFail bs = Right $ toArchive bs-#endif  -- | Writes an 'Archive' structure to a raw zip archive (in a lazy bytestring). fromArchive :: Archive -> B.ByteString@@ -785,10 +784,10 @@       then getLazyByteString (fromIntegral compressedSize)       else -- If bit 3 of general purpose bit flag is set,            -- then we need to read until we get to the-           -- data descriptor record.  We assume that the-           -- record has signature 0x08074b50; this is not required-           -- by the specification but is common.-           do raw <- getWordsTilSig 0x08074b50+           -- data descriptor record.+           do raw <- getCompressedData+              sig <- lookAhead getWord32le+              when (sig == 0x08074b50) $ skip 4               skip 4 -- crc32               cs <- getWord32le  -- compressed size               skip 4 -- uncompressed size@@ -797,54 +796,39 @@                  else fail "Content size mismatch in data descriptor record"   return (fromIntegral offset, compressedData) -getWordsTilSig :: Word32 -> Get B.ByteString-getWordsTilSig sig = (B.fromChunks . reverse) `fmap` go Nothing []-  where-    sig' = S.pack [fromIntegral $ sig .&. 0xFF,-                   fromIntegral $ sig `shiftR`  8 .&. 0xFF,-                   fromIntegral $ sig `shiftR` 16 .&. 0xFF,-                   fromIntegral $ sig `shiftR` 24 .&. 0xFF]-    chunkSize = 16384-    --chunkSize = 4 -- for testing prefix match-    checkChunk chunk = do -- find in content-          let (prefix, start) = S.breakSubstring sig' chunk-          if S.null start-            then return $ Right chunk-            else return $ Left $ S.length prefix-    go :: Maybe (Word8, Word8, Word8) -> [S.ByteString] -> Get [S.ByteString]-    go prefixes acc = do-      -- note: lookAheadE will rewind if the result is Left-      eitherChunkOrIndex <- lookAheadE $ do-          chunk <- getByteString chunkSize <|> B.toStrict `fmap` getRemainingLazyByteString-          case prefixes of-            Just (byte3,byte2,byte1) ->-              let len = S.length chunk in-                if len >= 1 &&-                   S.pack [byte3,byte2,byte1,S.index chunk 0] == sig'-                then return $ Left $ -3-                else if len >= 2 &&-                   S.pack [byte2,byte1,S.index chunk 0,S.index chunk 1] == sig'-                then return $ Left $ -2-                else if len >= 3 &&-                   S.pack [byte1,S.index chunk 0,S.index chunk 1,S.index chunk 2] == sig'-                then return $ Left $ -1-                else checkChunk chunk-            Nothing -> checkChunk chunk-      case eitherChunkOrIndex of-        Left index -> if index < 0-            then do -- prefix match-                skip (4 + index) -- skip over partial match in next chunk-                return $ (S.take (S.length (head acc) + index) (head acc)) : (tail acc)-            else do -- match inside this chunk-                lastchunk <- getByteString index -- must read again-                skip 4-                return (lastchunk:acc)-        Right chunk -> if len == chunkSize-            then go prefixes' (chunk:acc)-            else fail $ "getWordsTilSig: signature not found before EOF"-          where-            len = S.length chunk-            prefixes' = Just $ (S.index chunk (len - 3), S.index chunk (len - 2), S.index chunk (len - 1))+-- Move forward over data (not consuming it) until:+-- - start of the next local file header+-- - start of archive decryption header+-- Then back up 12 bytes (the data description record)+-- and possibly 4 more bytes+-- (conventional but not required sig 0x08074b50 for data description record).+getCompressedData :: Get B.ByteString+getCompressedData = do+  numbytes <- lookAhead $ findEnd 0+  getLazyByteString numbytes+ where+   chunkSize :: Int64+   chunkSize = 16384+   findEnd :: Int64 -> Get Int64+   findEnd n = do+     sig <- lookAhead getWord32le+     case sig of+       0x08074b50 -> skip 4 >> return n+       0x04034b50 -> -- sig for local file header+        return (n - 12) -- rewind past data description+       0x02014b50 -> -- sig for file header+        return (n - 12) -- rewind past data description+       0x06054b50 -> -- sig for end of central directory header+        return (n - 12) -- rewind past data description+       x | x .&. 0xFF == 0x50 -> skip 1 >> findEnd (n + 1)+       _ -> do bs <- lookAhead $ getLazyByteString chunkSize+                             <|> getRemainingLazyByteString+               let bsLen = B.length bs+               let mbIdx = B.elemIndex 0x50 bs+               case mbIdx of+                 Nothing -> skip (fromIntegral bsLen) >> findEnd (n + bsLen)+                 Just 0  -> skip 1 >> findEnd (n + 1)+                 Just idx -> skip (fromIntegral idx) >> findEnd (n + idx)  putLocalFile :: Entry -> Put putLocalFile f = do
zip-archive.cabal view
@@ -1,5 +1,5 @@ Name:                zip-archive-Version:             0.4.2.2+Version:             0.4.3 Cabal-Version:       2.0 Build-type:          Simple Synopsis:            Library for creating and modifying zip archives.@@ -55,7 +55,7 @@   Build-depends:     base >= 4.5 && < 5,                      pretty,                      containers,-                     binary >= 0.6,+                     binary >= 0.7.2,                      zlib,                      filepath,                      bytestring >= 0.10.0,