* Control characters in information coming from the repository or other
possible untrusted sources are filtered out of the display of many
commands.
+ * find, findkeys, examinekey: When outputting to a terminal and --format
+ is not used, quote unusual characters.
+ (Similar to the behavior of GNU find.)
* addurl --preserve-filename now rejects filenames that contain other
control characters, besides the escape sequences it already rejected.
- Licensed under the GNU AGPL version 3 or higher.
-}
+{-# LANGUAGE OverloadedStrings #-}
+
module Command.ExamineKey where
import Command
import Backend
import Types.Backend
import Types.Key
+import Utility.SafeOutput
import Data.Char
import qualified Data.ByteString as B
objectpath <- calcRepo $ gitAnnexLocation k
let objectpointer = formatPointer k
- showFormatted (format o) (serializeKey' k) $
+ isterminal <- liftIO $ checkIsTerminal stdout
+ showFormatted isterminal (format o) (serializeKey' k) $
[ ("objectpath", fromRawFilePath objectpath)
, ("objectpointer", fromRawFilePath objectpointer)
] ++ formatVars k af
{- git-annex command
-
- - Copyright 2010-2018 Joey Hess <id@joeyh.name>
+ - Copyright 2010-2023 Joey Hess <id@joeyh.name>
-
- Licensed under the GNU AGPL version 3 or higher.
-}
import Git.FilePath
import qualified Utility.Format
import Utility.DataUnits
+import Utility.SafeOutput
cmd :: Command
cmd = withAnnexOptions [annexedMatchingOptions] $ mkCommand $
seek o = do
unless (isJust (keyOptions o)) $
checkNotBareRepo
+ isterminal <- liftIO $ checkIsTerminal stdout
seeker <- contentPresentUnlessLimited $ AnnexedFileSeeker
- { startAction = start o
+ { startAction = start o isterminal
, checkContentPresent = Nothing
, usesLocationLog = False
}
case batchOption o of
NoBatch -> withKeyOptions (keyOptions o) False seeker
- (commandAction . startKeys o)
+ (commandAction . startKeys o isterminal)
(withFilesInGitAnnex ww seeker)
=<< workTreeItems ww (findThese o)
Batch fmt -> batchOnly (keyOptions o) (findThese o) $
else Just True
}
-start :: FindOptions -> SeekInput -> RawFilePath -> Key -> CommandStart
-start o _ file key = startingCustomOutput key $ do
- showFormatted (formatOption o) file
+start :: FindOptions -> IsTerminal -> SeekInput -> RawFilePath -> Key -> CommandStart
+start o isterminal _ file key = startingCustomOutput key $ do
+ showFormatted isterminal (formatOption o) file
(formatVars key (AssociatedFile (Just file)))
next $ return True
-startKeys :: FindOptions -> (SeekInput, Key, ActionItem) -> CommandStart
-startKeys o (si, key, ActionItemBranchFilePath (BranchFilePath _ topf) _) =
- start o si (getTopFilePath topf) key
-startKeys _ _ = stop
+startKeys :: FindOptions -> IsTerminal -> (SeekInput, Key, ActionItem) -> CommandStart
+startKeys o isterminal (si, key, ActionItemBranchFilePath (BranchFilePath _ topf) _) =
+ start o isterminal si (getTopFilePath topf) key
+startKeys _ _ _ = stop
-showFormatted :: Maybe Utility.Format.Format -> S.ByteString -> [(String, String)] -> Annex ()
-showFormatted format unformatted vars =
+showFormatted :: IsTerminal -> Maybe Utility.Format.Format -> S.ByteString -> [(String, String)] -> Annex ()
+showFormatted (IsTerminal isterminal) format unformatted vars =
unlessM (showFullJSON $ JSONChunk vars) $
case format of
- Nothing -> liftIO $ S8.putStrLn unformatted
+ Nothing -> do
+ liftIO $ S8.putStrLn $ if isterminal
+ then Utility.Format.escapedFormat unformatted
+ else unformatted
Just formatter -> liftIO $ putStr $
Utility.Format.format formatter $
M.fromList vars
module Command.FindKeys where
import Command
-import qualified Utility.Format
import qualified Command.Find
+import qualified Utility.Format
+import Utility.SafeOutput
cmd :: Command
cmd = withAnnexOptions [keyMatchingOptions] $ Command.Find.mkCommand $
seek :: FindKeysOptions -> CommandSeek
seek o = do
+ isterminal <- liftIO $ checkIsTerminal stdout
seeker <- Command.Find.contentPresentUnlessLimited $ AnnexedFileSeeker
{ checkContentPresent = Nothing
, usesLocationLog = False
-- startAction is not actually used since this
-- is not used to seek files
- , startAction = \_ _ key -> start' o key
+ , startAction = \_ _ key -> start' o isterminal key
}
withKeyOptions (Just WantAllKeys) False seeker
- (commandAction . start o)
+ (commandAction . start o isterminal)
(const noop) (WorkTreeItems [])
-start :: FindKeysOptions -> (SeekInput, Key, ActionItem) -> CommandStart
-start o (_si, key, _ai) = start' o key
+start :: FindKeysOptions -> IsTerminal -> (SeekInput, Key, ActionItem) -> CommandStart
+start o isterminal (_si, key, _ai) = start' o isterminal key
-start' :: FindKeysOptions -> Key -> CommandStart
-start' o key = startingCustomOutput key $ do
- Command.Find.showFormatted (formatOption o) (serializeKey' key)
+start' :: FindKeysOptions -> IsTerminal -> Key -> CommandStart
+start' o isterminal key = startingCustomOutput key $ do
+ Command.Find.showFormatted isterminal (formatOption o) (serializeKey' key)
(Command.Find.formatVars key (AssociatedFile Nothing))
next $ return True
import Annex.UUID
import qualified Utility.Format
import qualified Command.Find
-import Utility.SafeOutput
import qualified Data.Map as M
import qualified Data.Vector as V
Format,
gen,
format,
+ escapedFormat,
formatContainsVar,
decode_c,
encode_c,
where
expand (Const s) = s
expand (Var name j esc)
- | esc = justify j $ decodeBS $ encode_c needescape $
+ | esc = justify j $ decodeBS $ escapedFormat $
encodeBS $ getvar name
| otherwise = justify j $ getvar name
getvar name = fromMaybe "" $ M.lookup name vars
justify (RightJustified i) s = pad i s ++ s
pad i s = take (i - length s) spaces
spaces = repeat ' '
+
+escapedFormat :: S.ByteString -> S.ByteString
+escapedFormat = encode_c needescape
+ where
needescape c = isUtf8Byte c ||
isSpace (chr (fromIntegral c)) ||
c == fromIntegral (ord '"')
- License: BSD-2-clause
-}
-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, CPP #-}
{-# OPTIONS_GHC -fno-warn-tabs #-}
-module Utility.SafeOutput (safeOutput) where
+module Utility.SafeOutput (
+ safeOutput,
+ IsTerminal(..),
+ checkIsTerminal,
+) where
import Data.Char
import qualified Data.ByteString as S
+import System.IO
+#ifdef mingw32_HOST_OS
+import System.Win32.MinTTY (isMinTTYHandle)
+import System.Win32.File
+import System.Win32.Types
+import Graphics.Win32.Misc
+import Control.Exception
+#endif
class SafeOutputtable t where
safeOutput :: t -> t
instance SafeOutputtable S.ByteString where
safeOutput = S.filter (not . isControl . chr . fromIntegral)
+
+newtype IsTerminal = IsTerminal Bool
+
+checkIsTerminal :: Handle -> IO IsTerminal
+checkIsTerminal h = do
+#ifndef mingw32_HOST_OS
+ b <- hIsTerminalDevice h
+ return (IsTerminal b)
+#else
+ b <- hIsTerminalDevice h
+ if b
+ then return (IsTerminal b)
+ else do
+ h' <- getStdHandle sTD_OUTPUT_HANDLE
+ `catch` \(_ :: IOError) ->
+ return nullHANDLE
+ if h == nullHANDLE
+ then return (IsTerminal False)
+ else do
+ b' <- isMinTTYHandle h'
+ return (IsTerminal b)
+#endif
provided to examinekey).
Also, '\\n' is a newline, '\\000' is a NULL, etc.
+
+ The default output format is the same as `--format='${escapedkey}\\n'`
+ when outputting to the terminal, and otherwise `--format='${key}\\n'`
* `--json`
Also, '\\n' is a newline, '\\000' is a NULL, etc.
- The default output format is the same as `--format='${file}\\n'`
+ The default output format is the same as `--format='${escaped_file}\\n'`
+ when outputting to the terminal, and otherwise `--format='${file}\\n'`
* `--json`
Also, '\\n' is a newline, '\\000' is a NULL, etc.
- The default output format is the same as `--format='${key}\\n'`
+ The default output format is the same as `--format='${escapedkey}\\n'`
+ when outputting to the terminal, and otherwise `--format='${key}\\n'`
* `--json`