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

fromDOSTime :: Word16 -> Word16 -> LocalTime
fromDOSTime :: Word16 -> Word16 -> LocalTime
fromDOSTime Word16
time Word16
date = Day -> TimeOfDay -> LocalTime
LocalTime
  (Year -> MonthOfYear -> MonthOfYear -> Day
fromGregorian
    (Word16 -> Year
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word16 -> Year) -> Word16 -> Year
forall a b. (a -> b) -> a -> b
$ Word16
date Word16 -> MonthOfYear -> Word16
forall a. Bits a => a -> MonthOfYear -> a
`shiftR` MonthOfYear
9 Word16 -> Word16 -> Word16
forall a. Num a => a -> a -> a
+ Word16
1980)
    (Word16 -> MonthOfYear
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word16 -> MonthOfYear) -> Word16 -> MonthOfYear
forall a b. (a -> b) -> a -> b
$ Word16
date Word16 -> MonthOfYear -> Word16
forall a. Bits a => a -> MonthOfYear -> a
`shiftR` MonthOfYear
5 Word16 -> Word16 -> Word16
forall a. Bits a => a -> a -> a
.&. Word16
0x0f)
    (Word16 -> MonthOfYear
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word16 -> MonthOfYear) -> Word16 -> MonthOfYear
forall a b. (a -> b) -> a -> b
$ Word16
date            Word16 -> Word16 -> Word16
forall a. Bits a => a -> a -> a
.&. Word16
0x1f))
  (MonthOfYear -> MonthOfYear -> Pico -> TimeOfDay
TimeOfDay
    (Word16 -> MonthOfYear
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word16 -> MonthOfYear) -> Word16 -> MonthOfYear
forall a b. (a -> b) -> a -> b
$ Word16
time Word16 -> MonthOfYear -> Word16
forall a. Bits a => a -> MonthOfYear -> a
`shiftR` MonthOfYear
11)
    (Word16 -> MonthOfYear
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word16 -> MonthOfYear) -> Word16 -> MonthOfYear
forall a b. (a -> b) -> a -> b
$ Word16
time Word16 -> MonthOfYear -> Word16
forall a. Bits a => a -> MonthOfYear -> a
`shiftR` MonthOfYear
5 Word16 -> Word16 -> Word16
forall a. Bits a => a -> a -> a
.&. Word16
0x3f)
    (Word16 -> Pico
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word16 -> Pico) -> Word16 -> Pico
forall a b. (a -> b) -> a -> b
$ Word16
time Word16 -> MonthOfYear -> Word16
forall a. Bits a => a -> MonthOfYear -> a
`shiftL` MonthOfYear
1 Word16 -> Word16 -> Word16
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
    h <- Get (Header m)
-> ConduitT ByteString (Either ZipEntry ByteString) m (Header m)
forall (m :: * -> *) b z.
MonadThrow m =>
Get b -> ConduitT ByteString z m b
sinkGet (Get (Header m)
 -> ConduitT ByteString (Either ZipEntry ByteString) m (Header m))
-> Get (Header m)
-> ConduitT ByteString (Either ZipEntry ByteString) m (Header m)
forall a b. (a -> b) -> a -> b
$ do
      sig <- Get Word32
G.getWord32le
      case sig of
        Word32
0x04034b50 -> Get (Header m)
fileHeader
        Word32
_ -> Word32 -> Get (Header m)
forall {m :: * -> *}. Word32 -> Get (Header m)
centralBody Word32
sig
    case h of
      FileHeader{Bool
Word32
Word64
ConduitM ByteString ByteString m ()
ZipEntry
fileDecompress :: forall (m :: * -> *).
Header m -> ConduitM ByteString ByteString m ()
fileEntry :: forall (m :: * -> *). Header m -> ZipEntry
fileCRC :: forall (m :: * -> *). Header m -> Word32
fileCSize :: forall (m :: * -> *). Header m -> Word64
fileZip64 :: forall (m :: * -> *). Header m -> Bool
fileDecompress :: ConduitM ByteString ByteString m ()
fileEntry :: ZipEntry
fileCRC :: Word32
fileCSize :: Word64
fileZip64 :: Bool
..} -> do
        Either ZipEntry ByteString
-> ConduitT ByteString (Either ZipEntry ByteString) m ()
forall (m :: * -> *) o i. Monad m => o -> ConduitT i o m ()
C.yield (Either ZipEntry ByteString
 -> ConduitT ByteString (Either ZipEntry ByteString) m ())
-> Either ZipEntry ByteString
-> ConduitT ByteString (Either ZipEntry ByteString) m ()
forall a b. (a -> b) -> a -> b
$ ZipEntry -> Either ZipEntry ByteString
forall a b. a -> Either a b
Left ZipEntry
fileEntry
        r <- (ByteString -> Either ZipEntry ByteString)
-> ConduitT ByteString ByteString m Bool
-> ConduitT ByteString (Either ZipEntry ByteString) m Bool
forall (m :: * -> *) o1 o2 i r.
Monad m =>
(o1 -> o2) -> ConduitT i o1 m r -> ConduitT i o2 m r
C.mapOutput ByteString -> Either ZipEntry ByteString
forall a b. b -> Either a b
Right (ConduitT ByteString ByteString m Bool
 -> ConduitT ByteString (Either ZipEntry ByteString) m Bool)
-> ConduitT ByteString ByteString m Bool
-> ConduitT ByteString (Either ZipEntry ByteString) m Bool
forall a b. (a -> b) -> a -> b
$
          case ZipEntry -> Maybe Word64
zipEntrySize ZipEntry
fileEntry of
            Maybe Word64
Nothing -> do -- unknown size
              (csize, (size, crc)) <- ConduitM ByteString ByteString m ()
-> ConduitT ByteString ByteString m Word64
forall (m :: * -> *) o.
Monad m =>
ConduitT ByteString o m () -> ConduitT ByteString o m Word64
inputSize ConduitM ByteString ByteString m ()
fileDecompress ConduitT ByteString ByteString m Word64
-> ConduitT ByteString ByteString m (Word64, Word32)
-> ConduitT ByteString ByteString m (Word64, (Word64, Word32))
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` ConduitT ByteString ByteString m (Word64, Word32)
forall (m :: * -> *).
Monad m =>
ConduitT ByteString ByteString m (Word64, Word32)
sizeCRC
              -- traceM $ "csize=" ++ show csize ++ " size=" ++ show size ++ " crc=" ++ show crc
              -- required data description
              sinkGet $ dataDesc h
                { fileCSize = csize
                , fileCRC = crc
                , fileEntry = fileEntry
                  { zipEntrySize = Just size
                  }
                }
            Just Word64
usize -> do -- known size
              (size, crc) <- Word64 -> ConduitM ByteString ByteString m ()
forall (m :: * -> *) n.
(MonadThrow m, Integral n) =>
n -> ConduitM ByteString ByteString m ()
pass Word64
fileCSize
                ConduitM ByteString ByteString m ()
-> ConduitT ByteString ByteString m (Word64, Word32)
-> ConduitT ByteString ByteString m (Word64, Word32)
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 ConduitM ByteString ByteString m ()
-> ConduitM ByteString ByteString m ()
-> ConduitM ByteString ByteString m ()
forall a b.
ConduitT ByteString ByteString m a
-> ConduitT ByteString ByteString m b
-> ConduitT ByteString ByteString m b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> ConduitM ByteString ByteString m ()
forall (m :: * -> *) a o. Monad m => ConduitT a o m ()
CC.sinkNull)
                ConduitM ByteString ByteString m ()
-> ConduitT ByteString ByteString m (Word64, Word32)
-> ConduitT ByteString ByteString m (Word64, Word32)
forall (m :: * -> *) a b c r.
Monad m =>
ConduitT a b m () -> ConduitT b c m r -> ConduitT a c m r
C..| ConduitT ByteString ByteString m (Word64, Word32)
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!)
              sinkGet $ (guard =<< dataDesc h) <|> return ()
              return (size == usize && crc == fileCRC)
        unless r $ zipError $ either T.unpack BSC.unpack (zipEntryName fileEntry) ++ ": data integrity check failed"
        next
      EndOfCentralDirectory{ZipInfo
endInfo :: forall (m :: * -> *). Header m -> ZipInfo
endInfo :: ZipInfo
..} -> do
        ZipInfo
-> ConduitT ByteString (Either ZipEntry ByteString) m ZipInfo
forall a. a -> ConduitT ByteString (Either ZipEntry ByteString) m a
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
      sig <- Get Word32
G.getWord32le
      guard (sig == 0x08074b50)
      dataDescBody h)
    Get Bool -> Get Bool -> Get Bool
forall a. Get a -> Get a -> Get a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Header m -> Get Bool
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
fileDecompress :: forall (m :: * -> *).
Header m -> ConduitM ByteString ByteString m ()
fileEntry :: forall (m :: * -> *). Header m -> ZipEntry
fileCRC :: forall (m :: * -> *). Header m -> Word32
fileCSize :: forall (m :: * -> *). Header m -> Word64
fileZip64 :: forall (m :: * -> *). Header m -> Bool
fileDecompress :: ConduitM ByteString ByteString m ()
fileEntry :: ZipEntry
fileCRC :: Word32
fileCSize :: Word64
fileZip64 :: Bool
..} = do
    crc <- Get Word32
G.getWord32le
    let getSize = if Bool
fileZip64 then Get Word64
G.getWord64le else Word32 -> Word64
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word32 -> Word64) -> Get Word32 -> Get Word64
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get Word32
G.getWord32le
    csiz <- getSize
    usiz <- getSize
    -- traceM $ "crc=" ++ show crc ++ "," ++ show fileCRC ++ " csiz=" ++ show csiz ++ "," ++ show fileCSize ++ " usiz=" ++ show usiz ++ "," ++ show (zipEntrySize fileEntry)
    return $ crc == fileCRC && csiz == fileCSize && (usiz ==) `all` zipEntrySize fileEntry
  dataDescBody Header m
_ = Get Bool
forall a. Get a
forall (f :: * -> *) a. Alternative f => f a
empty
  central :: Get (Header m)
central = Get Word32
G.getWord32le Get Word32 -> (Word32 -> Get (Header m)) -> Get (Header m)
forall a b. Get a -> (a -> Get b) -> Get b
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 Get () -> Get (Header m) -> Get (Header m)
forall a b. Get a -> Get b -> Get b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Get (Header m)
central
  centralBody Word32
0x06064b50 = Get ()
zip64EndDirectory Get () -> Get (Header m) -> Get (Header m)
forall a b. Get a -> Get b -> Get b
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 Get () -> Get (Header m) -> Get (Header m)
forall a b. Get a -> Get b -> Get b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Get (Header m)
central
  centralBody Word32
0x06054b50 = ZipInfo -> Header m
forall (m :: * -> *). ZipInfo -> Header m
EndOfCentralDirectory (ZipInfo -> Header m) -> Get ZipInfo -> Get (Header m)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get ZipInfo
endDirectory
  centralBody Word32
sig = String -> Get (Header m)
forall a. String -> Get a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String -> Get (Header m)) -> String -> Get (Header m)
forall a b. (a -> b) -> a -> b
$ String
"Unknown header signature: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Word32 -> String
forall a. Show a => a -> String
show Word32
sig
  fileHeader :: Get (Header m)
fileHeader = do
    ver <- Get Word8
G.getWord8
    _os <- G.getWord8 -- OS Version (could require 0 = DOS, but we ignore ext attrs altogether)
    when (ver > zipVersion) $ fail $ "Unsupported version: " ++ show ver
    gpf <- G.getWord16le
    -- when (gpf .&. complement (bit 1 .|. bit 2 .|. bit 3) /= 0) $ fail $ "Unsupported flags: " ++ show gpf
    when (gpf `clearBit` 1 `clearBit` 2 `clearBit` 3 `clearBit` 11 /= 0) $ fail $ "Unsupported flags: " ++ show gpf
    comp <- G.getWord16le
    dcomp <- case comp of
      Word16
0 | Word16 -> MonthOfYear -> Bool
forall a. Bits a => a -> MonthOfYear -> Bool
testBit Word16
gpf MonthOfYear
3 -> String -> Get (ConduitM ByteString ByteString m ())
forall a. String -> Get a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"Unsupported uncompressed streaming file data"
        | Bool
otherwise -> ConduitM ByteString ByteString m ()
-> Get (ConduitM ByteString ByteString m ())
forall a. a -> Get a
forall (m :: * -> *) a. Monad m => a -> m a
return ConduitM ByteString ByteString m ()
forall (m :: * -> *) a. Monad m => ConduitT a a m ()
idConduit
      Word16
8 -> ConduitM ByteString ByteString m ()
-> Get (ConduitM ByteString ByteString m ())
forall a. a -> Get a
forall (m :: * -> *) a. Monad m => a -> m a
return (ConduitM ByteString ByteString m ()
 -> Get (ConduitM ByteString ByteString m ()))
-> ConduitM ByteString ByteString m ()
-> Get (ConduitM ByteString ByteString m ())
forall a b. (a -> b) -> a -> b
$ WindowBits -> ConduitM ByteString ByteString m ()
forall (m :: * -> *).
(PrimMonad m, MonadThrow m) =>
WindowBits -> ConduitT ByteString ByteString m ()
CZ.decompress WindowBits
deflateWindowBits
      Word16
_ -> String -> Get (ConduitM ByteString ByteString m ())
forall a. String -> Get a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String -> Get (ConduitM ByteString ByteString m ()))
-> String -> Get (ConduitM ByteString ByteString m ())
forall a b. (a -> b) -> a -> b
$ String
"Unsupported compression method: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Word16 -> String
forall a. Show a => a -> String
show Word16
comp
    time <- fromDOSTime <$> G.getWord16le <*> G.getWord16le
    crc <- G.getWord32le
    csiz <- G.getWord32le
    usiz <- G.getWord32le
    nlen <- fromIntegral <$> G.getWord16le
    elen <- fromIntegral <$> G.getWord16le
    name <- G.getByteString nlen
    let getExt ExtField
ext = do
          t <- Get Word16
G.getWord16le
          z <- fromIntegral <$> G.getWord16le
          G.isolate z $ case 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"
              usiz' <- if Word32
usiz Word32 -> Word32 -> Bool
forall a. Eq a => a -> a -> Bool
== Word32
forall n. Integral n => n
maxBound32 then Get Word64
G.getWord64le else Word64 -> Get Word64
forall a. a -> Get a
forall (m :: * -> *) a. Monad m => a -> m a
return (Word64 -> Get Word64) -> Word64 -> Get Word64
forall a b. (a -> b) -> a -> b
$ ExtField -> Word64
extZip64USize ExtField
ext
              csiz' <- if csiz == maxBound32 then G.getWord64le else return $ extZip64CSize ext
              return ext
                { extZip64 = True
                , extZip64USize = usiz'
                , extZip64CSize = 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 ExtField -> Get () -> Get ExtField
forall a b. a -> Get b -> Get a
forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ MonthOfYear -> Get ()
G.skip MonthOfYear
z
    ExtField{..} <- G.isolate elen $ foldGet getExt ExtField
      { extZip64 = False
      , extZip64USize = fromIntegral usiz
      , extZip64CSize = fromIntegral csiz
      }
    return FileHeader
      { fileEntry = ZipEntry
        { zipEntryName = if testBit gpf 11 then Left (TE.decodeUtf8 name) else Right name
        , zipEntryTime = time
        , zipEntrySize = if testBit gpf 3 then Nothing else Just extZip64USize
        , zipEntryExternalAttributes = Nothing
        }
      , fileDecompress = dcomp
      , fileCSize = extZip64CSize
      , fileCRC = crc
      , fileZip64 = extZip64
      }
  centralHeader :: Get ()
centralHeader = do
    -- ignore everything
    MonthOfYear -> Get ()
G.skip MonthOfYear
24
    nlen <- Word16 -> MonthOfYear
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word16 -> MonthOfYear) -> Get Word16 -> Get MonthOfYear
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get Word16
G.getWord16le
    elen <- fromIntegral <$> G.getWord16le
    clen <- fromIntegral <$> G.getWord16le
    G.skip $ 12 + nlen + elen + clen
  zip64EndDirectory :: Get ()
zip64EndDirectory = do
    len <- Get Word64
G.getWord64le
    G.skip $ fromIntegral len -- would not expect to overflow...
  endDirectory :: Get ZipInfo
endDirectory = do
    MonthOfYear -> Get ()
G.skip MonthOfYear
16
    clen <- Word16 -> MonthOfYear
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word16 -> MonthOfYear) -> Get Word16 -> Get MonthOfYear
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get Word16
G.getWord16le
    comm <- G.getByteString clen
    return ZipInfo
      { zipComment = comm
      }