From a576fc3b1272cfb1b015acb5e46712c992483fa9 Mon Sep 17 00:00:00 2001 From: Joey Hess Date: Wed, 12 Apr 2023 13:48:21 -0400 Subject: [PATCH] fix mojibake reversion in display of utf8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When displaying a ByteString like "💕", safeOutput operates on individual bytes like "\240\159\146\149" and isControl '\146' = True, so it got truncated to just "\240". So, only treat the low control characters, and DEL, as control characters. Also split Utility.Terminal out of Utility.SafeOutput. The latter needs win32, but Utility.SafeOutput is used by Control.Exception, which is used by Setup. Sponsored-by: Nicholas Golder-Manning on Patreon --- Annex/UntrustedFilePath.hs | 7 +++--- Command/ExamineKey.hs | 2 +- Command/Find.hs | 2 +- Command/FindKeys.hs | 2 +- Utility/SafeOutput.hs | 42 ++++++---------------------------- Utility/Terminal.hs | 47 ++++++++++++++++++++++++++++++++++++++ git-annex.cabal | 1 + 7 files changed, 61 insertions(+), 42 deletions(-) create mode 100644 Utility/Terminal.hs diff --git a/Annex/UntrustedFilePath.hs b/Annex/UntrustedFilePath.hs index 0b2777ab47..df7ac3bd35 100644 --- a/Annex/UntrustedFilePath.hs +++ b/Annex/UntrustedFilePath.hs @@ -10,6 +10,8 @@ module Annex.UntrustedFilePath where import Data.Char import System.FilePath +import Utility.SafeOutput + {- Given a string that we'd like to use as the basis for FilePath, but that - was provided by a third party and is not to be trusted, returns the closest - sane FilePath. @@ -55,10 +57,7 @@ sanitizeLeadingFilePathCharacter s = s controlCharacterInFilePath :: FilePath -> Bool controlCharacterInFilePath = any (not . safechar) where - safechar c - | not (isControl c) = True - | c == '\t' = True - | otherwise = False + safechar c = safeOutputChar c && c /= '\n' {- ../ is a path traversal, no matter where it appears. - diff --git a/Command/ExamineKey.hs b/Command/ExamineKey.hs index 73dd77f7c5..b3a3cbab21 100644 --- a/Command/ExamineKey.hs +++ b/Command/ExamineKey.hs @@ -16,7 +16,7 @@ import Annex.Link import Backend import Types.Backend import Types.Key -import Utility.SafeOutput +import Utility.Terminal import Data.Char import qualified Data.ByteString as B diff --git a/Command/Find.hs b/Command/Find.hs index 05bd17f76f..5dd6a4aac8 100644 --- a/Command/Find.hs +++ b/Command/Find.hs @@ -19,7 +19,7 @@ import Types.Key import Git.FilePath import qualified Utility.Format import Utility.DataUnits -import Utility.SafeOutput +import Utility.Terminal cmd :: Command cmd = withAnnexOptions [annexedMatchingOptions] $ mkCommand $ diff --git a/Command/FindKeys.hs b/Command/FindKeys.hs index f2a86cde50..e24075dacb 100644 --- a/Command/FindKeys.hs +++ b/Command/FindKeys.hs @@ -10,7 +10,7 @@ module Command.FindKeys where import Command import qualified Command.Find import qualified Utility.Format -import Utility.SafeOutput +import Utility.Terminal cmd :: Command cmd = withAnnexOptions [keyMatchingOptions] $ Command.Find.mkCommand $ diff --git a/Utility/SafeOutput.hs b/Utility/SafeOutput.hs index 275797adda..ee37d2c274 100644 --- a/Utility/SafeOutput.hs +++ b/Utility/SafeOutput.hs @@ -11,55 +11,27 @@ module Utility.SafeOutput ( safeOutput, - IsTerminal(..), - checkIsTerminal, + safeOutputChar, ) 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 String where - safeOutput = filter safeChar + safeOutput = filter safeOutputChar instance SafeOutputtable S.ByteString where - safeOutput = S.filter (safeChar . chr . fromIntegral) + safeOutput = S.filter (safeOutputChar . chr . fromIntegral) -safeChar :: Char -> Bool -safeChar c +safeOutputChar :: Char -> Bool +safeOutputChar c | not (isControl c) = True | c == '\n' = True | c == '\t' = True + | c == '\DEL' = False + | ord c > 31 = True | otherwise = False - -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 diff --git a/Utility/Terminal.hs b/Utility/Terminal.hs new file mode 100644 index 0000000000..6835478410 --- /dev/null +++ b/Utility/Terminal.hs @@ -0,0 +1,47 @@ +{- Determining if output is to a terminal. + - + - Copyright 2023 Joey Hess + - + - License: BSD-2-clause + -} + +{-# LANGUAGE CPP #-} +{-# OPTIONS_GHC -fno-warn-tabs #-} + +module Utility.Terminal ( + 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 + +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 diff --git a/git-annex.cabal b/git-annex.cabal index f249e95af1..c11047d2da 100644 --- a/git-annex.cabal +++ b/git-annex.cabal @@ -1144,6 +1144,7 @@ Executable git-annex Utility.SshHost Utility.Su Utility.SystemDirectory + Utility.Terminal Utility.TimeStamp Utility.TList Utility.Tense