packages feed

hOpenPGP-3.6: Codec/Encryption/OpenPGP/Compression.hs

-- Compression.hs: OpenPGP (RFC9580) compression and decompression
-- Copyright © 2012-2026  Clint Adams
-- This software is released under the terms of the Expat license.
-- (See the LICENSE file).

module Codec.Encryption.OpenPGP.Compression
    ( decompressPkt
    , compressPkts
    ) where

import qualified Codec.Compression.BZip as BZip
import qualified Codec.Compression.Zlib as Zlib
import qualified Codec.Compression.Zlib.Raw as ZlibRaw
import Data.Binary (get, put)
import Data.Binary.Get (runGetOrFail)
import Data.Binary.Put (runPut)
import qualified Data.ByteString.Lazy as BL

import Codec.Encryption.OpenPGP.Serialize ()
import Codec.Encryption.OpenPGP.Types
import Codec.Encryption.OpenPGP.Types.Internal.Errors
    ( CompressionError (..)
    , renderCompressionError
    )

{- | Decompress an OpenPGP Compressed Data packet, classifying structural
failures as 'CompressionErrorReason.  Non-Compressed-Data packets are returned
unchanged in @Right [p]@.  Corrupt compressed streams may still throw
'IOException' from the underlying decompression library.
-}
decompressPkt :: Pkt -> Either CompressionError [Pkt]
decompressPkt compressed@(CompressedDataPkt (OtherCA _) _) = Right [compressed]
decompressPkt (CompressedDataPkt algo bs)
    | BL.null bs = Left (EmptyCompressedPayload algo)
    | otherwise =
        case runGetOrFail get (dfunc algo bs) of
            Left (_, _, err) -> Left (InnerPacketParseFailed algo err)
            Right (_, _, packs) ->
                let pkts = unBlock packs
                 in case pkts of
                        [] -> Left (ZeroLengthDecompressedPayload algo)
                        _ | all isMarkerPkt pkts -> Left (MarkerOnlyPayload algo)
                        _ -> Right pkts
  where
    dfunc Uncompressed = id
    dfunc ZIP = ZlibRaw.decompress
    dfunc ZLIB = Zlib.decompress
    dfunc BZip2 = BZip.decompress
    dfunc (OtherCA _) = id
decompressPkt p = Right [p]

isMarkerPkt :: Pkt -> Bool
isMarkerPkt (MarkerPkt _) = True
isMarkerPkt _ = False

compressPkts :: CompressionAlgorithm -> [Pkt] -> Pkt
compressPkts ca packs =
    let bs = runPut $ put (Block packs)
        cbs = cfunc ca bs
        outAlgo =
            if isSupportedCompressionAlgorithm ca then ca else Uncompressed
     in CompressedDataPkt outAlgo cbs
  where
    cfunc Uncompressed = id
    cfunc ZIP = ZlibRaw.compress
    cfunc ZLIB = Zlib.compress
    cfunc BZip2 = BZip.compress
    cfunc _ = id

isSupportedCompressionAlgorithm :: CompressionAlgorithm -> Bool
isSupportedCompressionAlgorithm Uncompressed = True
isSupportedCompressionAlgorithm ZIP = True
isSupportedCompressionAlgorithm ZLIB = True
isSupportedCompressionAlgorithm BZip2 = True
isSupportedCompressionAlgorithm (OtherCA _) = False