-- |Stream the extraction of a zip file, e.g., as it's being downloaded.
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE RankNTypes #-}
module Codec.Archive.Zip.Conduit.UnZip
  ( unZipStream
  , ZipEntry(..)
  , ZipInfo(..)
  ) where

import           Control.Applicative ((<|>), empty)
import           Control.Monad (when, unless, guard)
import           Control.Monad.Catch (MonadThrow)
import           Control.Monad.Primitive (PrimMonad)
import qualified Data.Binary.Get as G
import           Data.Bits ((.&.), testBit, clearBit, shiftL, shiftR)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BSC
import qualified Data.Conduit as C
import qualified Data.Conduit.Combinators as CC
import           Data.Conduit.Serialization.Binary (sinkGet)
import qualified Data.Conduit.Zlib as CZ
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import           Data.Time (LocalTime(..), TimeOfDay(..), fromGregorian)
import           Data.Word (Word16, Word32, Word64)

import           Codec.Archive.Zip.Conduit.Types
import           Codec.Archive.Zip.Conduit.Internal

data Header m
  = FileHeader
    { forall (m :: * -> *).
Header m -> ConduitM ByteString ByteString m ()
fileDecompress :: C.ConduitM BS.ByteString BS.ByteString m ()
    , forall (m :: * -> *). Header m -> ZipEntry
fileEntry :: !ZipEntry
    , forall (m :: * -> *). Header m -> Word32
fileCRC :: !Word32
    , forall (m :: * -> *). Header m -> Word64
fileCSize :: !Word64
    , forall (m :: * -> *). Header m -> Bool
fileZip64 :: !Bool
    }
  | EndOfCentralDirectory
    { forall (m :: * -> *). Header m -> ZipInfo
endInfo :: ZipInfo
    }

data ExtField = ExtField
  { ExtField -> Bool
extZip64 :: Bool
  , ExtField -> Word64
extZip64USize
  , ExtField -> Word64
extZip64CSize :: Word64
  }

{- ExtUnix
  { extUnixATime
  , extUnixMTime :: UTCTime
  , extUnixUID
  , extUnixGID :: Word16
  , extUnixData :: BS.ByteString
  }
-}

pass :: (MonadThrow m, Integral n) => n -> C.ConduitM BS.ByteString BS.ByteString m ()
pass :: forall (m :: * -> *) n.
(MonadThrow m, Integral n) =>
n -> ConduitM ByteString ByteString m ()
pass n
0 = forall (m :: * -> *) a. Monad m => a -> m a
return ()
pass n
n = forall (m :: * -> *) i o. Monad m => ConduitT i o m (Maybe i)
C.await forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= forall b a. b -> (a -> b) -> Maybe a -> b
maybe
  (forall (m :: * -> *) a. MonadThrow m => String -> m a
zipError forall a b. (a -> b) -> a -> b
$ String
"EOF in file data, expecting " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show Year
ni forall a. [a] -> [a] -> [a]
++ String
" more bytes")
  (\ByteString
b ->
    let n' :: Year
n' = Year
ni forall a. Num a => a -> a -> a
- forall a. Integral a => a -> Year
toInteger (ByteString -> MonthOfYear
BS.length ByteString
b) in
    if Year
n' forall a. Ord a => a -> a -> Bool
< Year
0
      then do
        let (ByteString
b', ByteString
r) = MonthOfYear -> ByteString -> (ByteString, ByteString)
BS.splitAt (forall a b. (Integral a, Num b) => a -> b
fromIntegral n
n) ByteString
b
        forall (m :: * -> *) o i. Monad m => o -> ConduitT i o m ()
C.yield ByteString
b'
        forall i o (m :: * -> *). i -> ConduitT i o m ()
C.leftover ByteString
r
      else do
        forall (m :: * -> *) o i. Monad m => o -> ConduitT i o m ()
C.yield ByteString
b
        forall (m :: * -> *) n.
(MonadThrow m, Integral n) =>
n -> ConduitM ByteString ByteString m ()
pass Year
n')
  where ni :: Year
ni = forall a. Integral a => a -> Year
toInteger n
n

foldGet :: (a -> G.Get a) -> a -> G.Get a
foldGet :: forall a. (a -> Get a) -> a -> Get a
foldGet a -> Get a
g a
z = do
  Bool
e <- Get Bool
G.isEmpty
  if Bool
e then forall (m :: * -> *) a. Monad m => a -> m a
return a
z else a -> Get a
g a
z forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= forall a. (a -> Get a) -> a -> Get a
foldGet a -> Get a
g

fromDOSTime :: Word16 -> Word16 -> LocalTime
fromDOSTime :: Word16 -> Word16 -> LocalTime
fromDOSTime Word16
time Word16
date = Day -> TimeOfDay -> LocalTime
LocalTime
  (Year -> MonthOfYear -> MonthOfYear -> Day
fromGregorian
    (forall a b. (Integral a, Num b) => a -> b
fromIntegral forall a b. (a -> b) -> a -> b
$ Word16
date forall a. Bits a => a -> MonthOfYear -> a
`shiftR` MonthOfYear
9 forall a. Num a => a -> a -> a
+ Word16
1980)
    (forall a b. (Integral a, Num b) => a -> b
fromIntegral forall a b. (a -> b) -> a -> b
$ Word16
date forall a. Bits a => a -> MonthOfYear -> a
`shiftR` MonthOfYear
5 forall a. Bits a => a -> a -> a
.&. Word16
0x0f)
    (forall a b. (Integral a, Num b) => a -> b
fromIntegral forall a b. (a -> b) -> a -> b
$ Word16
date            forall a. Bits a => a -> a -> a
.&. Word16
0x1f))
  (MonthOfYear -> MonthOfYear -> Pico -> TimeOfDay
TimeOfDay
    (forall a b. (Integral a, Num b) => a -> b
fromIntegral forall a b. (a -> b) -> a -> b
$ Word16
time forall a. Bits a => a -> MonthOfYear -> a
`shiftR` MonthOfYear
11)
    (forall a b. (Integral a, Num b) => a -> b
fromIntegral forall a b. (a -> b) -> a -> b
$ Word16
time forall a. Bits a => a -> MonthOfYear -> a
`shiftR` MonthOfYear
5 forall a. Bits a => a -> a -> a
.&. Word16
0x3f)
    (forall a b. (Integral a, Num b) => a -> b
fromIntegral forall a b. (a -> b) -> a -> b
$ Word16
time forall a. Bits a => a -> MonthOfYear -> a
`shiftL` MonthOfYear
1 forall a. Bits a => a -> a -> a
.&. Word16
0x3f))

-- |Stream process a zip file, producing a sequence of entry headers and data blocks.
-- For example, this might produce: @Left (ZipEntry "directory\/" ...), Left (ZipEntry "directory\/file.txt" ...), Right "hello w", Right "orld!\\n", Left ...@
-- The final result is summary information taken from the end of the zip file.
-- No state is maintained during processing, and, in particular, any information in the central directory is discarded.
--
-- This only supports a limited number of zip file features, including deflate compression and zip64.
-- It does not (ironically) support uncompressed zip files that have been created as streams, where file sizes are not known beforehand.
-- Since it does not use the offset information at the end of the file, it assumes all entries are packed sequentially, which is usually the case.
-- Any errors are thrown in the underlying monad (as 'ZipError's or 'Data.Conduit.Serialization.Binary.ParseError').
unZipStream ::
  ( MonadThrow m
  , PrimMonad m
  ) => C.ConduitM BS.ByteString (Either ZipEntry BS.ByteString) m ZipInfo
unZipStream :: forall (m :: * -> *).
(MonadThrow m, PrimMonad m) =>
ConduitM ByteString (Either ZipEntry ByteString) m ZipInfo
unZipStream = ConduitT ByteString (Either ZipEntry ByteString) m ZipInfo
next where
  next :: ConduitT ByteString (Either ZipEntry ByteString) m ZipInfo
next = do -- local header, or start central directory
    Header m
h <- forall (m :: * -> *) b z.
MonadThrow m =>
Get b -> ConduitT ByteString z m b
sinkGet forall a b. (a -> b) -> a -> b
$ do
      Word32
sig <- Get Word32
G.getWord32le
      case Word32
sig of
        Word32
0x04034b50 -> Get (Header m)
fileHeader
        Word32
_ -> forall {m :: * -> *}. Word32 -> Get (Header m)
centralBody Word32
sig
    case Header m
h of
      FileHeader{Bool
Word32
Word64
ConduitM ByteString ByteString m ()
ZipEntry
fileZip64 :: Bool
fileCSize :: Word64
fileCRC :: Word32
fileEntry :: ZipEntry
fileDecompress :: ConduitM ByteString ByteString m ()
fileZip64 :: forall (m :: * -> *). Header m -> Bool
fileCSize :: forall (m :: * -> *). Header m -> Word64
fileCRC :: forall (m :: * -> *). Header m -> Word32
fileEntry :: forall (m :: * -> *). Header m -> ZipEntry
fileDecompress :: forall (m :: * -> *).
Header m -> ConduitM ByteString ByteString m ()
..} -> do
        forall (m :: * -> *) o i. Monad m => o -> ConduitT i o m ()
C.yield forall a b. (a -> b) -> a -> b
$ forall a b. a -> Either a b
Left ZipEntry
fileEntry
        Bool
r <- forall (m :: * -> *) o1 o2 i r.
Monad m =>
(o1 -> o2) -> ConduitT i o1 m r -> ConduitT i o2 m r
C.mapOutput forall a b. b -> Either a b
Right forall a b. (a -> b) -> a -> b
$
          case ZipEntry -> Maybe Word64
zipEntrySize ZipEntry
fileEntry of
            Maybe Word64
Nothing -> do -- unknown size
              (Word64
csize, (Word64
size, Word32
crc)) <- forall (m :: * -> *) o.
Monad m =>
ConduitT ByteString o m () -> ConduitT ByteString o m Word64
inputSize ConduitM ByteString ByteString m ()
fileDecompress forall (m :: * -> *) a b r1 c r2.
Monad m =>
ConduitT a b m r1 -> ConduitT b c m r2 -> ConduitT a c m (r1, r2)
`C.fuseBoth` forall (m :: * -> *).
Monad m =>
ConduitT ByteString ByteString m (Word64, Word32)
sizeCRC
              -- traceM $ "csize=" ++ show csize ++ " size=" ++ show size ++ " crc=" ++ show crc
              -- required data description
              forall (m :: * -> *) b z.
MonadThrow m =>
Get b -> ConduitT ByteString z m b
sinkGet forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}. Header m -> Get Bool
dataDesc Header m
h
                { fileCSize :: Word64
fileCSize = Word64
csize
                , fileCRC :: Word32
fileCRC = Word32
crc
                , fileEntry :: ZipEntry
fileEntry = ZipEntry
fileEntry
                  { zipEntrySize :: Maybe Word64
zipEntrySize = forall a. a -> Maybe a
Just Word64
size
                  }
                }
            Just Word64
usize -> do -- known size
              (Word64
size, Word32
crc) <- forall (m :: * -> *) n.
(MonadThrow m, Integral n) =>
n -> ConduitM ByteString ByteString m ()
pass Word64
fileCSize
                forall (m :: * -> *) a b c r.
Monad m =>
ConduitT a b m () -> ConduitT b c m r -> ConduitT a c m r
C..| (ConduitM ByteString ByteString m ()
fileDecompress forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> forall (m :: * -> *) a o. Monad m => ConduitT a o m ()
CC.sinkNull)
                forall (m :: * -> *) a b c r.
Monad m =>
ConduitT a b m () -> ConduitT b c m r -> ConduitT a c m r
C..| forall (m :: * -> *).
Monad m =>
ConduitT ByteString ByteString m (Word64, Word32)
sizeCRC
              -- traceM $ "size=" ++ show size ++ "," ++ show (zipEntrySize fileEntry) ++ " crc=" ++ show crc ++ "," ++ show fileCRC
              -- optional data description (possibly ambiguous!)
              forall (m :: * -> *) b z.
MonadThrow m =>
Get b -> ConduitT ByteString z m b
sinkGet forall a b. (a -> b) -> a -> b
$ (forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< forall {m :: * -> *}. Header m -> Get Bool
dataDesc Header m
h) forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> forall (m :: * -> *) a. Monad m => a -> m a
return ()
              forall (m :: * -> *) a. Monad m => a -> m a
return (Word64
size forall a. Eq a => a -> a -> Bool
== Word64
usize Bool -> Bool -> Bool
&& Word32
crc forall a. Eq a => a -> a -> Bool
== Word32
fileCRC)
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
r forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. MonadThrow m => String -> m a
zipError forall a b. (a -> b) -> a -> b
$ forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either Text -> String
T.unpack ByteString -> String
BSC.unpack (ZipEntry -> Either Text ByteString
zipEntryName ZipEntry
fileEntry) forall a. [a] -> [a] -> [a]
++ String
": data integrity check failed"
        ConduitT ByteString (Either ZipEntry ByteString) m ZipInfo
next
      EndOfCentralDirectory{ZipInfo
endInfo :: ZipInfo
endInfo :: forall (m :: * -> *). Header m -> ZipInfo
..} -> do
        forall (m :: * -> *) a. Monad m => a -> m a
return ZipInfo
endInfo
  dataDesc :: Header m -> Get Bool
dataDesc Header m
h = -- this takes a bit of flexibility to account for the various cases
    (do -- with signature
      Word32
sig <- Get Word32
G.getWord32le
      forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Word32
sig forall a. Eq a => a -> a -> Bool
== Word32
0x08074b50)
      forall {m :: * -> *}. Header m -> Get Bool
dataDescBody Header m
h)
    forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> forall {m :: * -> *}. Header m -> Get Bool
dataDescBody Header m
h -- without signature
  dataDescBody :: Header m -> Get Bool
dataDescBody FileHeader{Bool
Word32
Word64
ConduitM ByteString ByteString m ()
ZipEntry
fileZip64 :: Bool
fileCSize :: Word64
fileCRC :: Word32
fileEntry :: ZipEntry
fileDecompress :: ConduitM ByteString ByteString m ()
fileZip64 :: forall (m :: * -> *). Header m -> Bool
fileCSize :: forall (m :: * -> *). Header m -> Word64
fileCRC :: forall (m :: * -> *). Header m -> Word32
fileEntry :: forall (m :: * -> *). Header m -> ZipEntry
fileDecompress :: forall (m :: * -> *).
Header m -> ConduitM ByteString ByteString m ()
..} = do
    Word32
crc <- Get Word32
G.getWord32le
    let getSize :: Get Word64
getSize = if Bool
fileZip64 then Get Word64
G.getWord64le else forall a b. (Integral a, Num b) => a -> b
fromIntegral forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get Word32
G.getWord32le
    Word64
csiz <- Get Word64
getSize
    Word64
usiz <- Get Word64
getSize
    -- traceM $ "crc=" ++ show crc ++ "," ++ show fileCRC ++ " csiz=" ++ show csiz ++ "," ++ show fileCSize ++ " usiz=" ++ show usiz ++ "," ++ show (zipEntrySize fileEntry)
    forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Word32
crc forall a. Eq a => a -> a -> Bool
== Word32
fileCRC Bool -> Bool -> Bool
&& Word64
csiz forall a. Eq a => a -> a -> Bool
== Word64
fileCSize Bool -> Bool -> Bool
&& (Word64
usiz forall a. Eq a => a -> a -> Bool
==) forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
`all` ZipEntry -> Maybe Word64
zipEntrySize ZipEntry
fileEntry
  dataDescBody Header m
_ = forall (f :: * -> *) a. Alternative f => f a
empty
  central :: Get (Header m)
central = Get Word32
G.getWord32le forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Word32 -> Get (Header m)
centralBody
  centralBody :: Word32 -> Get (Header m)
centralBody Word32
0x02014b50 = Get ()
centralHeader forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Get (Header m)
central
  centralBody Word32
0x06064b50 = Get ()
zip64EndDirectory forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Get (Header m)
central
  centralBody Word32
0x07064b50 = MonthOfYear -> Get ()
G.skip MonthOfYear
16 forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Get (Header m)
central
  centralBody Word32
0x06054b50 = forall (m :: * -> *). ZipInfo -> Header m
EndOfCentralDirectory forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get ZipInfo
endDirectory
  centralBody Word32
sig = forall (m :: * -> *) a. MonadFail m => String -> m a
fail forall a b. (a -> b) -> a -> b
$ String
"Unknown header signature: " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show Word32
sig
  fileHeader :: Get (Header m)
fileHeader = do
    Word8
ver <- Get Word8
G.getWord8
    Word8
_os <- Get Word8
G.getWord8 -- OS Version (could require 0 = DOS, but we ignore ext attrs altogether)
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Word8
ver forall a. Ord a => a -> a -> Bool
> Word8
zipVersion) forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. MonadFail m => String -> m a
fail forall a b. (a -> b) -> a -> b
$ String
"Unsupported version: " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show Word8
ver
    Word16
gpf <- Get Word16
G.getWord16le
    -- when (gpf .&. complement (bit 1 .|. bit 2 .|. bit 3) /= 0) $ fail $ "Unsupported flags: " ++ show gpf
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Word16
gpf forall a. Bits a => a -> MonthOfYear -> a
`clearBit` MonthOfYear
1 forall a. Bits a => a -> MonthOfYear -> a
`clearBit` MonthOfYear
2 forall a. Bits a => a -> MonthOfYear -> a
`clearBit` MonthOfYear
3 forall a. Bits a => a -> MonthOfYear -> a
`clearBit` MonthOfYear
11 forall a. Eq a => a -> a -> Bool
/= Word16
0) forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. MonadFail m => String -> m a
fail forall a b. (a -> b) -> a -> b
$ String
"Unsupported flags: " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show Word16
gpf
    Word16
comp <- Get Word16
G.getWord16le
    ConduitM ByteString ByteString m ()
dcomp <- case Word16
comp of
      Word16
0 | forall a. Bits a => a -> MonthOfYear -> Bool
testBit Word16
gpf MonthOfYear
3 -> forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"Unsupported uncompressed streaming file data"
        | Bool
otherwise -> forall (m :: * -> *) a. Monad m => a -> m a
return forall (m :: * -> *) a. Monad m => ConduitT a a m ()
idConduit
      Word16
8 -> forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
(PrimMonad m, MonadThrow m) =>
WindowBits -> ConduitT ByteString ByteString m ()
CZ.decompress WindowBits
deflateWindowBits
      Word16
_ -> forall (m :: * -> *) a. MonadFail m => String -> m a
fail forall a b. (a -> b) -> a -> b
$ String
"Unsupported compression method: " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show Word16
comp
    LocalTime
time <- Word16 -> Word16 -> LocalTime
fromDOSTime forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get Word16
G.getWord16le forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Get Word16
G.getWord16le
    Word32
crc <- Get Word32
G.getWord32le
    Word32
csiz <- Get Word32
G.getWord32le
    Word32
usiz <- Get Word32
G.getWord32le
    MonthOfYear
nlen <- forall a b. (Integral a, Num b) => a -> b
fromIntegral forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get Word16
G.getWord16le
    MonthOfYear
elen <- forall a b. (Integral a, Num b) => a -> b
fromIntegral forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get Word16
G.getWord16le
    ByteString
name <- MonthOfYear -> Get ByteString
G.getByteString MonthOfYear
nlen
    let getExt :: ExtField -> Get ExtField
getExt ExtField
ext = do
          Word16
t <- Get Word16
G.getWord16le
          MonthOfYear
z <- forall a b. (Integral a, Num b) => a -> b
fromIntegral forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get Word16
G.getWord16le
          forall a. MonthOfYear -> Get a -> Get a
G.isolate MonthOfYear
z forall a b. (a -> b) -> a -> b
$ case Word16
t of
            Word16
0x0001 -> do
              -- the zip specs claim "the Local header MUST include BOTH" but "only if the corresponding field is set to 0xFFFFFFFF"
              Word64
usiz' <- if Word32
usiz forall a. Eq a => a -> a -> Bool
== forall n. Integral n => n
maxBound32 then Get Word64
G.getWord64le else forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ ExtField -> Word64
extZip64USize ExtField
ext
              Word64
csiz' <- if Word32
csiz forall a. Eq a => a -> a -> Bool
== forall n. Integral n => n
maxBound32 then Get Word64
G.getWord64le else forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ ExtField -> Word64
extZip64CSize ExtField
ext
              forall (m :: * -> *) a. Monad m => a -> m a
return ExtField
ext
                { extZip64 :: Bool
extZip64 = Bool
True
                , extZip64USize :: Word64
extZip64USize = Word64
usiz'
                , extZip64CSize :: Word64
extZip64CSize = Word64
csiz'
                }
            {-
            0x000d -> do
              atim <- G.getWord32le
              mtim <- G.getWord32le
              uid <- G.getWord16le
              gid <- G.getWord16le
              dat <- G.getByteString $ z - 12
              return ExtUnix
                { extUnixATime = posixSecondsToUTCTime atim
                , extUnixMTime = posixSecondsToUTCTime mtim
                , extUnixUID = uid
                , extUnixGID = gid
                , extUnixData = dat
                }
            -}
            Word16
_ -> ExtField
ext forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ MonthOfYear -> Get ()
G.skip MonthOfYear
z
    ExtField{Bool
Word64
extZip64CSize :: Word64
extZip64USize :: Word64
extZip64 :: Bool
extZip64CSize :: ExtField -> Word64
extZip64USize :: ExtField -> Word64
extZip64 :: ExtField -> Bool
..} <- forall a. MonthOfYear -> Get a -> Get a
G.isolate MonthOfYear
elen forall a b. (a -> b) -> a -> b
$ forall a. (a -> Get a) -> a -> Get a
foldGet ExtField -> Get ExtField
getExt ExtField
      { extZip64 :: Bool
extZip64 = Bool
False
      , extZip64USize :: Word64
extZip64USize = forall a b. (Integral a, Num b) => a -> b
fromIntegral Word32
usiz
      , extZip64CSize :: Word64
extZip64CSize = forall a b. (Integral a, Num b) => a -> b
fromIntegral Word32
csiz
      }
    forall (m :: * -> *) a. Monad m => a -> m a
return FileHeader
      { fileEntry :: ZipEntry
fileEntry = ZipEntry
        { zipEntryName :: Either Text ByteString
zipEntryName = if forall a. Bits a => a -> MonthOfYear -> Bool
testBit Word16
gpf MonthOfYear
11 then forall a b. a -> Either a b
Left (ByteString -> Text
TE.decodeUtf8 ByteString
name) else forall a b. b -> Either a b
Right ByteString
name
        , zipEntryTime :: LocalTime
zipEntryTime = LocalTime
time
        , zipEntrySize :: Maybe Word64
zipEntrySize = if forall a. Bits a => a -> MonthOfYear -> Bool
testBit Word16
gpf MonthOfYear
3 then forall a. Maybe a
Nothing else forall a. a -> Maybe a
Just Word64
extZip64USize
        , zipEntryExternalAttributes :: Maybe Word32
zipEntryExternalAttributes = forall a. Maybe a
Nothing
        }
      , fileDecompress :: ConduitM ByteString ByteString m ()
fileDecompress = ConduitM ByteString ByteString m ()
dcomp
      , fileCSize :: Word64
fileCSize = Word64
extZip64CSize
      , fileCRC :: Word32
fileCRC = Word32
crc
      , fileZip64 :: Bool
fileZip64 = Bool
extZip64
      }
  centralHeader :: Get ()
centralHeader = do
    -- ignore everything
    MonthOfYear -> Get ()
G.skip MonthOfYear
24
    MonthOfYear
nlen <- forall a b. (Integral a, Num b) => a -> b
fromIntegral forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get Word16
G.getWord16le
    MonthOfYear
elen <- forall a b. (Integral a, Num b) => a -> b
fromIntegral forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get Word16
G.getWord16le
    MonthOfYear
clen <- forall a b. (Integral a, Num b) => a -> b
fromIntegral forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get Word16
G.getWord16le
    MonthOfYear -> Get ()
G.skip forall a b. (a -> b) -> a -> b
$ MonthOfYear
12 forall a. Num a => a -> a -> a
+ MonthOfYear
nlen forall a. Num a => a -> a -> a
+ MonthOfYear
elen forall a. Num a => a -> a -> a
+ MonthOfYear
clen
  zip64EndDirectory :: Get ()
zip64EndDirectory = do
    Word64
len <- Get Word64
G.getWord64le
    MonthOfYear -> Get ()
G.skip forall a b. (a -> b) -> a -> b
$ forall a b. (Integral a, Num b) => a -> b
fromIntegral Word64
len -- would not expect to overflow...
  endDirectory :: Get ZipInfo
endDirectory = do
    MonthOfYear -> Get ()
G.skip MonthOfYear
16
    MonthOfYear
clen <- forall a b. (Integral a, Num b) => a -> b
fromIntegral forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get Word16
G.getWord16le
    ByteString
comm <- MonthOfYear -> Get ByteString
G.getByteString MonthOfYear
clen
    forall (m :: * -> *) a. Monad m => a -> m a
return ZipInfo
      { zipComment :: ByteString
zipComment = ByteString
comm
      }