1 {-# LANGUAGE OverloadedStrings #-}
2 module FuncTorrent.Bencode
13 import Prelude hiding (length, concat)
15 import Control.Applicative ((<*))
16 import Data.ByteString (ByteString, length, concat)
17 import Data.ByteString.Char8 (unpack, pack)
18 import Data.Functor ((<$>))
19 import Data.Map.Strict (Map, fromList, toList)
20 import Text.ParserCombinators.Parsec
21 import qualified Text.Parsec.ByteString as ParsecBS
23 data BVal = Bint Integer
26 | Bdict (Map String BVal)
27 deriving (Ord, Eq, Show)
30 bValToInteger :: BVal -> Maybe Integer
31 bValToInteger (Bint x) = Just x
32 bValToInteger _ = Nothing
34 bValToBytestr :: BVal -> Maybe ByteString
35 bValToBytestr (Bstr bs) = Just bs
36 bValToBytestr _ = Nothing
38 bValToBList :: BVal -> Maybe [BVal]
39 bValToBList (Blist lst) = Just lst
40 bValToBList _ = Nothing
42 bValToInfoDict :: BVal -> Maybe (Map String BVal)
43 bValToInfoDict (Bdict dict) = Just dict
44 bValToInfoDict _ = Nothing
46 bstrToString :: BVal -> Maybe String
47 bstrToString bval = unpack <$> bValToBytestr bval
50 -- >>> import Data.Either
54 -- >>> parse bencStr "Bstr" (pack "4:spam")
56 -- >>> parse bencStr "Bstr" (pack "0:")
58 -- >>> parse bencStr "Bstr" (pack "0:hello")
61 bencStr :: ParsecBS.Parser ByteString
62 bencStr = do ds <- many1 digit <* char ':'
63 s <- count (read ds) anyChar
68 -- >>> parse bencInt "Bint" (pack "i42e")
70 -- >>> parse bencInt "Bint" (pack "i123e")
72 -- >>> parse bencInt "Bint" (pack "i1e")
74 -- >>> parse bencInt "Bint" (pack "i0e")
76 -- >>> parse bencInt "Bint" (pack "i-1e")
78 -- >>> isLeft $ parse bencInt "Bint" (pack "i01e")
80 -- >>> isLeft $ parse bencInt "Bint" (pack "i00e")
82 -- >>> isLeft $ parse bencInt "Bint" (pack "i002e")
84 bencInt :: ParsecBS.Parser Integer
85 bencInt = do ds <- between (char 'i') (char 'e') numbers
87 where numbers = do d' <- char '-' <|> digit
90 parseNumber '0' [] = return "0"
91 parseNumber '0' _ = unexpected "numbers cannot be left-padded with zeros"
92 parseNumber '-' [] = unexpected "sign without any digits"
93 parseNumber '-' (d'':_) | d'' == '0' = unexpected "numbers cannot be left-padded with zeros"
94 parseNumber d'' ds'' = return (d'':ds'')
98 -- >>> parse bencList "Blist" (pack "le")
100 -- >>> parse bencList "Blist" (pack "l4:spam4:eggse")
101 -- Right [Bstr "spam",Bstr "eggs"]
102 -- >>> parse bencList "Blist" (pack "l4:spami42ee")
103 -- Right [Bstr "spam",Bint 42]
104 -- >>> parse bencList "Blist" (pack "l4:spam4:eggsli42eee")
105 -- Right [Bstr "spam",Bstr "eggs",Blist [Bint 42]]
106 bencList :: ParsecBS.Parser [BVal]
107 bencList = between (char 'l') (char 'e') (many bencVal)
111 -- >>> parse bencDict "Bdict" (pack "de")
112 -- Right (fromList [])
113 -- >>> parse bencDict "Bdict" (pack "d3:cow3:moo4:spam4:eggse")
114 -- Right (fromList [("cow",Bstr "moo"),("spam",Bstr "eggs")])
115 -- >>> parse bencDict "Bdict" (pack "d4:spaml1:a1:bee")
116 -- Right (fromList [("spam",Blist [Bstr "a",Bstr "b"])])
117 -- >>> parse bencDict "Bdict" (pack "d9:publisher3:bob17:publisher-webpage15:www.example.com18:publisher.location4:homee")
118 -- Right (fromList [("publisher",Bstr "bob"),("publisher-webpage",Bstr "www.example.com"),("publisher.location",Bstr "home")])
119 bencDict :: ParsecBS.Parser (Map String BVal)
120 bencDict = between (char 'd') (char 'e') $ fromList <$> many kvpair
121 where kvpair = do k <- bencStr
125 bencVal :: ParsecBS.Parser BVal
126 bencVal = Bstr <$> bencStr <|>
128 Blist <$> bencList <|>
131 decode :: ByteString -> Either ParseError BVal
132 decode = parse bencVal "BVal"
134 -- Encode BVal into a bencoded ByteString. Inverse of decode
136 -- TODO: Use builders and lazy byte string to get O(1) concatenation over O(n)
137 -- provided by lists.
139 -- TODO: encode . decode pair might be a good candidate for Quickcheck.
140 -- | encode bencoded-values
142 -- >>> encode (Bstr (pack ""))
144 -- >>> encode (Bstr (pack "spam"))
146 -- >>> encode (Bint 0)
148 -- >>> encode (Bint 42)
150 -- >>> encode (Blist [(Bstr (pack "spam")), (Bstr (pack "eggs"))])
152 -- >>> encode (Blist [])
154 -- >>> encode (Bdict (fromList [("spam", Bstr $ pack "eggs")]))
156 encode :: BVal -> ByteString
157 encode (Bstr bs) = pack $ show (length bs) ++ ":" ++ unpack bs
158 encode (Bint i) = pack $ "i" ++ show i ++ "e"
159 encode (Blist xs) = concat ["l", concat $ map encode xs, "e"]
160 encode (Bdict d) = concat ["d", concat kvlist, "e"]
162 kvlist :: [ByteString]
163 kvlist = [encPair kv | kv <- toList d]
164 encPair (k, v) = concat [encode . Bstr . pack $ k, encode v]