-- This is a quick hack for uploading packages to Hackage. -- See http://hackage.haskell.org/trac/hackage/wiki/CabalUpload module Hackage.Upload (check, upload) where import Hackage.Types (Username(..), Password(..)) import Hackage.HttpUtils (proxy) import Distribution.Simple.Utils (debug, notice, warn) import Distribution.Verbosity (Verbosity) import Network.Browser (BrowserAction, browse, request, Authority(..), addAuthority, setOutHandler, setErrHandler, setProxy) import Network.HTTP (Header(..), HeaderName(..), Request(..), RequestMethod(..), Response(..)) import Network.URI (URI, parseURI) import Data.Char (intToDigit) import Numeric (showHex) import System.IO (hFlush, stdin, stdout, hGetEcho, hSetEcho ,openBinaryFile, IOMode(ReadMode), hGetContents) import Control.Exception (bracket) import System.Random (randomRIO) --FIXME: how do we find this path for an arbitrary hackage server? -- is it always at some fixed location relative to the server root? uploadURI :: URI Just uploadURI = parseURI "http://hackage.haskell.org/cgi-bin/hackage-scripts/protected/upload-pkg" checkURI :: URI Just checkURI = parseURI "http://hackage.haskell.org/cgi-bin/hackage-scripts/check-pkg" upload :: Verbosity -> Maybe Username -> Maybe Password -> [FilePath] -> IO () upload verbosity mUsername mPassword paths = do Username username <- maybe promptUsername return mUsername Password password <- maybe promptPassword return mPassword let auth = addAuthority AuthBasic { auRealm = "Hackage", auUsername = username, auPassword = password, auSite = uploadURI } flip mapM_ paths $ \path -> do notice verbosity $ "Uploading " ++ path ++ "... " handlePackage verbosity uploadURI auth path where promptUsername :: IO Username promptUsername = do putStr "Hackage username: " hFlush stdout fmap Username getLine promptPassword :: IO Password promptPassword = do putStr "Hackage password: " hFlush stdout -- save/restore the terminal echoing status bracket (hGetEcho stdin) (hSetEcho stdin) $ \_ -> do hSetEcho stdin False -- no echoing for entering the password fmap Password getLine check :: Verbosity -> [FilePath] -> IO () check verbosity paths = do flip mapM_ paths $ \path -> do notice verbosity $ "Checking " ++ path ++ "... " handlePackage verbosity checkURI (return ()) path handlePackage :: Verbosity -> URI -> BrowserAction () -> FilePath -> IO () handlePackage verbosity uri auth path = do req <- mkRequest uri path p <- proxy verbosity debug verbosity $ "\n" ++ show req (_,resp) <- browse $ do setProxy p setErrHandler (warn verbosity . ("http error: "++)) setOutHandler (debug verbosity) auth request req debug verbosity $ show resp case rspCode resp of (2,0,0) -> do notice verbosity "OK" (x,y,z) -> do notice verbosity $ "ERROR: " ++ path ++ ": " ++ map intToDigit [x,y,z] ++ " " ++ rspReason resp debug verbosity $ rspBody resp mkRequest :: URI -> FilePath -> IO Request mkRequest uri path = do pkg <- readBinaryFile path boundary <- genBoundary 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 } readBinaryFile :: FilePath -> IO String readBinaryFile path = openBinaryFile path ReadMode >>= hGetContents genBoundary :: IO String genBoundary = 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"