hascar 0.2.1.2 → 0.2.2.0
raw patch · 7 files changed
+197/−74 lines, 7 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Codec.Archive.SAPCAR.Pat: instance GHC.Classes.Eq Codec.Archive.SAPCAR.Pat.PatChunk
+ Codec.Archive.SAPCAR.Pat: instance GHC.Classes.Eq Codec.Archive.SAPCAR.Pat.PatChunkType
+ Codec.Archive.SAPCAR.Pat: instance GHC.Enum.Enum Codec.Archive.SAPCAR.Pat.PatChunkType
+ Codec.Archive.SAPCAR.Pat: instance GHC.Show.Show Codec.Archive.SAPCAR.Pat.PatChunk
+ Codec.Archive.SAPCAR.Pat: instance GHC.Show.Show Codec.Archive.SAPCAR.Pat.PatChunkType
+ Codec.Archive.SAPCAR.Pat: patToTransport :: Monad m => Conduit ByteString m ByteString
Files
- README.md +16/−67
- app/GPL.hs +1/−1
- app/Main.hs +34/−3
- app/Options.hs +6/−0
- changelog.md +6/−0
- hascar.cabal +6/−3
- src/Codec/Archive/SAPCAR/Pat.hs +128/−0
README.md view
@@ -2,7 +2,11 @@ # What is hascar? -HASCAR is a free unzip utility for SAP's SAPCAR format.+HASCAR is a free unzip utility for SAP's SAPCAR format. You can use it to+decompress sap car files on the command line. The command line utility also+supports unwrapping of transport files that are contained inside patch files.+(Option -p, --depat) You can also use it as a library in your own haskell programs. So+far, only the latest version of sapcar files are is supported, which is 2.01. It is implemented 100% in haskell, including the lzh decompression routine.@@ -20,6 +24,7 @@ * Reading SAPCAR archives version 2.01 only * Unpacking files that are LZH compressed * Unpacking files that are not compressed+* Unpacking transport files are wrapped inside a PAT file TODO: @@ -32,31 +37,16 @@ => Some work on this has been done; more is required. * Implement CRC checking -# Performance -The lzh algorithm is implemented in pure haskell. I have spent some time-optimizing it, but more can certainly be done. Currently the performance is-probably acceptable for most cases, but it is still a factor ten compared to-the reference C implementation:--Decompressing a 136 MB payload (34MB compressed), the performance looks like-this:-- hascar: 7.94user 0.10system 0:08.05elapsed 99%CPU (0avgtext+0avgdata 102676maxresident)k-- sapcar: 0.84user 0.07system 0:00.92elapsed 99%CPU (0avgtext+0avgdata 8244maxresident)k--Both executed on a single CPU core. (Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz)- # Installing hascar -# nixos+## nixos Users of [nixos](https://nixos.org) can simply install hascar by issuing "nix-env -iaP haskellPackages.hascar". Or if you only need it temporarily, open a nix-shell like this: "nix-shell -p haskellPackages.hascar" -# FreeBSD/GNU_Linux/OS X/Windows+## FreeBSD/GNU_Linux/OS X/Windows To compile and install, first [get stack]( http://docs.haskellstack.org/en/stable/README/), then issue:@@ -66,26 +56,9 @@ hascar will be installed to ~/.local/bin . You should set your PATH variable to point to this directory. -# Verifying signatures--You can use hascar to decompress and subsequently inspect the contents-of a SAR file. You would normally not install the contents from-untrusted SAR files, so there is no need to verify the signature.--If, OTOH, you do trust the original SAR file but wish to verify the-signature to ensure you are indeed dealing with the original SAR-file's contents, like when installing patches to your SAP system, then-you will need to do the following: Since hascar at this time does not-support verifying signatures, you need to use SAP's own tool for that-purpose. You can use hascar to initially decompress the archive. This-step ensures that only archives with a correct and untampered file-header and compressed contents are accepted. Then, use SAP's own-sapcar tool to create a new archive from the decompressed archive. You-will now have a trusted archive, because you created it yourself. You-can then use SAP's original tool to decompress it again, while-verifying the signature.+# Verifying signatures and encrypted archives -The only attack vector left is the signature checking algorithm.+Since both use a proprietary crypto API, this is currently not supported. # Usage @@ -94,34 +67,10 @@ command line parser is a bit more strict than what you might be used to. -# Example run:+# Extracting transport files from PAT files - hc@espererh-pc ~/I/hascar λ hascar -xtvf /home/hc/test.sar - ┌────────────────────────────────────────────────────────────────────┐- │ hascar, Copyright (C) 2016, Virtual Forge GmbH. │- │ │- │ Maint.: Hans-Christian Esperer │- │ <hans-christian.esperer@virtualforge.com> │- │ │- │ hascar comes with ABSOLUTELY NO WARRANTY; │- │ for details read the LICENSE file. │- │ This is free software, and you are welcome to redistribute │- │ it under certain conditions; see the LICENSE file for details. │- └────────────────────────────────────────────────────────────────────┘- - 5 entrie(s) in the archive.- - All entries:- -rw-r--r-- 0 root root 9302 Jun 10 00:00 sapcar-usage- -rw-r--r-- 0 root root 267468 Jun 10 00:00 pg244.txt- -rw-r--r-- 0 root root 34857 Jun 10 00:00 man.txt- -rw-r--r-- 0 root root 10485764 Jun 10 00:00 foo- -rw-r--r-- 0 root root 30 Jun 10 00:00 date- - Extracting "sapcar-usage"- Extracting "pg244.txt"- Extracting "man.txt"- Extracting "foo"- Extracting "date"- - hc@espererh-pc ~/I/hascar λ+SAR files can contain transport files that are wrapped inside PAT (patch)+files. Since hascar 0.2.2.0 the option -p is offered, that will try to+automatically detect PAT files. If such a file is detected, the contained+transport file is extracted. In verbose mode, this is denoted by a 'P' next to+the filename instead of an 'x'.
app/GPL.hs view
@@ -17,7 +17,7 @@ -- | The copyright text gpl' :: [Text] gpl' =- [ "hascar, Copyright (C) 2016, Virtual Forge GmbH.", ""+ [ "hascar, Copyright (C) 2016-2018, Virtual Forge GmbH.", "" , "Maint.: Hans-Christian Esperer" , "<hans-christian.esperer@virtualforge.com>", "" , "hascar comes with ABSOLUTELY NO WARRANTY;"
app/Main.hs view
@@ -22,8 +22,11 @@ module Main where import Control.Monad+import Control.Monad.Catch import Control.Monad.IO.Class import Data.Binary.Get+import Data.ByteString (ByteString)+import Data.Conduit import Foreign.C.Types (CTime(..)) import Path import System.Directory@@ -38,6 +41,7 @@ import Options import Codec.Archive.SAPCAR+import Codec.Archive.SAPCAR.Pat #ifndef mingw32_HOST_OS import System.Posix.Files as SPF@@ -77,9 +81,17 @@ forM_ files $ \file -> do filename <- parseRelFile $ T.unpack $ carEntryFilename file liftIO $ cdim $ fromRelFile filename- when (oVerbose options) $- liftIO $ putStrLn $ "x " ++ fromRelFile filename- writeToFile file filename+ patWritten <- if (oExtractPatFiles options)+ then unpackPat (fromRelFile filename) file+ else return False+ if patWritten+ then when (oVerbose options) $+ liftIO $ putStrLn $ "P " ++ fromRelFile filename+ else do+ when (oVerbose options) $+ liftIO $ putStrLn $ "x " ++ fromRelFile filename+ writeToFile file filename+ #ifndef mingw32_HOST_OS liftIO $ SPF.setFileMode (fromRelFile filename) $ CMode $ fromIntegral $ cfPermissions file@@ -87,6 +99,25 @@ liftIO $ SPF.setFileTimes (fromRelFile filename) amTime amTime #endif +-- | Write a transport contained inside a PAT file+-- return if any bytes were written+unpackPat :: FilePath -> CarEntry s -> SapCar s IO Bool+unpackPat path file = bracket open close w+ where+ open = liftIO $ openBinaryFile path WriteMode+ close = liftIO . hClose+ w h = sourceEntry file (patToTransport =$= writePat h)++-- | Provide a conduit sink, write everything that arrives there to+-- the given handle. Return True if at least one chunk was written.+writePat :: Handle -> Sink S.ByteString IO Bool+writePat h =+ let loop c = do+ chunk <- await+ case chunk of+ Just chunk' -> liftIO (S.hPut h chunk') >> loop True+ Nothing -> return c+ in loop False cdim :: FilePath -> IO () cdim fp = do
app/Options.hs view
@@ -24,6 +24,8 @@ oQuiet :: !Bool , -- | Whether to list all entries in the archive oListEntries :: !Bool+ , -- | Try to extract transports from PAT files+ oExtractPatFiles :: !Bool } deriving (Show) run :: (Options -> IO a) -> IO a@@ -61,5 +63,9 @@ ( long "list" <> short 't' <> help "List all entries in the archive" )+ <*> switch+ ( long "depat"+ <> short 'p'+ <> help "Try to extract transport files from PAT files" )
changelog.md view
@@ -1,3 +1,9 @@+0.2.2.0+-------++- Add an option to extract transport files from PAT+ files automatically+ 0.2.1.2 -------
hascar.cabal view
@@ -1,13 +1,13 @@ name: hascar-version: 0.2.1.2+version: 0.2.2.0 synopsis: Decompress SAPCAR archives-description: Decompress SAPCAR archives+description: Decompressor command line utility and library for SAP's SAPCAR format. homepage: https://github.com/VirtualForgeGmbH/hascar license: GPL-2 license-file: LICENSE author: Hans-Christian Esperer <hc@hcesperer.org> maintainer: Hans-Christian Esperer <hc@hcesperer.org>-copyright: 2016, Virtual Forge GmbH+copyright: 2016-2018, Virtual Forge GmbH category: Codec build-type: Simple extra-source-files: README.md changelog.md test/test6.sar@@ -17,6 +17,7 @@ hs-source-dirs: src exposed-modules: Codec.Archive.SAPCAR , Codec.Archive.SAPCAR.FlatedFile+ , Codec.Archive.SAPCAR.Pat other-modules: Codec.Archive.SAPCAR.BitStream , Codec.Archive.SAPCAR.CanonicalHuffmanTree , Codec.Archive.SAPCAR.FlexibleUtils@@ -43,7 +44,9 @@ , ansi-wl-pprint , binary , bytestring+ , conduit , directory+ , exceptions , filepath , gitrev , hascar
+ src/Codec/Archive/SAPCAR/Pat.hs view
@@ -0,0 +1,128 @@+-- |+-- Module: Pat+-- Copyright: (C) 2015-2016, Virtual Forge GmbH+-- License: GPL2+-- Maintainer: Hans-Christian Esperer <hc@hcesperer.org>+-- Stability: experimental+-- Portability: portable+-- |+-- (De-)compress SAPCAR files+--+-- Copyright (C) 2016, Virtual Forge GmbH+--+-- This program is free software; you can redistribute it and/or modify+-- it under the terms of the GNU General Public License as published by+-- the Free Software Foundation; either version 2 of the License, or (at+-- your option) any later version.+--+-- This program is distributed in the hope that it will be useful, but+-- WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU+-- General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program; if not, write to the Free Software+-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307+-- USA+module Codec.Archive.SAPCAR.Pat+ ( patToTransport+ ) where++import Control.Monad+import Data.Binary.Get+import Data.Bits+import Data.Conduit+import Data.Maybe+import Data.Word+import Data.Text (Text)+import System.IO+import Text.Printf+import Text.Read++import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE+import qualified Data.Text.Encoding.Error as TEE++-- | The maximum size we allow per chunk. This is+-- important to help prevent memory exhaustion attacks.+maximumChunkSize :: Int+maximumChunkSize = 65536++-- | One chunk in a PAT file+data PatChunk = PatChunk+ { -- | The version of the chunk+ pcVersion :: Int+ , -- | The type of the PAT chunk+ pcType :: PatChunkType+ , -- | The length ot the chunk+ pcLength :: Int+ , -- | Reserved 14 bytes+ pcReserved :: S.ByteString+ , -- | The raw payload of the chunk+ pcPayload :: S.ByteString }+ deriving (Show, Eq)++-- | The type of a PAT chunk+data PatChunkType+ = -- | A fragment of an SAP transport file+ TransportPatChunk+ | -- | An unknown type of chunk+ UnknownPatChunk+ | -- | Not a PAT fiel+ NotAPatFile+ deriving (Eq, Enum, Show)++getChunkType :: Get PatChunkType+getChunkType = getChunkType' <$> getWord8++getChunkType' :: Word8 -> PatChunkType+getChunkType' 82 = TransportPatChunk+getChunkType' (-1) = NotAPatFile+getChunkType' _ = UnknownPatChunk++getChunkVersion :: Get Int+getChunkVersion = fromMaybe (-1) . readMaybe . T.unpack . TE.decodeUtf8With TEE.lenientDecode <$> getByteString 2++getChunkLength :: Get Int+getChunkLength = do+ length <- fromMaybe (0) . readMaybe . T.unpack . TE.decodeUtf8 <$> getByteString 8 :: Get Int+ when (length > maximumChunkSize) $ error "Too big a chunk"+ return length++-- | Get one SAP PAT chunk+getPatChunk :: Get PatChunk+getPatChunk = do+ v <- getChunkVersion+ if v == (-1)+ then return $ PatChunk (-1) NotAPatFile 0 S.empty S.empty+ else do+ t <- getChunkType+ l <- getChunkLength+ r <- getByteString 14+ p <- getByteString $ l - 25+ return $ PatChunk v t l r p++-- | Extract a transport file from a PAT file chunk by chunk+patToTransport :: Monad m => Conduit S.ByteString m S.ByteString+patToTransport = patToTransport' S.empty $ runGetIncremental getPatChunk++patToTransport'+ :: Monad m+ => S.ByteString+ -> Decoder PatChunk+ -> Conduit S.ByteString m S.ByteString+patToTransport' s (Partial d)+ | S.null s = do+ chunk <- await+ case chunk of+ Just chunk' -> patToTransport' S.empty $ pushChunk (Partial d) chunk'+ Nothing -> return ()+ | otherwise = patToTransport' S.empty $ pushChunk (Partial d) s+patToTransport' s (Done rest _ r) = do+ when (pcType r == TransportPatChunk) $+ yield (pcPayload r)+ unless (pcType r == NotAPatFile) $+ patToTransport' rest $ runGetIncremental getPatChunk+