    -- |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
          }