1 {-# LANGUAGE OverloadedStrings #-}
2 module FuncTorrent.Tracker
10 import Prelude hiding (lookup, concat, replicate, splitAt)
11 import Data.ByteString.Char8 (ByteString, unpack, splitAt)
12 import Data.Char (chr)
13 import Data.List (intercalate)
14 import Data.Map as M (lookup)
15 import Data.Maybe (fromJust)
16 import Network.HTTP (simpleHTTP, defaultGETRequest_, getResponseBody)
17 import Network.HTTP.Base (urlEncode)
18 import Network.URI (parseURI)
19 import qualified Data.ByteString.Base16 as B16 (encode)
21 import FuncTorrent.Bencode (BVal(..), InfoDict)
22 import FuncTorrent.Metainfo (infoHash)
23 import FuncTorrent.Peer (Peer(..))
24 import FuncTorrent.Utils (splitN)
28 data TrackerResponse = TrackerResponse {
29 interval :: Maybe Integer
31 , complete :: Maybe Integer
32 , incomplete :: Maybe Integer
37 -- | Deserialize tracker response
38 mkTrackerResponse :: BVal -> Either ByteString TrackerResponse
39 mkTrackerResponse resp =
40 case lookup "failure reason" body of
41 Just (Bstr err) -> Left err
42 Just _ -> Left "Unknown failure"
44 let (Just (Bint i)) = lookup "interval" body
45 (Just (Bstr peersBS)) = lookup "peers" body
46 pl = map makePeer (splitN 6 peersBS)
47 in Right TrackerResponse {
51 , incomplete = Nothing
56 toInt :: String -> Integer
59 toPort :: ByteString -> Integer
60 toPort = read . ("0x" ++) . unpack . B16.encode
62 toIP :: ByteString -> String
63 toIP = intercalate "." .
64 map (show . toInt . ("0x" ++) . unpack) .
67 makePeer :: ByteString -> Peer
68 makePeer peer = Peer (toIP ip') (toPort port')
69 where (ip', port') = splitAt 4 peer
74 -- >>> urlEncodeHash $ pack "123456789abcdef123456789abcdef123456789a"
75 -- "%124Vx%9a%bc%de%f1%23Eg%89%ab%cd%ef%124Vx%9a"
76 urlEncodeHash :: ByteString -> String
77 urlEncodeHash bs = concatMap (encode' . unpack) (splitN 2 bs)
78 where encode' b@[c1, c2] = let c = chr (read ("0x" ++ b))
81 escape i c1 c2 | i `elem` nonSpecialChars = [i]
82 | otherwise = "%" ++ [c1] ++ [c2]
84 nonSpecialChars = ['A'..'Z'] ++ ['a'..'z'] ++ ['0'..'9'] ++ "-_.~"
86 prepareRequest :: InfoDict -> String -> Integer -> String
87 prepareRequest d peer_id len =
88 let p = [("info_hash", urlEncodeHash ((B16.encode . infoHash) d)),
89 ("peer_id", urlEncode peer_id),
96 in intercalate "&" [f ++ "=" ++ s | (f,s) <- p]
98 connect :: Url -> String -> IO ByteString
99 connect baseurl qstr = simpleHTTP (defaultGETRequest_ url) >>= getResponseBody
100 where url = fromJust . parseURI $ (baseurl ++ "?" ++ qstr)