packages feed

nova-cache-0.4.1.0: src/NovaCache/Compression.hs

{-# LANGUAGE ScopedTypeVariables #-}

-- | xz compression and decompression for NAR files.
--
-- Thin wrappers around the @lzma@ package, converting between strict
-- 'ByteString' and the underlying lazy interface.
module NovaCache.Compression
  ( compressXz,
    decompressXz,
  )
where

import qualified Codec.Compression.Lzma as Lzma
import Control.Exception (SomeAsyncException, SomeException, evaluate, fromException, throwIO, try)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as BL

-- | Compress a strict 'ByteString' with xz.
compressXz :: ByteString -> ByteString
compressXz = BL.toStrict . Lzma.compress . BL.fromStrict

-- | Decompress an xz-compressed strict 'ByteString'.
--
-- Returns 'Left' with an error message if the input is not valid xz data.
decompressXz :: ByteString -> IO (Either String ByteString)
decompressXz bs = do
  result <- try (evaluate (BL.toStrict (Lzma.decompress (BL.fromStrict bs))))
  case result of
    Right decompressed -> pure (Right decompressed)
    -- Catch only SYNCHRONOUS failures; re-raise async exceptions (timeout,
    -- ThreadKilled) so a caller's timeout/cancellation still works on this
    -- untrusted-input decoder.
    Left err
      | Just (_ :: SomeAsyncException) <- fromException err -> throwIO err
      | otherwise -> pure (Left ("xz decompression failed: " ++ show (err :: SomeException)))