-- This is a quick hack for uploading packages to Hackage.
-- See http://hackage.haskell.org/trac/hackage/wiki/CabalUpload
import Network.Browser
import Network.HTTP
import Data.Char
import Data.Maybe
import Network.URI
import Numeric
import System.Environment
import System.Exit
import System.IO
import System.Random
type Username = String
type Password = String
main :: IO ()
main = do args <- getArgs
(user, pwd, paths) <- parseOptions args
mapM_ (uploadPackage user pwd) paths
uploadPackage :: Username -> Password -> FilePath -> IO ()
uploadPackage user pwd path =
do
let uri = uploadURI
auth = AuthBasic { auRealm = "Hackage",
auUsername = user,
auPassword = pwd,
auSite = uri }
putStr $ "Uploading " ++ path ++ "... "
hFlush stdout
req <- mkRequest uri path
(_,resp) <- browse (setErrHandler ignoreMsg
>> setOutHandler ignoreMsg
>> addAuthority auth
>> request req)
case rspCode resp of
(2,0,0) -> do putStrLn "OK"
(x,y,z) -> do putStrLn $ "ERROR: " ++ map intToDigit [x,y,z] ++ " " ++ rspReason resp
putStrLn $ rspBody resp
ignoreMsg :: String -> IO ()
ignoreMsg _ = return ()
uploadURI :: URI
uploadURI = fromJust $ parseURI "http://hackage.haskell.org/cgi-bin/hackage-scripts/protected/upload-pkg"
mkRequest :: URI -> FilePath -> IO Request
mkRequest uri path =
do pkg <- readFile path
boundary <- genBoundary pkg
let body = printMultiPart boundary (mkFormData path pkg)
return $ Request {
rqURI = uri,
rqMethod = POST,
rqHeaders = [Header HdrContentType ("multipart/form-data; boundary="++boundary),
Header HdrContentLength (show (length body)),
Header HdrAccept ("text/plain")],
rqBody = body
}
genBoundary :: String -> IO String
genBoundary pkg = do i <- randomRIO (0x10000000000000,0xFFFFFFFFFFFFFF) :: IO Integer
return $ showHex i ""
mkFormData :: FilePath -> String -> [BodyPart]
mkFormData path pkg =
-- yes, web browsers are that stupid (re quoting)
[BodyPart [Header hdrContentDisposition ("form-data; name=package; filename=\""++path++"\""),
Header HdrContentType "application/x-gzip"]
pkg]
hdrContentDisposition :: HeaderName
hdrContentDisposition = HdrCustom "Content-disposition"
-- * Multipart, partly stolen from the cgi package.
data BodyPart = BodyPart [Header] String
printMultiPart :: String -> [BodyPart] -> String
printMultiPart boundary xs =
concatMap (printBodyPart boundary) xs ++ crlf ++ "--" ++ boundary ++ "--" ++ crlf
printBodyPart :: String -> BodyPart -> String
printBodyPart boundary (BodyPart hs c) = crlf ++ "--" ++ boundary ++ crlf ++ concatMap show hs ++ crlf ++ c
crlf :: String
crlf = "\r\n"
-- * Command-line options
parseOptions :: [String] -> IO (Username, Password, [FilePath])
parseOptions (user:pwd:tarballs) = return (user,pwd,tarballs)
parseOptions _ = do hPutStrLn stderr "Usage: cabal-upload <username> <password> [<tarball> ...]"
exitFailure