packages feed

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"