-----------------------------------------------------------------------------
-- |
-- Module      :  Codec.Archive.Tar
-- Copyright   :  (c) 2007 Bjorn Bringert,
--                    2008 Andrea Vezzosi,
--                    2008-2012 Duncan Coutts
-- License     :  BSD3
--
-- Maintainer  :  duncan@community.haskell.org
-- Portability :  portable
--
-- Reading, writing and manipulating \"@.tar@\" archive files.
--
-- This module uses common names and so is designed to be imported qualified:
--
-- > import qualified Codec.Archive.Tar as Tar
--
-----------------------------------------------------------------------------
module Codec.Archive.Tar (

  -- | Tar archive files are used to store a collection of other files in a
  -- single file. They consists of a sequence of entries. Each entry describes
  -- a file or directory (or some other special kind of file). The entry stores
  -- a little bit of meta-data, in particular the file or directory name.
  --
  -- Unlike some other archive formats, a tar file contains no index. The
  -- information about each entry is stored next to the entry. Because of this,
  -- tar files are almost always processed linearly rather than in a
  -- random-access fashion.
  --
  -- The functions in this package are designed for working on tar files
  -- linearly and lazily. This makes it possible to do many operations in
  -- constant space rather than having to load the entire archive into memory.
  --
  -- It can read and write standard POSIX tar files and also the GNU and old
  -- Unix V7 tar formats. The convenience functions that are provided in the
  -- "Codec.Archive.Tar.Entry" module for creating archive entries are
  -- primarily designed for standard portable archives. If you need to
  -- construct GNU format archives or exactly preserve file ownership and
  -- permissions then you will need to write some extra helper functions.
  --
  -- This module contains just the simple high level operations without
  -- exposing the all the details of tar files. If you need to inspect tar
  -- entries in more detail or construct them directly then you also need
  -- the module "Codec.Archive.Tar.Entry".

  -- * High level \"all in one\" operations
  create,
  extract,

  -- * Notes
  -- ** Compressed tar archives
  -- | Tar files are commonly used in conjunction with gzip compression, as in
  -- \"@.tar.gz@\" or \"@.tar.bz2@\" files. This module does not directly
  -- handle compressed tar files however they can be handled easily by
  -- composing functions from this module and the modules
  -- @Codec.Compression.GZip@ or @Codec.Compression.BZip@
  -- (see @zlib@ or @bzlib@ packages).
  --
  -- Creating a compressed \"@.tar.gz@\" file is just a minor variation on the
  -- 'create' function, but where throw compression into the pipeline:
  --
  -- > BS.writeFile tar . GZip.compress . Tar.write =<< Tar.pack base dir
  --
  -- Similarly, extracting a compressed \"@.tar.gz@\" is just a minor variation
  -- on the 'extract' function where we use decompression in the pipeline:
  --
  -- > Tar.unpack dir . Tar.read . GZip.decompress =<< BS.readFile tar
  --

  -- ** Security
  -- | This is pretty important. A maliciously constructed tar archives could
  -- contain entries that specify bad file names. It could specify absolute
  -- file names like \"@\/etc\/passwd@\" or relative files outside of the
  -- archive like \"..\/..\/..\/something\". This security problem is commonly
  -- called a \"directory traversal vulnerability\". Historically, such
  -- vulnerabilities have been common in packages handling tar archives.
  --
  -- The 'extract' and 'unpack' functions check for bad file names. See the
  -- 'checkSecurity' function for more details. If you need to do any custom
  -- unpacking then you should use this.

  -- ** Tarbombs
  -- | A \"tarbomb\" is a @.tar@ file where not all entries are in a
  -- subdirectory but instead files extract into the top level directory. The
  -- 'extract' function does not check for these however if you want to do
  -- that you can use the 'checkTarbomb' function like so:
  --
  -- > Tar.unpack dir . Tar.checkTarbomb expectedDir
  -- >                . Tar.read =<< BS.readFile tar
  --
  -- In this case extraction will fail if any file is outside of @expectedDir@.

  -- * Converting between internal and external representation
  -- | Note, you cannot expect @write . read@ to give exactly the same output
  -- as input. You can expect the information to be preserved exactly however.
  -- This is because 'read' accepts common format variations while 'write'
  -- produces the standard format.
  read,
  write,

  -- * Packing and unpacking files to\/from internal representation
  -- | These functions are for packing and unpacking portable archives. They
  -- are not suitable in cases where it is important to preserve file ownership
  -- and permissions or to archive special files like named pipes and Unix
  -- device files.
  pack,
  unpack,

  -- * Types
  -- ** Tar entry type
  -- | This module provides only very simple and limited read-only access to
  -- the 'Entry' type. If you need access to the details or if you need to
  -- construct your own entries then also import "Codec.Archive.Tar.Entry".
  Entry,
  entryPath,
  entryContent,
  EntryContent(..),

  -- ** Sequences of tar entries
  Entries(..),
  mapEntries,
  mapEntriesNoFail,
  foldEntries,
  unfoldEntries,

  -- * Error handling
  -- | Reading tar files can fail if the data does not match the tar file
  -- format correctly.
  --
  -- The style of error handling by returning structured errors. The pure
  -- functions in the library do not throw exceptions, they return the errors
  -- as data. The IO actions in the library can throw exceptions, in particular
  -- the 'unpack' action does this. All the error types used are an instance of
  -- the standard 'Exception' class so it is possible to 'throw' and 'catch'
  -- them.

  -- ** Errors from reading tar files
  FormatError(..),
  ) where

import Codec.Archive.Tar.Types

import Codec.Archive.Tar.Read
import Codec.Archive.Tar.Write

import Codec.Archive.Tar.Pack
import Codec.Archive.Tar.Unpack

import Codec.Archive.Tar.Check

import Control.Exception (Exception, throw, catch)
import qualified Data.ByteString.Lazy as BS
import Prelude hiding (read)

-- | Create a new @\".tar\"@ file from a directory of files.
--
-- It is equivalent to calling the standard @tar@ program like so:
--
-- @$ tar -f tarball.tar -C base -c dir@
--
-- This assumes a directory @.\/base\/dir@ with files inside, eg
-- @.\/base\/dir\/foo.txt@. The file names inside the resulting tar file will be
-- relative to @dir@, eg @dir\/foo.txt@.
--
-- This is a high level \"all in one\" operation. Since you may need variations
-- on this function it is instructive to see how it is written. It is just:
--
-- > BS.writeFile tar . Tar.write =<< Tar.pack base paths
--
-- Notes:
--
-- The files and directories must not change during this operation or the
-- result is not well defined.
--
-- The intention of this function is to create tarballs that are portable
-- between systems. It is /not/ suitable for doing file system backups because
-- file ownership and permissions are not fully preserved. File ownership is
-- not preserved at all. File permissions are set to simple portable values:
--
-- * @rw-r--r--@ for normal files
--
-- * @rwxr-xr-x@ for executable files
--
-- * @rwxr-xr-x@ for directories
--
create :: FilePath   -- ^ Path of the \".tar\" file to write.
       -> FilePath   -- ^ Base directory
       -> [FilePath] -- ^ Files and directories to archive, relative to base dir
       -> IO ()
create tar base paths = BS.writeFile tar . write =<< pack base paths

-- | Extract all the files contained in a @\".tar\"@ file.
--
-- It is equivalent to calling the standard @tar@ program like so:
--
-- @$ tar -x -f tarball.tar -C dir@
--
-- So for example if the @tarball.tar@ file contains @foo\/bar.txt@ then this
-- will extract it to @dir\/foo\/bar.txt@.
--
-- This is a high level \"all in one\" operation. Since you may need variations
-- on this function it is instructive to see how it is written. It is just:
--
-- > Tar.unpack dir . Tar.read =<< BS.readFile tar
--
-- Notes:
--
-- Extracting can fail for a number of reasons. The tarball may be incorrectly
-- formatted. There may be IO or permission errors. In such cases an exception
-- will be thrown and extraction will not continue.
--
-- Since the extraction may fail part way through it is not atomic. For this
-- reason you may want to extract into an empty directory and, if the
-- extraction fails, recursively delete the directory.
--
-- Security: only files inside the target directory will be written. Tarballs
-- containing entries that point outside of the tarball (either absolute paths
-- or relative paths) will be caught and an exception will be thrown.
--
extract :: FilePath -- ^ Destination directory
        -> FilePath -- ^ Tarball
        -> IO ()
extract dir tar = unpack dir . read =<< BS.readFile tar