scp-streams-0.1.0: Network/SCP/Types.hs
{-# LANGUAGE OverloadedStrings #-}
module Network.SCP.Types where
import Control.Applicative ((<$>), (<|>), (<$))
import Data.Attoparsec.ByteString (anyWord8)
import Data.Attoparsec.ByteString.Char8
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as C
import Prelude hiding (takeWhile)
import Data.Word (Word8)
data Command =
Copy Word8 Word8 Word8 Word8 Int ByteString
-- ^ Copy a file. Permissions (4 bytes), file size in bytes, filename.
| Push Word8 Word8 Word8 Word8 ByteString
-- ^ Enter directory. Permissions, directory name.
| Pop
-- ^ Exit directory.
| Abort ByteString
-- ^ This is not really a command, it is an error message received from the
-- client. For instance when the client sends a non-existing file and
-- discovers the fact only after having started the upload process.
deriving Show
-- e.g. C0755 567 run.sh
commandParser :: Parser Command
commandParser = copyParser <|> pushParser <|> popParser <|> errorParser
copyParser :: Parser Command
copyParser = do
_ <- char 'C'
(a, b, c, d) <- permissionsParser
_ <- char ' '
size <- decimal
_ <- char ' '
filename <- takeWhile (/= '\n') -- TODO enforce correct filename (no slash, no single dot, ...)
_ <- char '\n'
return $ Copy a b c d size filename
pushParser :: Parser Command
pushParser = do
_ <- char 'D'
(a, b, c, d) <- permissionsParser
_ <- string " 0 "
dir <- takeWhile (/= '\n')
_ <- char '\n'
return $ Push a b c d dir
popParser :: Parser Command
popParser = Pop <$ string "E\n"
errorParser :: Parser Command
errorParser = do
_ <- anyWord8
msg <- takeWhile (/= '\n')
_ <- char '\n'
return $ Abort msg
permissionsParser :: Parser (Word8, Word8, Word8, Word8)
permissionsParser = do
a <- read . (:[]) <$> digit
b <- read . (:[]) <$> digit
c <- read . (:[]) <$> digit
d <- read . (:[]) <$> digit
return (a, b, c, d)
-- TODO use cereal
unparse :: Command -> ByteString
unparse command = case command of
(Copy a b c d size filename) ->
C.pack ['C', head $ show a, head $ show b, head $ show c, head $ show d]
`B.append` " " `B.append` C.pack (show size) `B.append` " "
`B.append` filename
(Push a b c d filename) ->
C.pack ['D', head $ show a, head $ show b, head $ show c, head $ show d]
`B.append` " 0 " `B.append` filename
Pop -> "E"
Abort msg -> 1 `B.cons` msg `B.append` "\n"