torrent 2007.10.27 → 10000.0.0
raw patch · 8 files changed
+134/−388 lines, 8 filesdep +bytestringdep +containersdep +sybdep −networkdep ~basesetup-changednew-uploader
Dependencies added: bytestring, containers, syb
Dependencies removed: network
Dependency ranges changed: base
Files
- CHANGELOG +11/−0
- Setup.hs +2/−1
- cbits/sha1.c +0/−166
- cbits/sha1.h +0/−75
- src/Data/Torrent.hs +92/−94
- src/Data/Torrent/SHA1.hs +0/−32
- src/Data/Torrent/Scrape.hs +9/−5
- torrent.cabal +20/−15
+ CHANGELOG view
@@ -0,0 +1,11 @@+haskell-torrent (10000.0.0) unstable; urgency=medium++ * Taking over upstream maintenance of this package.+ * Updated to build with the current haskell ecosystem.+ * Removed SHA1 implementation for simplicity, and removed iInfoHash+ from Torrent.+ * Use the PVP rather than date-based version.+ * Some data type changes, including using Integer rather than Int+ for file sizes.++ -- Joey Hess <id@joeyh.name> Thu, 18 Dec 2014 13:15:42 -0400
Setup.hs view
@@ -1,2 +1,3 @@ import Distribution.Simple-main = defaultMainWithHooks defaultUserHooks++main = defaultMain
− cbits/sha1.c
@@ -1,166 +0,0 @@-/*- * sha1.c- *- * Originally witten by Steve Reid <steve@edmweb.com>- * - * Modified by Aaron D. Gifford <agifford@infowest.com>- *- * NO COPYRIGHT - THIS IS 100% IN THE PUBLIC DOMAIN- *- * The original unmodified version is available at:- * ftp://ftp.funet.fi/pub/crypt/hash/sha/sha1.c- *- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND 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 AUTHOR(S) OR 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.- */--#include "sha1.h"-#include <string.h>--#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))--/* blk0() and blk() perform the initial expand. */-/* I got the idea of expanding during the round function from SSLeay */--#ifdef LITTLE_ENDIAN-#define blk0(i) (block->l[i] = (rol(block->l[i],24)&(sha1_quadbyte)0xFF00FF00) \- |(rol(block->l[i],8)&(sha1_quadbyte)0x00FF00FF))-#else-#define blk0(i) block->l[i]-#endif--#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \- ^block->l[(i+2)&15]^block->l[i&15],1))--/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */-#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);-#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);-#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);-#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);-#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);--typedef union _BYTE64QUAD16 {- sha1_byte c[64];- sha1_quadbyte l[16];-} BYTE64QUAD16;--/* Utility function for easy use in Haskell */-void SHA1(sha1_byte *data, unsigned int len, sha1_byte digest[SHA1_DIGEST_LENGTH])-{- SHA_CTX context;- SHA1_Init(&context);- SHA1_Update(&context,data,len);- SHA1_Final(digest,&context);-}--/* Hash a single 512-bit block. This is the core of the algorithm. */-void SHA1_Transform(sha1_quadbyte state[5], sha1_byte buffer[64]) {- sha1_quadbyte a, b, c, d, e;- BYTE64QUAD16 *block;-- block = (BYTE64QUAD16*)buffer;- /* Copy context->state[] to working vars */- a = state[0];- b = state[1];- c = state[2];- d = state[3];- e = state[4];- /* 4 rounds of 20 operations each. Loop unrolled. */- R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);- R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);- R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);- R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);- R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);- R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);- R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);- R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);- R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);- R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);- R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);- R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);- R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);- R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);- R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);- R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);- R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);- R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);- R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);- R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);- /* Add the working vars back into context.state[] */- state[0] += a;- state[1] += b;- state[2] += c;- state[3] += d;- state[4] += e;- /* Wipe variables */- a = b = c = d = e = 0;-}---/* SHA1_Init - Initialize new context */-void SHA1_Init(SHA_CTX* context) {- /* SHA1 initialization constants */- context->state[0] = 0x67452301;- context->state[1] = 0xEFCDAB89;- context->state[2] = 0x98BADCFE;- context->state[3] = 0x10325476;- context->state[4] = 0xC3D2E1F0;- context->count[0] = context->count[1] = 0;-}--/* Run your data through this. */-void SHA1_Update(SHA_CTX *context, sha1_byte *data, unsigned int len) {- unsigned int i, j;-- j = (context->count[0] >> 3) & 63;- if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++;- context->count[1] += (len >> 29);- if ((j + len) > 63) {- memcpy(&context->buffer[j], data, (i = 64-j));- SHA1_Transform(context->state, context->buffer);- for ( ; i + 63 < len; i += 64) {- SHA1_Transform(context->state, &data[i]);- }- j = 0;- }- else i = 0;- memcpy(&context->buffer[j], &data[i], len - i);-}---/* Add padding and return the message digest. */-void SHA1_Final(sha1_byte digest[SHA1_DIGEST_LENGTH], SHA_CTX *context) {- sha1_quadbyte i, j;- sha1_byte finalcount[8];-- for (i = 0; i < 8; i++) {- finalcount[i] = (sha1_byte)((context->count[(i >= 4 ? 0 : 1)]- >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */- }- SHA1_Update(context, (sha1_byte *)"\200", 1);- while ((context->count[0] & 504) != 448) {- SHA1_Update(context, (sha1_byte *)"\0", 1);- }- /* Should cause a SHA1_Transform() */- SHA1_Update(context, finalcount, 8);- for (i = 0; i < SHA1_DIGEST_LENGTH; i++) {- digest[i] = (sha1_byte)- ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);- }- /* Wipe variables */- i = j = 0;- memset(context->buffer, 0, SHA1_BLOCK_LENGTH);- memset(context->state, 0, SHA1_DIGEST_LENGTH);- memset(context->count, 0, 8);- memset(&finalcount, 0, 8);-}-
− cbits/sha1.h
@@ -1,75 +0,0 @@-/*- * sha.h- *- * Originally taken from the public domain SHA1 implementation- * written by by Steve Reid <steve@edmweb.com>- * - * Modified by Aaron D. Gifford <agifford@infowest.com>- *- * NO COPYRIGHT - THIS IS 100% IN THE PUBLIC DOMAIN- *- * The original unmodified version is available at:- * ftp://ftp.funet.fi/pub/crypt/hash/sha/sha1.c- *- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND 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 AUTHOR(S) OR 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.- */--#ifndef __SHA1_H__-#define __SHA1_H__--#ifdef __cplusplus-extern "C" {-#endif--/* Define this if your machine is LITTLE_ENDIAN, otherwise #undef it: */-#define LITTLE_ENDIAN--/* Make sure you define these types for your architecture: */-typedef unsigned int sha1_quadbyte; /* 4 byte type */-typedef unsigned char sha1_byte; /* single byte type */--/*- * Be sure to get the above definitions right. For instance, on my- * x86 based FreeBSD box, I define LITTLE_ENDIAN and use the type- * "unsigned long" for the quadbyte. On FreeBSD on the Alpha, however,- * while I still use LITTLE_ENDIAN, I must define the quadbyte type- * as "unsigned int" instead.- */--#define SHA1_BLOCK_LENGTH 64-#define SHA1_DIGEST_LENGTH 20--/* The SHA1 structure: */-typedef struct _SHA_CTX {- sha1_quadbyte state[5];- sha1_quadbyte count[2];- sha1_byte buffer[SHA1_BLOCK_LENGTH];-} SHA_CTX;--#ifndef NOPROTO-void SHA1(sha1_byte *data, unsigned int len, sha1_byte digest[SHA1_DIGEST_LENGTH]);-void SHA1_Init(SHA_CTX *context);-void SHA1_Update(SHA_CTX *context, sha1_byte *data, unsigned int len);-void SHA1_Final(sha1_byte digest[SHA1_DIGEST_LENGTH], SHA_CTX* context);-#else-void SHA1_Init();-void SHA1_Update();-void SHA1_Final();-#endif--#ifdef __cplusplus-}-#endif--#endif-
src/Data/Torrent.hs view
@@ -1,119 +1,117 @@-{-# OPTIONS -fglasgow-exts #-}-module Data.Torrent- ( Torrent(..)- , TorrentInfo(..)- , TorrentFile(..)- , readTorrent -- :: ByteString -> Either String Torrent- , serializeTorrent- , torrentSize -- :: Torrent -> Int- ) where+{-# LANGUAGE DeriveDataTypeable #-} --- import Network.URI+module Data.Torrent+ ( Torrent(..)+ , TorrentInfo(..)+ , TorrentFile(..)+ , readTorrent+ , serializeTorrent+ , torrentSize+ ) where import Data.BEncode import Data.BEncode.Parser-import Data.Torrent.SHA1-import Data.Maybe import Data.Binary import Data.Generics -import qualified Data.ByteString.Char8 as BS-import Data.ByteString (ByteString)+import qualified Data.ByteString.Lazy as BS+import Data.ByteString.Lazy (ByteString) import qualified Data.Map as Map data Torrent- = Torrent- { tAnnounce :: ByteString- , tAnnounceList :: [ByteString]- , tComment :: ByteString- , tCreatedBy :: Maybe ByteString- , tInfo :: TorrentInfo- , tInfoHash :: ByteString- } deriving (Show, Read, Typeable, Data)+ = Torrent+ { tAnnounce :: ByteString+ , tAnnounceList :: [ByteString]+ , tComment :: ByteString+ , tCreatedBy :: Maybe ByteString+ , tInfo :: TorrentInfo+ }+ deriving (Show, Read, Typeable, Data) data TorrentInfo- = SingleFile- { tLength :: Int- , tName :: ByteString- , tPieceLength :: Int- , tPieces :: ByteString }- | MultiFile- { tFiles :: [TorrentFile]- , tName :: ByteString- , tPieceLength :: Int- , tPieces :: ByteString- } deriving (Show, Read, Typeable, Data)+ = SingleFile+ { tLength :: Integer+ , tName :: ByteString+ , tPieceLength :: Integer+ , tPieces :: ByteString }+ | MultiFile+ { tFiles :: [TorrentFile]+ , tName :: ByteString+ , tPieceLength :: Integer+ , tPieces :: ByteString+ }+ deriving (Show, Read, Typeable, Data) data TorrentFile- = TorrentFile- { fileLength :: Int- , filePath :: [ByteString]- } deriving (Show, Read, Typeable, Data)+ = TorrentFile+ { fileLength :: Integer+ , filePath :: [ByteString]+ }+ deriving (Show, Read, Typeable, Data) instance Binary Torrent where- put = put . serializeTorrent- get = do e <- get- case readTorrent e of- Left err -> fail $ "Failed to parse torrent: " ++ err- Right t -> return t---torrentSize :: Torrent -> Int-torrentSize torrent- = case tInfo torrent of- s@SingleFile{} -> tLength s- MultiFile{tFiles=files} -> sum (map fileLength files)+ put = put . serializeTorrent+ get = do+ e <- get+ case readTorrent e of+ Left err -> fail $ "Failed to parse torrent: " ++ err+ Right t -> return t -{--buri :: BParser BEncode -> BParser URI-buri p = do str <- bstring p- case parseURI str of- Nothing -> fail $ "Expected URI: " ++ str- Just uri -> return uri--}+-- | Size of the files in the torrent.+torrentSize :: Torrent -> Integer+torrentSize torrent = case tInfo torrent of+ s@SingleFile{} -> tLength s+ MultiFile{tFiles=files} -> sum (map fileLength files) readTorrent :: ByteString -> Either String Torrent-readTorrent inp- = case bRead inp of- Nothing -> Left "Not BEncoded"- Just be -> runParser parseTorrent be+readTorrent inp = case bRead inp of+ Nothing -> Left "Not BEncoded"+ Just be -> runParser parseTorrent be parseTorrent :: BParser Torrent-parseTorrent =- do announce <- bbytestring $ dict "announce"- creator <- optional $ bbytestring $ dict "created by"- info <- dict "info"- setInput info- name <- bbytestring $ dict "name"- pLen <- bint $ dict "piece length"- pieces <- bbytestring $ dict "pieces"- torrentInfo <- parseTorrentInfo name pLen pieces- let infoHash = sha1 (BS.pack $ bShow info "")- return $ Torrent announce [] BS.empty creator torrentInfo infoHash+parseTorrent = do+ announce <- bbytestring $ dict "announce"+ creator <- optional $ bbytestring $ dict "created by"+ info <- dict "info"+ setInput info+ name <- bbytestring $ dict "name"+ pLen <- bint $ dict "piece length"+ pieces <- bbytestring $ dict "pieces"+ torrentInfo <- parseTorrentInfo name pLen pieces+ return $ Torrent announce [] BS.empty creator torrentInfo -parseTorrentInfo :: ByteString -> Int -> ByteString -> BParser TorrentInfo-parseTorrentInfo name pLen pieces- = do len <- bint $ dict "length"- return $ SingleFile len name pLen pieces- <|>- do files <- list "files" $ do len <- bint $ dict "length"- filePaths <- list "path" $ bbytestring token- return $ TorrentFile len filePaths- return $ MultiFile files name pLen pieces+parseTorrentInfo :: ByteString -> Integer -> ByteString -> BParser TorrentInfo+parseTorrentInfo name pLen pieces = single <|> multi+ where+ single = do+ len <- bint $ dict "length"+ return $ SingleFile len name pLen pieces+ multi = do+ files <- list "files" $ do+ len <- bint $ dict "length"+ filePaths <- list "path" $ bbytestring token+ return $ TorrentFile len filePaths+ return $ MultiFile files name pLen pieces serializeTorrent :: Torrent -> BEncode-serializeTorrent torrent- = BDict $ Map.fromList [("announce",BString $ tAnnounce torrent)- ,("comment", BString $ tComment torrent)- ,("info",info)- ]- where info = BDict $ Map.fromList $ [("name", BString $ tName (tInfo torrent))- ,("pieces", BString $ tPieces (tInfo torrent))- ,("piece length", BInt $ tPieceLength (tInfo torrent))- ] ++ case tInfo torrent of- SingleFile len _ _ _ -> [("length", BInt len)]- MultiFile files _ _ _ -> [("files", BList $ flip map files $ \file ->- BDict $ Map.fromList [("length", BInt (fileLength file))- ,("path", BList (map BString $ filePath file))]- )]+serializeTorrent torrent = BDict $ Map.fromList+ [ ("announce", BString $ tAnnounce torrent)+ , ("comment", BString $ tComment torrent)+ , ("info", info)+ ]+ where+ info = BDict $ Map.fromList $+ [ ("name", BString $ tName (tInfo torrent))+ , ("pieces", BString $ tPieces (tInfo torrent))+ , ("piece length", BInt $ tPieceLength (tInfo torrent))+ ] ++ case tInfo torrent of+ SingleFile len _ _ _ ->+ [ ("length", BInt len) ]+ MultiFile files _ _ _ ->+ [ ("files", BList $ map serfile files) ]++ serfile file = BDict $ Map.fromList+ [ ("length", BInt (fileLength file))+ , ("path", BList (map BString $ filePath file))+ ]
− src/Data/Torrent/SHA1.hs
@@ -1,32 +0,0 @@--------------------------------------------------------------------------------- |--- Module : Data.Torrent.SHA1--- Copyright : (c) Lemmih 2005--- License : BSD-like------ Maintainer : lemmih@gmail.com--- Stability : experimental--- Portability : portable----------------------------------------------------------------------------------module Data.Torrent.SHA1 (sha1) where--import Foreign-import Foreign.C-import System.IO.Unsafe--import qualified Data.ByteString as BS-import qualified Data.ByteString.Base as BS-import Data.ByteString (ByteString)----void SHA1(sha1_byte *data, unsigned int len, sha1_byte digest[SHA1_DIGEST_LENGTH]);-foreign import ccall safe "SHA1" c_sha1 :: CString -> Int -> CString -> IO ()--sha1 :: ByteString -> ByteString-sha1 fs = unsafePerformIO $- BS.unsafeUseAsCString fs $ \cstr ->- do ret <- mallocBytes 20- c_sha1 cstr (BS.length fs) ret- fp <- newForeignPtr finalizerFree (castPtr ret)- return (BS.fromForeignPtr fp 20)-
src/Data/Torrent/Scrape.hs view
@@ -1,6 +1,6 @@ ----------------------------------------------------------------------------- -- |--- Module : Data.Torrent.SHA1+-- Module : Data.Torrent.Scrape -- Copyright : (c) Lemmih 2005 -- License : BSD-like --@@ -8,7 +8,11 @@ -- Stability : experimental -- Portability : portable --+-- <http://en.wikipedia.org/wiki/Tracker_scrape> -----------------------------------------------------------------------------++{-# LANGUAGE PatternGuards #-}+ module Data.Torrent.Scrape ( ScrapeInfo(..) , parseScrapeInfo@@ -17,14 +21,14 @@ import Data.Char import Data.BEncode-import qualified Data.ByteString.Char8 as BS-import Data.ByteString (ByteString)+import qualified Data.ByteString.Lazy.Char8 as BS+import Data.ByteString.Lazy (ByteString) import System.FilePath import qualified Data.Map as Map data ScrapeInfo = ScrapeInfo- { scrapeSeeds :: Int- , scrapeLeechers :: Int+ { scrapeSeeds :: Integer+ , scrapeLeechers :: Integer } deriving (Read,Show) parseScrapeInfo :: ByteString -> Maybe ScrapeInfo
torrent.cabal view
@@ -1,20 +1,25 @@ Name: torrent-Version: 2007.10.27-Maintainer: Lemmih (lemmih@gmail.com)-Author: Lemmih (lemmih@gmail.com)+Version: 10000.0.0+Cabal-Version: >= 1.6+Maintainer: Joey Hess <id@joeyh.name>+Author: Lemmih <lemmih@gmail.com> Copyright: 2005-2007, Lemmih License-File: LICENSE License: BSD3-Build-Depends: base, binary, bencode >=0.2, filepath, network+Build-Type: Simple Category: Network-Synopsis: BitTorrent file parser.-Tested-with: GHC ==6.6-Extensions: ForeignFunctionInterface, PatternGuards-Exposed-Modules:- Data.Torrent- Data.Torrent.Scrape- Data.Torrent.SHA1-GHC-Options: -Wall -Werror-Hs-Source-Dirs: src-C-Sources: cbits/sha1.c-Extra-source-files: cbits/sha1.h+Extra-Source-Files: CHANGELOG+Synopsis: BitTorrent file parser and generater++Library+ GHC-Options: -Wall+ Hs-Source-Dirs: src+ Exposed-Modules:+ Data.Torrent+ Data.Torrent.Scrape+ Build-Depends: base >= 4.5, base < 5, binary, bencode >=0.2,+ filepath, containers, bytestring, syb++source-repository head+ type: git+ location: git://git.kitenet.net/haskell-torrent.git