fix: Only escape special characters by single quoting, not escaping *and* quoting

This commit is contained in:
Caleb Maclennan 2025-10-02 09:50:33 -06:00 committed by Simon Michael
parent 8cbe4c6003
commit 3c731ae2f7
2 changed files with 8 additions and 19 deletions

View File

@ -15,7 +15,6 @@ module Hledger.Utils.String (
-- quotechars, -- quotechars,
-- whitespacechars, -- whitespacechars,
words', words',
unwords',
stripAnsi, stripAnsi,
-- * single-line layout -- * single-line layout
strip, strip,
@ -165,26 +164,25 @@ singleQuote s = "'"++s++"'"
-- >>> quoteForCommandLine "\"" -- >>> quoteForCommandLine "\""
-- "'\"'" -- "'\"'"
-- >>> quoteForCommandLine "$" -- >>> quoteForCommandLine "$"
-- "'\\$'" -- "'$'"
-- --
quoteForCommandLine :: String -> String quoteForCommandLine :: String -> String
quoteForCommandLine s quoteForCommandLine s
| any (`elem` s) (quotechars++whitespacechars++shellchars) = singleQuote $ quoteShellChars s | any (`elem` s) (quotechars++whitespacechars++shellchars) = singleQuote $ escapeSingleQuotes s
| otherwise = s | otherwise = s
-- | Try to backslash-quote common shell-significant characters in this string. -- | Escape single quotes appearing in a string we're protecting by wrapping in single quotes
-- Doesn't handle single quotes, & probably others. escapeSingleQuotes :: String -> String
quoteShellChars :: String -> String escapeSingleQuotes = concatMap escapeSingleQuote
quoteShellChars = concatMap escapeShellChar
where where
escapeShellChar c | c `elem` shellchars = ['\\',c] escapeSingleQuote c | c `elem` "'" = ['\\',c]
escapeShellChar c = [c] escapeSingleQuote c = [c]
quotechars, whitespacechars, redirectchars, shellchars :: [Char] quotechars, whitespacechars, redirectchars, shellchars :: [Char]
quotechars = "'\"" quotechars = "'\""
whitespacechars = " \t\n\r" whitespacechars = " \t\n\r"
redirectchars = "<>" redirectchars = "<>"
shellchars = "<>(){}[]$&?#!~`" shellchars = "<>(){}[]$&?#!~`*+\\"
-- | Quote-aware version of words - don't split on spaces which are inside quotes. -- | Quote-aware version of words - don't split on spaces which are inside quotes.
-- NB correctly handles "a'b" but not "''a''". Can raise an error if parsing fails. -- NB correctly handles "a'b" but not "''a''". Can raise an error if parsing fails.
@ -198,10 +196,6 @@ words' s = map stripquotes $ fromparse $ parsewithString p s -- PARTIAL
singleQuotedPattern = between (char '\'') (char '\'') (many $ noneOf "'") singleQuotedPattern = between (char '\'') (char '\'') (many $ noneOf "'")
doubleQuotedPattern = between (char '"') (char '"') (many $ noneOf "\"") doubleQuotedPattern = between (char '"') (char '"') (many $ noneOf "\"")
-- | Quote-aware version of unwords - single-quote strings which contain whitespace
unwords' :: [String] -> String
unwords' = unwords . map quoteIfNeeded
-- | Strip one matching pair of single or double quotes on the ends of a string. -- | Strip one matching pair of single or double quotes on the ends of a string.
stripquotes :: String -> String stripquotes :: String -> String
stripquotes s = if isSingleQuoted s || isDoubleQuoted s then init $ tailErr s else s -- PARTIAL tailErr won't fail because isDoubleQuoted stripquotes s = if isSingleQuoted s || isDoubleQuoted s then init $ tailErr s else s -- PARTIAL tailErr won't fail because isDoubleQuoted

View File

@ -23,7 +23,6 @@ module Hledger.Utils.Text
-- escapeSingleQuotes, -- escapeSingleQuotes,
-- escapeQuotes, -- escapeQuotes,
-- words', -- words',
-- unwords',
stripquotes, stripquotes,
-- isSingleQuoted, -- isSingleQuoted,
-- isDoubleQuoted, -- isDoubleQuoted,
@ -162,10 +161,6 @@ escapeBackslash = T.replace "\\" "\\\\"
-- singleQuotedPattern = between (char '\'') (char '\'') (many $ noneOf "'") -- singleQuotedPattern = between (char '\'') (char '\'') (many $ noneOf "'")
-- doubleQuotedPattern = between (char '"') (char '"') (many $ noneOf "\"") -- doubleQuotedPattern = between (char '"') (char '"') (many $ noneOf "\"")
-- -- | Quote-aware version of unwords - single-quote strings which contain whitespace
-- unwords' :: [Text] -> Text
-- unwords' = T.unwords . map quoteIfNeeded
-- | Strip one matching pair of single or double quotes on the ends of a string. -- | Strip one matching pair of single or double quotes on the ends of a string.
stripquotes :: Text -> Text stripquotes :: Text -> Text
stripquotes s = if isSingleQuoted s || isDoubleQuoted s then T.init $ T.tail s else s stripquotes s = if isSingleQuoted s || isDoubleQuoted s then T.init $ T.tail s else s