packages feed

shell-escape (empty) → 0.0.0

raw patch · 13 files changed

+514/−0 lines, 13 filesdep +basedep +binarydep +bytestringsetup-changed

Dependencies added: base, binary, bytestring, containers, vector

Files

+ LICENSE view
@@ -0,0 +1,28 @@++  ©2010 Jason Dusek.++  Redistribution and use in source and binary forms, with or without+  modification, are permitted provided that the following conditions are met:++ .  Redistributions of source code must retain the above copyright notice,+    this list of conditions and the following disclaimer.++ .  Redistributions in binary form must reproduce the above copyright notice,+    this list of conditions and the following disclaimer in the documentation+    and/or other materials provided with the distribution.++ .  Names of the contributors to this software may not be used to endorse or+    promote products derived from this software without specific prior written+    permission.++  This software is provided by the contributors "as is" and any express or+  implied warranties, including, but not limited to, the implied warranties of+  merchantability and fitness for a particular purpose are disclaimed. In no+  event shall the contributors be liable for any direct, indirect, incidental,+  special, exemplary, or consequential damages (including, but not limited to,+  procurement of substitute goods or services; loss of use, data, or profits;+  or business interruption) however caused and on any theory of liability,+  whether in contract, strict liability, or tort (including negligence or+  otherwise) arising in any way out of the use of this software, even if+  advised of the possibility of such damage. +
+ Makefile view
@@ -0,0 +1,47 @@++OPTS ?= --bash+RUNS ?= 10000+GHC = ghc --make -outputdir+++clean:+	rm -rf */build */echo bench/bench test/lengths* test/chars*+++.PHONY: bench+bench: bench/bench+	cd bench && ./bench++bench/bench: bench/Bench.hs+	mkdir -p bench/build+	${GHC} bench/build -O $< -o $@+++.PHONY: prof+prof: prof/echo+	cd prof && ./echo ${RUNS} +RTS -hb -RTS && hp2ps echo.hp++prof/echo: test/Echo.hs+	mkdir -p prof/build+	${GHC} prof/build test/Echo.hs -o prof/echo -prof -auto-all+++test: test/chars.histogram test/lengths.histogram++test/echo: test/Echo.hs+	mkdir -p test/build+	${GHC} test/build -O2 test/Echo.hs -o test/echo++.PHONY: test_run+test_run: test/echo+	rm -f test/chars* test/lengths*+	cd test && ./echo ${OPTS} ${RUNS}++test/chars: test_run+test/chars.histogram: test/chars+	sort < $< | uniq -c > $@++test/lengths: test_run+test/lengths.histogram: test/lengths+	sort -n < $< | uniq -c > $@+
+ README view
@@ -0,0 +1,1 @@+  Library for generating fully escaped strings for use with the shell.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ Text/ShellEscape.hs view
@@ -0,0 +1,14 @@++{-| Typed shell escaping for Bourne Shell and Bash.+ -}++module Text.ShellEscape+  ( Text.ShellEscape.Escape.Escape(..)+  , Text.ShellEscape.Sh.Sh()+  , Text.ShellEscape.Bash.Bash()+  ) where++import Text.ShellEscape.Escape+import Text.ShellEscape.Sh+import Text.ShellEscape.Bash+
+ Text/ShellEscape/Bash.hs view
@@ -0,0 +1,75 @@++module Text.ShellEscape.Bash where++import Data.Maybe+import Data.Char+import Text.Printf+import Data.ByteString (ByteString)++import qualified Data.Vector as Vector++import Text.ShellEscape.Escape+import qualified Text.ShellEscape.Put as Put+import Text.ShellEscape.EscapeVector++{-| A Bash escaped 'ByteString'. The strings are wrapped in @$\'...\'@ if any+    bytes within them must be escaped; otherwise, they are left as is.+    Newlines and other control characters are represented as ANSI escape+    sequences. High bytes are represented as hex codes. Thus Bash escaped+    strings will always fit on one line and never contain non-ASCII bytes.+ -}+newtype Bash                 =  Bash (EscapeVector EscapingMode)+ deriving (Eq, Ord, Show)+++instance Escape Bash where+  escape                     =  Bash . escWith classify+  unescape (Bash v)          =  stripEsc v+  bytes (Bash v) | literal v =  stripEsc v+                 | otherwise =  interpretEsc v renderANSI' end (begin, Literal)+   where+    literal                  =  isNothing . Vector.find ((/= Literal) . snd)+    begin                    =  [      Put.putString "$'"]+    end                      =  const (Put.putChar '\'')+    renderANSI' _ (c, e)     =  (renderANSI c, e)+++{-| Bash escaping modes.+ -}+data EscapingMode            =  ANSIHex | ANSIBackslash | Literal | Quoted+ deriving (Eq, Ord, Show)++renderANSI c =+  case classify c of+    Literal                 ->  Put.putChar     c+    Quoted                  ->  Put.putChar     c+    ANSIHex                 ->  Put.putString $ hexify c+    ANSIBackslash           ->  Put.putString $ backslashify c++backslashify                ::  Char -> String+backslashify '\ESC'          =  "\\e"+backslashify c               =  (take 2 . drop 1 . show) c++hexify                      ::  Char -> String+hexify                       =  printf "\\x%02X" . ord++classify                    ::  Char -> EscapingMode+classify c | c <= '\ACK'     =  ANSIHex+           | c <= '\r'       =  ANSIBackslash+           | c <= '\SUB'     =  ANSIHex+           | c == '\ESC'     =  ANSIBackslash+           | c <= '\US'      =  ANSIHex+           | c <= '&'        =  Quoted+           | c == '\''       =  ANSIBackslash+           | c <= '+'        =  Quoted+           | c <= '9'        =  Literal+           | c <= '?'        =  Quoted+           | c <= 'Z'        =  Literal+           | c == '['        =  Quoted+           | c == '\\'       =  ANSIBackslash+           | c <= '`'        =  Quoted+           | c <= 'z'        =  Literal+           | c <= '~'        =  Quoted+           | c == '\DEL'     =  ANSIHex+           | otherwise       =  ANSIHex+
+ Text/ShellEscape/Escape.hs view
@@ -0,0 +1,21 @@++module Text.ShellEscape.Escape where++import Data.ByteString++{-| A type class for objects that represent an intermediate state of+    escaping.+ -}+class Escape t where+  -- |    Transform a 'ByteString' into the escaped intermediate form.+  escape                    ::  ByteString -> t+  -- |    Recover the original 'ByteString'.+  unescape                  ::  t -> ByteString+  -- |    Yield the escaped 'ByteString'.+  bytes                     ::  t -> ByteString++instance Escape ByteString where+  escape                     =  id+  unescape                   =  id+  bytes                      =  id+
+ Text/ShellEscape/EscapeVector.hs view
@@ -0,0 +1,39 @@++module Text.ShellEscape.EscapeVector where++import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as ByteString++import Data.Vector (Vector)+import qualified Data.Vector as Vector+import qualified Data.Vector.Mutable as Vector++import qualified Text.ShellEscape.Put as Put+++type EscapeVector escapingMode = Vector (Char, escapingMode)++escWith :: (Char -> escapingMode) -> ByteString -> EscapeVector escapingMode+escWith cf b                 =  Vector.create $ do+  v                         <-  Vector.new (ByteString.length b)+  sequence_ . snd $ ByteString.foldl' (f v) (0, []) b+  return v+ where+  f v (i, ops) c             =  (i + 1, Vector.write v i (c, cf c) : ops)+++stripEsc                    ::  Vector (Char, escapingMode) -> ByteString+stripEsc v                   =  ByteString.unfoldr f . fst $ Vector.unzip v+ where+  f v | Vector.null v        =  Nothing+      | otherwise            =  Just (Vector.unsafeHead v, Vector.unsafeTail v)++interpretEsc v f finish init =  (eval . (finish lastMode :)) instructions+ where+  eval                       =  Put.runPut' . sequence_ . reverse+  (instructions, lastMode)   =  Vector.foldl' f' init v+   where+    f' (list, mode) (c, e)   =  (put:list, mode')+     where+      (put, mode')           =  f mode (c, e)+
+ Text/ShellEscape/Put.hs view
@@ -0,0 +1,24 @@++module Text.ShellEscape.Put+  ( module Data.Binary.Put+  , putChar+  , putString+  , runPut'+  ) where++import Prelude hiding (putChar)+import Data.Binary.Put+import Data.Char+import Data.ByteString.Lazy+import Data.ByteString+import Data.ByteString.Char8++putChar                     ::  Char -> Put+putChar                      =  putWord8 . toEnum . fromEnum++putString                   ::  String -> Put+putString                    =  putByteString . Data.ByteString.Char8.pack++runPut'                     ::  Put -> Data.ByteString.ByteString+runPut' = Data.ByteString.concat . Data.ByteString.Lazy.toChunks . runPut +
+ Text/ShellEscape/Sh.hs view
@@ -0,0 +1,64 @@++module Text.ShellEscape.Sh where++import Data.ByteString (ByteString)++import Text.ShellEscape.Escape+import qualified Text.ShellEscape.Put as Put+import Text.ShellEscape.EscapeVector+++{-| A Bourne Shell escaped 'ByteString'. An oddity of Bourne shell escaping is+    the absence of escape codes for newline and other ASCII control+    characters. These bytes are simply placed literally in single quotes; the+    effect is that a Bourne Shell escaped string may cover several lines and+    contain non-ASCII bytes. Runs of bytes that must be escaped are wrapped in+    @\'...\'@; bytes that are acceptable as literals in Bourne Shell are left+    as is.+ -}+newtype Sh                   =  Sh (EscapeVector EscapingMode)+ deriving (Eq, Ord, Show)++instance Escape Sh where+  escape                     =  Sh . escWith classify+  unescape (Sh v)            =  stripEsc v+  bytes (Sh v)               =  interpretEsc v act finish ([], Literal)+   where+    finish Quote             =  Put.putChar '\''+    finish Backslash         =  Put.putChar '\\'+    finish Literal           =  return ()+++{-| Accept the present escaping mode and desired escaping mode and yield an+    action and the resulting mode.+ -}+act :: EscapingMode -> (Char, EscapingMode) -> (Put.Put, EscapingMode)+act Quote (c, Quote)         =  (Put.putChar c                 , Quote)+act Quote (c, Literal)       =  (Put.putString ['\'', c]       , Literal)+act Quote (c, Backslash)     =  (Put.putString ['\'', '\\', c] , Literal)+act Backslash (c, Backslash) =  (Put.putChar c                 , Literal)+act Backslash (c, Quote)     =  (Put.putString ['\\', '\'', c] , Quote)+act Backslash (c, Literal)   =  (Put.putString ['\\', c]       , Literal)+act Literal (c, Literal)     =  (Put.putChar c                 , Literal)+act Literal (c, Backslash)   =  (Put.putString ['\\', c]       , Literal)+act Literal (c, Quote)       =  (Put.putString ['\'', c]       , Quote)++classify                    ::  Char -> EscapingMode+classify c | c <= '&'        =  Quote           --  0x00..0x26+           | c == '\''       =  Backslash       --  0x27+           | c <= ','        =  Quote           --  0x28..0x2c+           | c <= '9'        =  Literal         --  0x2d..0x39+           | c <= '?'        =  Quote           --  0x3a..0x3f+           | c <= 'Z'        =  Literal         --  0x40..0x5a+           | c <= '^'        =  Quote           --  0x5b..0x5e+           | c == '_'        =  Literal         --  0x5f+           | c == '`'        =  Quote           --  0x60+           | c <= 'z'        =  Literal         --  0x61..0x7a+           | c <= '\DEL'     =  Quote           --  0x7b..0x7f+           | otherwise       =  Quote           --  0x80..0xff++{-| Bourne Shell escaping modes. + -}+data EscapingMode            =  Backslash | Literal | Quote+ deriving (Eq, Ord, Show)+
+ bench/Bench.hs view
@@ -0,0 +1,26 @@++{-# LANGUAGE OverloadedStrings+  #-}++import Data.ByteString.Char8 (ByteString)++import Criterion.Main++import Text.ShellEscape+++strings                     ::  [ByteString]+strings =+  [ "echo * $PWD"+  , ""+  , "~/Music/M.I.A. & Diplo - Piracy Funds Terrorism Vol. 1 (2004)"+  , "abcds"+  , "\x00\n\204\DEL"+  , "\x00\n\204\DELecho * $PWD" ]++main = (defaultMain . concat . fmap (`fmap` strings)) [benchBash, benchSh]+ where+  bench' d f s = bench (d ++ show s) (whnf f s)+  benchBash = bench' "bash:" (escape :: ByteString -> Bash)+  benchSh = bench' "sh:" (escape :: ByteString -> Sh)+
+ shell-escape.cabal view
@@ -0,0 +1,33 @@+name                          : shell-escape+version                       : 0.0.0+category                      : Text+license                       : BSD3+license-file                  : LICENSE+author                        : Jason Dusek+maintainer                    : oss@solidsnack.be+homepage                      : http://github.com/solidsnack/shell-escape+synopsis                      : Shell escaping library.+description                   :+  Shell escaping library, offering both Bourne shell and Bash style escaping+  of ByteStrings.++cabal-version                 : >= 1.6+build-type                    : Simple+extra-source-files            : README+                              , bench/Bench.hs+                              , test/Echo.hs+                              , Makefile++library+  build-depends               : base >= 2 && <= 5+                              , binary+                              , containers+                              , vector >= 0.6.0.2+                              , bytestring >= 0.9+  exposed-modules             : Text.ShellEscape+  other-modules               : Text.ShellEscape.Escape+                                Text.ShellEscape.EscapeVector+                                Text.ShellEscape.Sh+                                Text.ShellEscape.Bash+                                Text.ShellEscape.Put+
+ test/Echo.hs view
@@ -0,0 +1,140 @@+{-# LANGUAGE OverloadedStrings+  #-}++import System.Environment+import System.Process (runInteractiveProcess, waitForProcess, ProcessHandle)+import System.IO (Handle, stderr, stdout, stdin)+import Data.ByteString (ByteString, pack, hGetContents, hPutStrLn)+import qualified Data.ByteString+import Data.ByteString.Char8 (unpack)+import qualified Data.ByteString.Char8 as Char8+import Data.Word+import Control.Monad+import qualified Text.Printf+import Data.IORef+import Data.List++import Test.QuickCheck+import Test.QuickCheck.Monadic++import Text.ShellEscape+++--  It is best to implement the echo test with `printf':+--+-- .  Some echo implementations always interprete backslash escapes like \3+--    and give us no way to turn it off. Dash is like this.+--+-- .  GNU echo can not be made to simply ignore options like `--help'.+--+--  The `printf' implementations available to me -- Bash and Dash --+--  consistently ignore backslash escape sequences and any options that follow+--  the format argument.+--+printf                      ::  (Shell t, Escape t) => t -> IO ByteString+printf escaped               =  do+  (i, o, e, p)              <-  shell escaped cmd+  exit                      <-  waitForProcess p+  hGetContents o+ where+  cmd                        =  "printf '%s' " ++ unpack raw+  raw                        =  bytes escaped+++prop_echoBash               ::  ByteString -> Property+prop_echoBash                =  something_prop escapeBash++prop_echoSh                 ::  ByteString -> Property+prop_echoSh                  =  something_prop escapeSh++something_prop esc b         =  monadicIO $ do+  assert =<< run (test printf b (esc b))++escapeSh                    ::  ByteString -> Sh+escapeSh b                   =  escape b++escapeBash                  ::  ByteString -> Bash+escapeBash b                 =  escape b++++test cmd original escaped    =  do+  b                         <-  cmd escaped+  Data.ByteString.appendFile "./lengths" (displayLength original)+  Data.ByteString.appendFile "./chars" (Char8.unlines $ displayBytes original)+  when (b /= original) $ do+    err "Result differs from unescaped result:"+    err "Escaped form:"+    err (Char8.unwords . displayBytes $ bytes escaped)+    err "Output:"+    err (Char8.unwords . displayBytes $ b)+    err "Original:"+    err (Char8.unwords . displayBytes $ original)+  return (b == original)+++displayBytes                 =  fmap Char8.pack+                             .  fmap (\w ->  Text.Printf.printf "0x%02x" w)+                             .  Data.ByteString.unpack++displayLength                =  Char8.pack+                             .  (\w ->  Text.Printf.printf "%4d\n" w)+                             .  Data.ByteString.length++class (Escape t) => Shell t where+  shell :: t -> String -> IO (Handle, Handle, Handle, ProcessHandle)+instance Shell Sh where+  shell _                    =  sh+instance Shell Bash where+  shell _                    =  bash+++sh s = runInteractiveProcess "sh" ["-c", s] Nothing Nothing+bash s = runInteractiveProcess "bash" ["-c", s] Nothing Nothing++instance Arbitrary ByteString where+  arbitrary                  =  do+    bytes                   <-  arbitrary :: Gen [Word8]+    NonEmpty bytes'         <-  arbitrary :: Gen (NonEmptyList Word8)+    pack `fmap` elements [bytes', bytes', bytes, bytes', bytes']++instance Arbitrary Word8 where+  arbitrary                  =  fmap fromIntegral (choose ( 0x01 :: Int+                                                          , 0xFF :: Int ))++main                         =  do+  runSh                     <-  newIORef True+  runBash                   <-  newIORef True+  testsR                    <-  newIORef 10000+  let testsRwrite            =  writeIORef testsR+      noBash                 =  writeIORef runBash False+      noSh                   =  writeIORef runSh False+  args                      <-  getArgs+  case (sort . nub) args of+    ["--bash", "--sh", d]   ->  testsRwrite (read d)+    ["--bash", "--sh"   ]   ->  return ()+    ["--bash",         d]   ->  noSh >> testsRwrite (read d)+    [          "--sh", d]   ->  noBash >> testsRwrite (read d)+    [                  d]   ->  testsRwrite (read d)+    [                   ]   ->  return ()+    _                       ->  error "Invalid arguments."+  tests                     <-  readIORef testsR+  let msg                    =  "Performing " ++ show tests ++ " tests."+      qcArgs                 =  Args Nothing tests tests 32+      qc                     =  quickCheckWith qcArgs+  err "Tests are random ByteStrings, containing any byte but null."+  runSh ?> do+    err "Testing Sh escaping."+    err (Char8.pack msg)+    qc prop_echoSh+  runBash ?> do+    err "Testing Bash escaping."+    err (Char8.pack msg)+    qc prop_echoBash++err                          =  hPutStrLn stderr++(?>)                        ::  IORef Bool -> IO () -> IO ()+ref ?> action                =  readIORef ref >>= (`when` action)++