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

Can't extract tar file: inappropriate type (Is a directory) #27

Closed
bitc opened this issue Dec 18, 2017 · 7 comments · Fixed by #82
Closed

Can't extract tar file: inappropriate type (Is a directory) #27

bitc opened this issue Dec 18, 2017 · 7 comments · Fixed by #82

Comments

@bitc
Copy link

bitc commented Dec 18, 2017

I found a .tar file that this library chokes on: https://nodejs.org/dist/v8.9.3/node-v8.9.3-linux-x64.tar.xz

I extracted it with the "unxz" program and got "node-v8.9.3-linux-x64.tar". This file extracts just fine using the regular "tar" program. But when using this library (extract function) I get this error:

*** Exception: ./node-v8.9.3-linux-x64/lib/node_modules/npm/node_modules/readable-stream/node_modules/util-deprecate/: openBinaryFile: inappropriate type

I managed to print the failing Tar Entry:

Entry
  { entryTarPath = "node-v8.9.3-linux-x64/lib/node_modules/npm/node_modules/readable-stream/node_modules/util-deprecate/"
  , entryContent = NormalFile "\n1.0.2 / 2015-10-07\n==================\n\n  * use try/catch when checking `localStorage` (#3, @kumavis)\n\n1.0.1 / 2014-11-25\n==================\n\n  * browser: use `console.warn()` for deprecation calls\n  * browser: more jsdocs\n\n1.0.0 / 2014-04-30\n==================\n\n  * initial commit\n" 282
  , entryPermissions = 436
  , entryOwnership = Ownership
      { ownerName = "iojs"
      , groupName = "iojs"
      , ownerId = 500
      , groupId = 500
      }
  , entryTime = 1496058403
  , entryFormat = GnuFormat
  }

Looking at the "entryContent", the "entryTarPath" is incorrect, and is supposed to be the file: node-v8.9.3-linux-x64/lib/node_modules/npm/node_modules/readable-stream/node_modules/util-deprecate/History.md

@bitc
Copy link
Author

bitc commented Dec 18, 2017

Here is the "History.md" file, this is the Tar Entry just before the above failing one:

Entry
  { entryTarPath = "././@LongLink"
  , entryContent = OtherEntryType 'L' "node-v9.3.0-linux-x64/lib/node_modules/npm/node_modules/readable-stream/node_modules/util-deprecate/History.md\NUL" 111
  , entryPermissions = 0
  , entryOwnership = Ownership
      { ownerName = "root"
      , groupName = "root"
      , ownerId = 0
      , groupId = 0
      }
  , entryTime = 0
  , entryFormat = GnuFormat
  }

@vmchale
Copy link

vmchale commented Feb 5, 2019

I suspect that this could be resolved by adding nicer support for GNU/old GNU/POSIX tar archives. According to this, GNU tar archives support unlimited file names, but according to a comment here, there doesn't seem to be any attempt to support this.

@bitc
Copy link
Author

bitc commented Feb 8, 2019

I have some code that can extract PAX tar archives pretty well (note: these are different from GNU tar archives that I originally reported)

WARNING: This might have security issues (overwriting files outside the destination directory)

unpackTar :: FilePath -> Tar.Entries Tar.FormatError -> IO ()
unpackTar dir x =
    (loop Nothing Nothing . (checkEntries checkEntrySecurity)) x
  where
    loop :: (Maybe FilePath) -> (Maybe FilePath) -> Tar.Entries (Either Tar.FormatError Tar.FileNameError) -> IO ()
    loop _ _ Tar.Done = return ()
    loop _ _ (Tar.Fail e) = either throwIO throwIO e
    loop pathOverride linkOverride (Tar.Next e es) = case Tar.entryContent e of
        Tar.Directory -> loop Nothing Nothing es
        Tar.NormalFile lbs _ -> do
            let fp = dir </> fromMaybe (Tar.entryPath e) pathOverride
            saveFile fp lbs (Tar.entryPermissions e)
            loop Nothing Nothing es
        Tar.SymbolicLink target -> do
            let fp = dir </> fromMaybe (Tar.entryPath e) pathOverride
            let link = fromMaybe (Tar.fromLinkTarget target) linkOverride
            saveSymbolicLink fp link
            loop Nothing Nothing es
        Tar.OtherEntryType 'x' pax _ -> do
            case parsePaxExtended pax of
                Left err -> fail err
                Right overrides -> do
                    let pathOverride' = lookup (L.pack "path") overrides
                        linkOverride' = lookup (L.pack "linkpath") overrides
                    loop (fmap L.unpack pathOverride') (fmap L.unpack linkOverride') es
        _ -> do
            loop Nothing Nothing es

saveFile :: FilePath -> L.ByteString -> Tar.Permissions -> IO ()
saveFile fp lbs perms = do
    createDirectoryIfMissing True $ F.takeDirectory fp
    runConduitRes $ mapM_ yield (L.toChunks lbs) .| sinkFile fp
    when ((perms .&. (ownerExecuteMode .|. groupExecuteMode .|. otherExecuteMode)) /= 0 ) $ do
        stat <- getFileStatus fp
        setFileMode fp (fileMode stat .|. (ownerExecuteMode .|. groupExecuteMode .|. otherExecuteMode))

saveSymbolicLink :: FilePath -> FilePath -> IO ()
saveSymbolicLink fp target = do
    createDirectoryIfMissing True $ F.takeDirectory fp
    createSymbolicLink target fp

checkEntries :: (Tar.Entry -> Maybe e') -> Tar.Entries e -> Tar.Entries (Either e e')
checkEntries checkEntry =
    Tar.mapEntries (\entry -> maybe (Right entry) Left (checkEntry entry))

checkEntrySecurity :: Tar.Entry -> Maybe Tar.FileNameError
checkEntrySecurity _ = Nothing

parsePaxExtended :: L.ByteString -> Either String [(L.ByteString, L.ByteString)]
parsePaxExtended = next []
    where
    next :: [(L.ByteString, L.ByteString)] -> L.ByteString -> Either String [(L.ByteString, L.ByteString)]
    next accum str =
        let (lenStr, _) = L.break (==' ') str
        in case L.readInt lenStr of
            Nothing -> Left "Invalid PAX"
            Just (len, _) ->
                let (seg, rest) = L.splitAt (fromIntegral len) str
                    seg' = L.drop 1 (L.dropWhile (/=' ') seg)
                    seg'' = L.take (L.length seg' - 1) seg'
                    (key, val) = L.break (=='=') seg''
                    newPair = (key, L.drop 1 val)
                in if L.null rest then Right (newPair:accum)
                    else next (newPair:accum) rest

@hasufell
Copy link
Member

hasufell commented Mar 1, 2020

#50 seems related

@Bodigrim
Copy link
Contributor

It works better wrt long file names after #77, but still fails later on:

$ cabal run htar -- --extract -f node-v8.9.3-linux-x64.tar
htar: Invalid file name in tar archive: "../lib/node_modules/npm/bin/npx-cli.js"

@hasufell
Copy link
Member

hasufell commented Dec 7, 2023

@Bodigrim I presume it works now?

@Bodigrim
Copy link
Contributor

Bodigrim commented Dec 7, 2023

@hasufell yes, it works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants