Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for over-long filepaths via GNU extension and fix hardlinks #77

Merged
merged 26 commits into from
Nov 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d35c6bb
Add support for over-long filepaths via GNU extension
hasufell Feb 21, 2020
5473554
Fix htar build
hasufell Apr 1, 2021
f244f90
Update changelog
hasufell Apr 1, 2021
899cbda
Fix unpacking when symlink points to a directory
hasufell Apr 2, 2021
203aeb6
Update changelog
hasufell Apr 2, 2021
596631d
Create symlinks instead of always copying during unpack
hasufell Apr 4, 2021
db74bc4
Create symlinks during packing as well
hasufell Apr 4, 2021
ad8c0fe
Avoid exposing These
hasufell Apr 7, 2021
9b96588
Improve documentation about toTarPath
hasufell Apr 7, 2021
5978fdc
Expose These/these
hasufell Apr 7, 2021
09b68c2
Expose longLinkEntry and packSymlinkEntry
hasufell Apr 7, 2021
87574a0
Improve docs
hasufell Apr 7, 2021
c85a2b9
Restore toTarPath to its previous type to improve compatibility
Bodigrim Nov 17, 2023
fe45d1d
Require directory >= 1.3.1
Bodigrim Nov 17, 2023
fab630e
Revert some stylistic changes
Bodigrim Nov 17, 2023
0a3d1e4
putEntry: allow long file names only in GnuFormat
Bodigrim Nov 18, 2023
f7fd44d
Test pack-unpack with long file names
Bodigrim Nov 18, 2023
32a37af
Add support for long symlink targets
hasufell Nov 19, 2023
caa858c
Adhere to spec by attempting to unpack unknown typecodes
hasufell Nov 19, 2023
e9604df
Add test for long symlink
hasufell Nov 19, 2023
120b4f0
Drop support for GHC <8.4
hasufell Nov 19, 2023
3cdddcf
Avoid printing to stderr and don't error on unknown typecode
hasufell Nov 19, 2023
d8c88e0
Use packAscii in longSymlinkEntry
hasufell Nov 19, 2023
b5aa188
Sanitise long filenames / symlinks: skip the trailing NUL byte
Bodigrim Nov 19, 2023
5e872fc
Fix pack/unpack roundtrip test on Windows
Bodigrim Nov 19, 2023
65112d9
CI: enable --test-show-details=direct
Bodigrim Nov 19, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/arm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ jobs:
uses: docker://hasufell/arm32v7-ubuntu-haskell:focal
name: Run build (arm32v7 linux)
with:
args: sh -c "curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 BOOTSTRAP_HASKELL_INSTALL_NO_STACK=1 BOOTSTRAP_HASKELL_GHC_VERSION=9.2.8 sh && cabal update && cabal test -w ~/.ghcup/bin/ghc"
args: sh -c "curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 BOOTSTRAP_HASKELL_INSTALL_NO_STACK=1 BOOTSTRAP_HASKELL_GHC_VERSION=9.2.8 sh && cabal update && cabal test -w ~/.ghcup/bin/ghc" --test-show-details=direct

- if: matrix.arch == 'arm64v8'
uses: docker://hasufell/arm64v8-ubuntu-haskell:focal
name: Run build (arm64v8 linux)
with:
args: sh -c "curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 BOOTSTRAP_HASKELL_INSTALL_NO_STACK=1 sh && cabal update && cabal test -w ~/.ghcup/bin/ghc"
args: sh -c "curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 BOOTSTRAP_HASKELL_INSTALL_NO_STACK=1 sh && cabal update && cabal test -w ~/.ghcup/bin/ghc" --test-show-details=direct
2 changes: 1 addition & 1 deletion .github/workflows/centos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ jobs:
run: |
source ~/.ghcup/env
cabal update
cabal test
cabal test --test-show-details=direct
10 changes: 0 additions & 10 deletions .github/workflows/haskell-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,6 @@ jobs:
compilerVersion: 8.4.4
setup-method: hvr-ppa
allow-failure: false
- compiler: ghc-8.2.2
compilerKind: ghc
compilerVersion: 8.2.2
setup-method: hvr-ppa
allow-failure: false
- compiler: ghc-8.0.2
compilerKind: ghc
compilerVersion: 8.0.2
setup-method: hvr-ppa
allow-failure: false
fail-fast: false
steps:
- name: apt
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/i386.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ jobs:
run: |
source ~/.ghcup/env
cabal update
cabal test
cabal test --test-show-details=direct
2 changes: 1 addition & 1 deletion .github/workflows/windows_and_macOS.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
run: |
bld() { cabal build pkg:tar:tests; }
bld || bld || bld
cabal test
cabal test --test-show-details=direct
- name: Haddock
run: cabal haddock
- name: SDist
Expand Down
2 changes: 2 additions & 0 deletions Codec/Archive/Tar/Check.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module Codec.Archive.Tar.Check (

-- * Security
checkSecurity,
checkEntrySecurity,
FileNameError(..),

-- * Tarbombs
Expand Down Expand Up @@ -62,6 +63,7 @@ import qualified System.FilePath.Posix as FilePath.Posix
checkSecurity :: Entries e -> Entries (Either e FileNameError)
checkSecurity = checkEntries checkEntrySecurity

-- | @since 0.6.0.0
checkEntrySecurity :: Entry -> Maybe FileNameError
checkEntrySecurity entry = case entryContent entry of
HardLink link -> check (entryPath entry)
Expand Down
3 changes: 3 additions & 0 deletions Codec/Archive/Tar/Entry.hs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ module Codec.Archive.Tar.Entry (
simpleEntry,
fileEntry,
directoryEntry,
longLinkEntry,
longSymLinkEntry,

-- * Standard file permissions
-- | For maximum portability when constructing archives use only these file
Expand All @@ -58,6 +60,7 @@ module Codec.Archive.Tar.Entry (
-- * Constructing entries from disk files
packFileEntry,
packDirectoryEntry,
packSymlinkEntry,
getDirectoryContentsRecursive,

-- * TarPath type
Expand Down
82 changes: 68 additions & 14 deletions Codec/Archive/Tar/Pack.hs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE LambdaCase #-}
-----------------------------------------------------------------------------
-- |
-- Module : Codec.Archive.Tar
Expand All @@ -14,19 +16,23 @@ module Codec.Archive.Tar.Pack (
pack,
packFileEntry,
packDirectoryEntry,
packSymlinkEntry,
longLinkEntry,

getDirectoryContentsRecursive,
) where

import Codec.Archive.Tar.Types

import Control.Monad (join, when, forM)
import qualified Data.ByteString as BSS
import qualified Data.ByteString.Lazy as BS
import System.FilePath
( (</>) )
import qualified System.FilePath as FilePath.Native
( addTrailingPathSeparator, hasTrailingPathSeparator )
( addTrailingPathSeparator, hasTrailingPathSeparator, splitDirectories )
import System.Directory
( getDirectoryContents, doesDirectoryExist, getModificationTime
, pathIsSymbolicLink, getSymbolicLinkTarget
, Permissions(..), getPermissions )
import Data.Time.Clock
( UTCTime )
Expand All @@ -35,16 +41,17 @@ import Data.Time.Clock.POSIX
import System.IO
( IOMode(ReadMode), withBinaryFile, hFileSize )
import System.IO.Unsafe (unsafeInterleaveIO)
import Control.Exception (throwIO)
import Codec.Archive.Tar.Check (checkEntrySecurity)

-- | Creates a tar archive from a list of directory or files. Any directories
-- specified will have their contents included recursively. Paths in the
-- archive will be relative to the given base directory.
--
-- This is a portable implementation of packing suitable for portable archives.
-- In particular it only constructs 'NormalFile' and 'Directory' entries. Hard
-- links and symbolic links are treated like ordinary files. It cannot be used
-- to pack directories containing recursive symbolic links. Special files like
-- FIFOs (named pipes), sockets or device files will also cause problems.
-- In particular it only constructs 'NormalFile', 'Directory' and 'SymbolicLink'
-- entries. Hard links are treated like ordinary files. Special files like
-- FIFOs (named pipes), sockets or device files will cause problems.
--
-- An exception will be thrown for any file names that are too long to
-- represent as a 'TarPath'.
Expand All @@ -70,15 +77,42 @@ preparePaths baseDir paths =
else return [path]
| path <- paths ]


-- | Pack paths while accounting for overlong filepaths.
packPaths :: FilePath -> [FilePath] -> IO [Entry]
packPaths baseDir paths =
interleave
[ do tarpath <- either fail return (toTarPath isDir relpath)
if isDir then packDirectoryEntry filepath tarpath
else packFileEntry filepath tarpath
fmap concat $ interleave
[ do let tarpathRes = toTarPath' isDir relpath
isSymlink <- pathIsSymbolicLink filepath
case tarpathRes of
FileNameEmpty -> throwIO $ userError "File name empty"
FileNameOK tarpath
| isSymlink -> (:[]) <$> packSymlinkEntry filepath tarpath
| isDir -> (:[]) <$> packDirectoryEntry filepath tarpath
| otherwise -> (:[]) <$> packFileEntry filepath tarpath
FileNameTooLong tarpath
| isSymlink -> do
linkTarget <- getSymbolicLinkTarget filepath
packSymlinkEntry' linkTarget tarpath >>= \case
sym@(Entry { entryContent = SymbolicLink (LinkTarget bs) })
| BSS.length bs > 100 -> do
pure [longSymLinkEntry linkTarget, longLinkEntry filepath, sym]
_ -> withLongLinkEntry filepath tarpath packSymlinkEntry
| isDir -> withLongLinkEntry filepath tarpath packDirectoryEntry
| otherwise -> withLongLinkEntry filepath tarpath packFileEntry
| relpath <- paths
, let isDir = FilePath.Native.hasTrailingPathSeparator filepath
filepath = baseDir </> relpath ]
where
-- prepend the long filepath entry if necessary
withLongLinkEntry
:: FilePath
-> TarPath
-> (FilePath -> TarPath -> IO Entry)
-> IO [Entry]
withLongLinkEntry filepath tarpath f = do
mainEntry <- f filepath tarpath
pure [longLinkEntry filepath, mainEntry]

interleave :: [IO a] -> IO [a]
interleave = unsafeInterleaveIO . go
Expand Down Expand Up @@ -106,10 +140,10 @@ packFileEntry filepath tarpath = do
content <- BS.readFile filepath
let size = BS.length content
return (simpleEntry tarpath (NormalFile content (fromIntegral size))) {
entryPermissions = if executable perms then executableFilePermissions
else ordinaryFilePermissions,
entryTime = mtime
}
entryPermissions = if executable perms then executableFilePermissions
else ordinaryFilePermissions,
entryTime = mtime
}

-- | Construct a tar 'Entry' based on a local directory (but not its contents).
--
Expand All @@ -125,6 +159,26 @@ packDirectoryEntry filepath tarpath = do
entryTime = mtime
}

-- | Construct a tar 'Entry' based on a local symlink.
--
-- This automatically checks symlink safety via 'checkEntrySecurity'.
--
-- @since 0.6.0.0
packSymlinkEntry :: FilePath -- ^ Full path to find the file on the local disk
-> TarPath -- ^ Path to use for the tar Entry in the archive
-> IO Entry
packSymlinkEntry filepath tarpath = do
linkTarget <- getSymbolicLinkTarget filepath
packSymlinkEntry' linkTarget tarpath

packSymlinkEntry' :: String -- ^ link target
-> TarPath -- ^ Path to use for the tar Entry in the archive
-> IO Entry
packSymlinkEntry' linkTarget tarpath = do
let entry tp = symlinkEntry tp linkTarget
safeReturn tp = maybe (pure tp) throwIO $ checkEntrySecurity tp
safeReturn $ entry tarpath

-- | This is a utility function, much like 'getDirectoryContents'. The
-- difference is that it includes the contents of subdirectories.
--
Expand Down
Loading
Loading