packages feed

darcs-cabalized-2.0.2: src/Darcs/External.hs

{-# OPTIONS_GHC -cpp -fffi #-}

module Darcs.External (
    backupByRenaming, backupByCopying,
    copyFileOrUrl, speculateFileOrUrl, copyFilesOrUrls, copyLocal, cloneFile,
    cloneTree, cloneTreeExcept, clonePartialsTree, clonePaths,
    fetchFilePS, gzFetchFilePS,
    sendEmail, generateEmail, sendEmailDoc, resendEmail,
    signString, verifyPS,
    execDocPipe, execPipeIgnoreError,
    getTermNColors,
    pipeDoc, pipeDocSSH, execSSH,
    maybeURLCmd,
    Cachable(Cachable, Uncachable, MaxAge),
    viewDoc, viewDocWith,
  ) where

import Data.List ( intersperse )
import Control.Monad ( liftM, when, zipWithM_ )
import System.Exit ( ExitCode(..) )
import System.Environment ( getEnv )
import System.Cmd ( system )
import System.IO ( hPutStr, hPutStrLn, hGetContents, hClose,
                   openBinaryFile, IOMode( ReadMode ),
                   openBinaryTempFile,
                   hIsTerminalDevice, stdout, stderr, Handle )
import System.IO.Error ( isDoesNotExistError )
import System.IO.Unsafe ( unsafePerformIO )
import System.Posix.Files ( getSymbolicLinkStatus, isRegularFile, isDirectory )
import System.Directory ( createDirectory, getDirectoryContents,
                          doesFileExist, doesDirectoryExist,
                          renameFile, renameDirectory, copyFile )
import System.Process ( runProcess, runInteractiveProcess, waitForProcess )
import Control.Concurrent ( forkIO, newEmptyMVar, putMVar, takeMVar )
import Control.Exception ( bracket, try, finally )
import Data.Char ( toUpper )
import Foreign.C ( CString, withCString, CInt )
import Foreign.Ptr ( nullPtr )
#ifdef HAVE_CURSES
import Foreign.C ( CChar )
import Foreign.Ptr ( Ptr )
import Foreign.Marshal.Alloc (allocaBytes)
import Autoconf ( use_color )
#endif
import Workaround ( createLink, createDirectoryIfMissing )

import Darcs.Flags ( DarcsFlag( SignAs, Sign, SignSSL, NoLinks,
                                Verify, VerifySSL ) )
import Darcs.Utils ( withCurrentDirectory, breakCommand, get_viewer, ortryrunning, )
import Darcs.Progress ( withoutProgress, progressList, debugMessage )
import FastPackedString ( PackedString, readFilePS, gzReadFilePS, writeFilePS,
                          hPutPS, unpackPS, linesPS, unlinesPS,
                          lengthPS, takePS, dropPS, packString,
                          nullPS, nilPS, concatPS, hGetContentsPS
                        )
import Darcs.Lock ( withTemp, withOpenTemp, tempdir_loc, canonFilename, writeDocBinFile,
                    removeFileMayNotExist, )
import CommandLine ( parseCmd, addUrlencoded )
import Autoconf ( have_libcurl, have_libwww, have_HTTP, have_sendmail, have_mapi, sendmail_path, darcs_version )
import qualified URL ( copyUrl, copyUrlFirst, waitUrl )
import qualified HTTP ( copyUrl, exists )
import Ssh ( getSSH, copySSH, copySSHs, SSHCmd(..) )
import URL ( Cachable(..) )
import Exec ( exec, Redirect(..), withoutNonBlock )
import FileName ( fn2fp, fp2fn, norm_path )
import Darcs.URL ( is_file, is_url, is_ssh )
import Darcs.Utils ( catchall )
import Printer ( Doc, Printers, putDocLnWith, hPutDoc, hPutDocLn, hPutDocWith, ($$), (<+>), renderPS,
                 simplePrinters,
                 text, empty, packedString, vcat, renderString )
#include "impossible.h"

backupByRenaming :: FilePath -> IO ()
backupByRenaming = backupBy rename
 where rename x y = do
         isD <- doesDirectoryExist x
         if isD then renameDirectory x y else renameFile x y

backupByCopying :: FilePath -> IO ()
backupByCopying = backupBy copy
 where
  copy x y = do
    isD <- doesDirectoryExist x
    if isD then do createDirectory y
                   cloneTree (do_norm x) (do_norm y)
           else copyFile x y
  do_norm f = fn2fp $ norm_path $ fp2fn f

backupBy :: (FilePath -> FilePath -> IO ()) -> FilePath -> IO ()
backupBy backup f =
           do hasBF <- doesFileExist f
              hasBD <- doesDirectoryExist f
              when (hasBF || hasBD) $ helper (0::Int)
  where
  helper i = do existsF <- doesFileExist next
                existsD <- doesDirectoryExist next
                if (existsF || existsD)
                   then helper (i + 1)
                   else do putStrLn $ "Backing up " ++ f ++ "(" ++ suffix ++ ")"
                           backup f next
             where next = f ++ suffix
                   suffix = "-darcs-backup" ++ show i

fetchFilePS :: String -> Cachable -> IO PackedString
fetchFilePS fou _ | is_file fou = readFilePS fou
fetchFilePS fou cache = withTemp $ \t -> do copyFileOrUrl [] fou t cache
                                            readFilePS t

gzFetchFilePS :: String -> Cachable -> IO PackedString
gzFetchFilePS fou _ | is_file fou = gzReadFilePS fou
gzFetchFilePS fou cache = withTemp $ \t-> do copyFileOrUrl [] fou t cache
                                             gzReadFilePS t


copyFileOrUrl :: [DarcsFlag] -> FilePath -> FilePath -> Cachable -> IO ()
copyFileOrUrl opts fou out _     | is_file fou = copyLocal opts fou out
copyFileOrUrl _    fou out cache | is_url  fou = copyRemote fou out cache
copyFileOrUrl _    fou out _     | is_ssh  fou = copySSH fou out
copyFileOrUrl _    fou _   _     = fail $ "unknown transport protocol: " ++ fou

speculateFileOrUrl :: String -> FilePath -> IO ()
speculateFileOrUrl fou out | is_url fou = speculateRemote fou out
                           | otherwise = return ()

copyLocal  :: [DarcsFlag] -> String -> FilePath -> IO ()
copyLocal opts fou out | NoLinks `elem` opts = cloneFile fou out
                       | otherwise = createLink fou out `catchall` cloneFile fou out

clonePaths :: FilePath -> FilePath -> [FilePath] -> IO ()
clonePaths source dest = mapM_ (clonePath source dest)

clonePath :: FilePath -> FilePath -> FilePath -> IO ()
clonePath source dest path
 = do let source' = source ++ "/" ++ path
          dest' = dest ++ "/" ++ path
      fs <- getSymbolicLinkStatus source'
      if isDirectory fs then do
          createDirectoryIfMissing True dest'
       else if isRegularFile fs then do
          createDirectoryIfMissing True (dest ++ "/" ++ basename path)
          cloneFile source' dest'
       else fail ("clonePath: Bad file " ++ source')
   `catch` fail ("clonePath: Bad file " ++ source ++ "/" ++ path)
 where basename = reverse . dropWhile ('/' /=) . dropWhile ('/' ==) . reverse

clonePartialsTree :: FilePath -> FilePath -> [FilePath] -> IO ()
clonePartialsTree source dest = mapM_ (clonePartialTree source dest)

clonePartialTree :: FilePath -> FilePath -> FilePath -> IO ()
clonePartialTree source dest "" = cloneTree source dest
clonePartialTree source dest pref
 = do createDirectoryIfMissing True (dest ++ "/" ++ basename pref)
      cloneSubTree (source ++ "/" ++ pref) (dest ++ "/" ++ pref)
 where basename = reverse . dropWhile ('/' /=) . dropWhile ('/' ==) . reverse

cloneTree :: FilePath -> FilePath -> IO ()
cloneTree = cloneTreeExcept []

cloneTreeExcept :: [FilePath] -> FilePath -> FilePath -> IO ()
cloneTreeExcept except source dest =
 do fs <- getSymbolicLinkStatus source
    if isDirectory fs then do
        fps <- getDirectoryContents source
        let fps' = filter (`notElem` (".":"..":except)) fps
            mk_source fp = source ++ "/" ++ fp
            mk_dest   fp = dest   ++ "/" ++ fp
        zipWithM_ cloneSubTree (map mk_source fps') (map mk_dest fps')
     else fail ("cloneTreeExcept: Bad source " ++ source)
   `catch` fail ("cloneTreeExcept: Bad source " ++ source)

cloneSubTree :: FilePath -> FilePath -> IO ()
cloneSubTree source dest =
 do fs <- getSymbolicLinkStatus source
    if isDirectory fs then do
        createDirectory dest
        fps <- getDirectoryContents source
        let fps' = filter (`notElem` [".", ".."]) fps
            mk_source fp = source ++ "/" ++ fp
            mk_dest   fp = dest   ++ "/" ++ fp
        zipWithM_ cloneSubTree (map mk_source fps') (map mk_dest fps')
     else if isRegularFile fs then do
        cloneFile source dest
     else fail ("cloneSubTree: Bad source "++ source)
    `catch` (\e -> if isDoesNotExistError e
                   then return ()
                   else ioError e)

cloneFile :: FilePath -> FilePath -> IO ()
cloneFile = copyFile

maybeURLCmd :: String -> String -> IO(Maybe(String))
maybeURLCmd what url =
  do let prot = map toUpper $ takeWhile (/= ':') url
     liftM Just (getEnv ("DARCS_" ++ what ++ "_" ++ prot))
             `catch` \_ -> return Nothing

speculateRemote :: String -> FilePath -> IO () -- speculations are always Cachable
speculateRemote u v =
    do maybeget <- maybeURLCmd "GET" u
       case maybeget of
         Just _ -> return () -- can't pipeline these
         Nothing -> if have_libwww || have_libcurl
                    then URL.copyUrl u v Cachable
                    else return ()

copyRemote :: String -> FilePath -> Cachable -> IO ()
copyRemote u v cache =
    do maybeget <- maybeURLCmd "GET" u
       case maybeget of
         Nothing -> copyRemoteNormal u v cache
         Just get ->
           do let (cmd,args) = breakCommand get
              r <- exec cmd (args++[u]) (Null, File v, AsIs)
              when (r /= ExitSuccess) $
                  fail $ "(" ++ get ++ ") failed to fetch: " ++ u

copyRemoteNormal :: String -> FilePath -> Cachable -> IO ()
copyRemoteNormal u v cache = if have_libwww || have_libcurl
                             then URL.copyUrlFirst u v cache >> URL.waitUrl u
                             else if HTTP.exists
                                  then HTTP.copyUrl u v cache
                                  else copyRemoteCmd u v

copyFilesOrUrls :: [DarcsFlag]->FilePath->[String]->FilePath->Cachable->IO ()
copyFilesOrUrls opts dou ns out _ | is_file dou = copyLocals opts dou ns out
copyFilesOrUrls _ dou ns out c    | is_url  dou = copyRemotes dou ns out c
copyFilesOrUrls _ dou ns out _    | is_ssh  dou = copySSHs dou ns out
copyFilesOrUrls _ dou _  _   _    = fail $ "unknown transport protocol: "++dou


copyLocals :: [DarcsFlag] -> String -> [String] -> FilePath -> IO ()
copyLocals opts u ns d =
    doWithPatches (\n -> copyLocal opts (u++"/"++n) (d++"/"++n)) ns

copyRemotes :: String -> [String] -> FilePath -> Cachable -> IO()
copyRemotes u ns d cache =
    do maybeget <- maybeURLCmd "GET" u
       maybemget <- maybeURLCmd "MGET" u
       case (maybeget, maybemget) of
         (Nothing, _) -> copyRemotesNormal u ns d cache
         (Just _, Nothing) -> doWithPatches (\n -> copyRemote (u++"/"++n) (d++"/"++n) cache) ns
         (Just _, Just mget) -> mgetRemotes mget u ns d

stringToInt :: String -> Int -> Int
stringToInt num def = case reads num of [(x,"")] -> x
                                        _ -> def

mgetRemotes :: String -> String -> [String] -> FilePath -> IO()
mgetRemotes _ _ [] _ = return ()
mgetRemotes mget u ns d = do
    mgetmax <- getEnv "DARCS_MGETMAX" `catch` \_ -> return ""
    let (nsnow, nslater) = splitAt (stringToInt mgetmax 200) ns
        (cmd, args) = breakCommand mget
        urls = map (\n -> u++"/"++n) nsnow
    withCurrentDirectory d $ do
        r <- exec cmd (args++urls) (Null,Null,AsIs)
        when (r /= ExitSuccess) $
            fail $ unlines $
                ["(" ++ mget ++ ") failed to fetch files.",
                     "source directory: " ++ d,
                     "source files:"] ++ (upto 5 nsnow) ++
                     ["still to go:"] ++ (upto 5 nslater)
    mgetRemotes mget u nslater d
    where
    upto :: Integer -> [String] -> [String]
    upto _ [] = []
    upto 0 l = [ "(" ++ (show (length l)) ++ " more)" ]
    upto n (h : t) = h : (upto (n - 1) t)

copyRemotesNormal :: String -> [String] -> FilePath -> Cachable -> IO()
copyRemotesNormal u ns d cache =
    if have_libwww || have_libcurl
    then do mapM_ (\n -> URL.copyUrl (u++"/"++n) (d++"/"++n) cache) ns
            doWithPatches (\n -> URL.waitUrl (u++"/"++n)) ns
    else if have_HTTP
         then doWithPatches (\n -> copyRemote (u++"/"++n) (d++"/"++n) cache) ns
         else wgetRemotes u ns d

-- Argh, this means darcs get will fail if we don't have libcurl and don't
-- have wget.  :(
wgetRemotes :: String -> [String] -> FilePath -> IO ()
wgetRemotes u ns d = do wget_command <- getEnv "DARCS_WGET" `catch`
                                               \_ -> return "wget"
                        let (wget, wget_args) = breakCommand wget_command
                            input = unlines $ map (\n -> u++"/"++n) ns
                        withCurrentDirectory d $ withOpenTemp $ \(th,tn) ->
                            do hPutStr th input
                               hClose th
                               r <- exec wget (wget_args++["-i",tn])
                                         (Null,Null,AsIs)
                               when (r /= ExitSuccess) $
                                    fail $ unlines $
                                             ["(wget) failed to fetch files.",
                                              "source directory: " ++ d,
                                              "source files:"] ++ ns

copyRemoteCmd :: String -> FilePath -> IO ()
copyRemoteCmd s tmp = do
    let cmd = get_ext_cmd
    r <- stupidexec (cmd tmp s) (Null,Null,AsIs)
    when (r /= ExitSuccess) $
         fail $ "failed to fetch: " ++ s ++" " ++ show r
    where stupidexec [] = bug "stupidexec without a command"
          stupidexec xs = exec (head xs) (tail xs)

doWithPatches :: (String -> IO ()) -> [String] -> IO ()
doWithPatches f patches = mapM_ (\p -> seq p $ f p) $ progressList "Copying patch" patches

{-# NOINLINE get_ext_cmd #-}
get_ext_cmd :: String -> String -> [String]
-- Only need to find the command once..
get_ext_cmd = unsafePerformIO get_ext_cmd'

-- Would be better to read possible command lines from config-file..
get_ext_cmd' :: IO (String -> String -> [String])
get_ext_cmd' = try_cmd cmds
    where cmds = [("wget", (("--version",0),
                          -- use libcurl for proper cache control
                          \t s -> ["wget", "-q",
                                   "--header=Pragma: no-cache",
                                   "--header=Cache-Control: no-cache",
                                   "-O",t,s])),
                  ("curl", (("--version",2),
                            \t s -> ["curl", "-s", "-f", "-L",
                                     "-H", "Pragma: no-cache",
                                     "-H", "Cache-Control: no-cache",
                                     "-o",t,s]))]
          try_cmd [] = fail $ "I need one of: " ++ cs
              where cs = concat $ intersperse ", " (map fst cmds)
          try_cmd ((c,(ok_check,f)):cs) = do
            True <- can_execute ok_check c
            return f
           `catch` (\_ -> try_cmd cs)

-- | Run a command on a remote location without passing it any input or
--   reading its output.  Return its ExitCode
execSSH :: String -> String -> IO ExitCode
execSSH remoteAddr command =
    do (ssh, ssh_args) <- getSSH SSH remoteAddr
       debugMessage $ unwords (ssh:ssh_args++[remoteAddr,command])
       withoutProgress $ do hid <- runProcess ssh (ssh_args++[remoteAddr,command])
                                   Nothing Nothing Nothing Nothing Nothing
                            waitForProcess hid

pipeDoc :: String -> [String] -> Doc -> IO ExitCode
pipeDoc c args inp = withoutNonBlock $ withoutProgress $
    do debugMessage $ unwords (c:args)
       (i,o,e,pid) <- runInteractiveProcess c args Nothing Nothing
       mvare <- newEmptyMVar
       forkIO ((hGetContents e >>= -- ratify hGetContents: it's immediately consumed
                hPutStr stderr)
               `finally` putMVar mvare ())
       mvaro <- newEmptyMVar
       forkIO ((hGetContents o >>= -- ratify hGetContents: it's immediately consumed
                hPutStr stdout)
               `finally` putMVar mvaro ())
       hPutDoc i inp
       hClose i
       rval <- waitForProcess pid
       takeMVar mvare
       takeMVar mvaro
       when (rval == ExitFailure 127) $
            putStrLn $ "Command not found:\n   "++ show (c:args)
       return rval

pipeDocSSH :: String -> [String] -> Doc -> IO ExitCode
pipeDocSSH remoteAddr args input =
    do (ssh, ssh_args) <- getSSH SSH remoteAddr
       pipeDoc ssh (ssh_args++ (remoteAddr:args)) input

sendEmail :: String -> String -> String -> String -> String -> String -> IO ()
sendEmail f t s cc scmd body =
  sendEmailDoc f t s cc scmd Nothing (text body)


generateEmail
    :: Handle  -- ^ handle to write email to
    -> String  -- ^ From
    -> String  -- ^ To
    -> String  -- ^ Subject
    -> String  -- ^ CC
    -> Doc     -- ^ body
    -> IO ()
generateEmail h f t s cc body = do
     hPutDocLn h $
           text "To:"      <+> text t
        $$ text "From:"    <+> text f
        $$ text "Subject:" <+> text s
        $$ formated_cc
        $$ text "X-Mail-Originator: Darcs Version Control System"
        $$ text ("X-Darcs-Version: " ++ darcs_version)
        $$ body
  where formated_cc = if cc == ""
                      then empty
                      else text "Cc:" <+> text cc

-- | Send an email, optionally containing a patch bundle
--   (more precisely, its description and the bundle itself)
sendEmailDoc :: String -> String -> String -> String -> String
             -> Maybe (Doc, Doc) -> Doc -> IO ()
sendEmailDoc _ "" _ "" _ _ _ = return ()
sendEmailDoc f "" s cc scmd mbundle body =
  sendEmailDoc f cc s "" scmd mbundle body
sendEmailDoc f t s cc scmd mbundle body =
  if have_sendmail || scmd /= "" then do
    withOpenTemp $ \(h,fn) -> do
     generateEmail h f t s cc body
     hClose h
     ftable' <- case mbundle of
                Just (content,bundle) ->
                  withOpenTemp $ \(hat,at) -> do
                    hPutDocLn hat $ bundle
                    hClose hat
                    return [ ('b', renderString content) , ('a', at) ]
                Nothing ->
                    return [ ('b', renderString body) ]
     let ftable = [ ('t',addressOnly t),('c',cc),('f',f),('s',s) ] ++ ftable'
     r <- execSendmail ftable scmd fn
     when (r /= ExitSuccess) $ fail ("failed to send mail to: " ++ t
                                     ++ "\nPerhaps sendmail is not configured.")
  else if have_mapi then do
     r <- withCString t $ \tp ->
           withCString f $ \fp ->
            withCString cc $ \ccp ->
             withCString s $ \sp ->
              withOpenTemp $ \(h,fn) -> do
               hPutDoc h body
               hClose h
               writeDocBinFile "mailed_patch" body
               cfn <- canonFilename fn
               withCString cfn $ \pcfn ->
                c_send_email fp tp ccp sp nullPtr pcfn
     when (r /= 0) $ fail ("failed to send mail to: " ++ t)
  else fail $ "no mail facility (sendmail or mapi) located at configure time!"
  where addressOnly a =
          case dropWhile (/= '<') a of
          ('<':a2) -> takeWhile (/= '>') a2
          _        -> a

resendEmail :: String -> String -> PackedString -> IO ()
resendEmail "" _ _ = return ()
resendEmail t scmd body =
  case (have_sendmail || scmd /= "", have_mapi) of
   (True, _) -> do
    withOpenTemp $ \(h,fn) -> do
     hPutStrLn h $ "To: "++ t
     hPutStrLn h $ find_from (linesPS body)
     hPutStrLn h $ find_subject (linesPS body)
     hPutDocLn h $ fixit $ linesPS body
     hClose h
     let ftable = [('t',t)]
     r <-  execSendmail ftable scmd fn
     when (r /= ExitSuccess) $ fail ("failed to send mail to: " ++ t)
   (_, True) -> fail "Don't know how to resend email with MAPI"
   _ -> fail $ "no mail facility (sendmail or mapi) located at configure time (use the sendmail-command option)!"
  where br = packString "\r"
        darcsurl = packString "DarcsURL:"
        content = packString "Content-"
        from_start = packString "From:"
        subject_start = packString "Subject:"
        fixit (l:ls)
         | nullPS l = packedString nilPS $$ vcat (map packedString ls)
         | l == br = packedString nilPS $$ vcat (map packedString ls)
         | takePS 9 l == darcsurl || takePS 8 l == content
            = packedString l $$ fixit ls
         | otherwise = fixit ls
        fixit [] = empty
        find_from (l:ls) | takePS 5 l == from_start = unpackPS l
                         | otherwise = find_from ls
        find_from [] = "From: unknown"
        find_subject (l:ls) | takePS 8 l == subject_start = unpackPS l
                            | otherwise = find_subject ls
        find_subject [] = "Subject: (no subject)"

execSendmail :: [(Char,String)] -> String -> String -> IO ExitCode
execSendmail ftable scmd fn =
  if scmd == "" then
     exec sendmail_path ["-i", "-t"] (File fn, Null, AsIs)
  else case parseCmd (addUrlencoded ftable) scmd of
         Right (arg0:opts, wantstdin) ->
           do let stdin = if wantstdin then File fn else Null
              exec arg0 opts (stdin, Null, AsIs)
         Left e -> fail $ ("failed to send mail, invalid sendmail-command: "++(show e))
         _ -> fail $ ("failed to send mail, invalid sendmail-command")

#ifdef HAVE_MAPI
foreign import ccall "win32/send_email.h send_email" c_send_email
#else
c_send_email
#endif
             :: CString -> {- sender -}
                CString -> {- recipient -}
                CString -> {- cc -}
                CString -> {- subject -}
                CString -> {- body -}
                CString -> {- path -}
                IO Int
#ifndef HAVE_MAPI
c_send_email = impossible
#endif

execPSPipe :: String -> [String] -> PackedString -> IO PackedString
execPSPipe c args ps = liftM renderPS
                     $ execDocPipe c args
                     $ packedString ps

execDocPipe :: String -> [String] -> Doc -> IO Doc
execDocPipe c args instr = withoutProgress $
    do (i,o,e,pid) <- runInteractiveProcess c args Nothing Nothing
       forkIO $ hPutDoc i instr >> hClose i
       mvare <- newEmptyMVar
       forkIO ((hGetContents e >>= -- ratify hGetContents: it's immediately consumed
                hPutStr stderr)
               `finally` putMVar mvare ())
       out <- hGetContentsPS o
       rval <- waitForProcess pid
       takeMVar mvare
       case rval of
         ExitFailure ec ->fail $ "External program '"++c++
                          "' failed with exit code "++ show ec
         ExitSuccess -> return $ packedString out

-- The following is needed for diff, which returns non-zero whenever
-- the files differ.
execPipeIgnoreError :: String -> [String] -> Doc -> IO Doc
execPipeIgnoreError c args instr = withoutProgress $
    do (i,o,e,pid) <- runInteractiveProcess c args Nothing Nothing
       forkIO $ hPutDoc i instr >> hClose i
       mvare <- newEmptyMVar
       forkIO ((hGetContents e >>= -- ratify hGetContents: it's immediately consumed
                hPutStr stderr)
               `finally` putMVar mvare ())
       out <- hGetContentsPS o
       waitForProcess pid
       takeMVar mvare
       return $ packedString out

signString :: [DarcsFlag] -> Doc -> IO Doc
signString [] d = return d
signString (Sign:_) d = signPGP [] d
signString (SignAs keyid:_) d = signPGP ["--local-user", keyid] d
signString (SignSSL idf:_) d = signSSL idf d
signString (_:os) d = signString os d

signPGP :: [String] -> Doc -> IO Doc
signPGP args t = execDocPipe "gpg" ("--clearsign":args) t

signSSL :: String -> Doc -> IO Doc
signSSL idfile t =
    withTemp $ \cert -> do
    opensslPS ["req", "-new", "-key", idfile,
               "-outform", "PEM", "-days", "365"]
                (packString "\n\n\n\n\n\n\n\n\n\n\n")
                >>= opensslPS ["x509", "-req", "-extensions",
                               "v3_ca", "-signkey", idfile,
                               "-outform", "PEM", "-days", "365"]
                >>= opensslPS ["x509", "-outform", "PEM"]
                >>= writeFilePS cert
    opensslDoc ["smime", "-sign", "-signer", cert,
                "-inkey", idfile, "-noattr", "-text"] t
    where opensslDoc = execDocPipe "openssl"
          opensslPS = execPSPipe "openssl"


verifyPS :: [DarcsFlag] -> PackedString -> IO (Maybe PackedString)
verifyPS [] ps = return $ Just ps
verifyPS (Verify pks:_) ps = verifyGPG pks ps
verifyPS (VerifySSL auks:_) ps = verifySSL auks ps
verifyPS (_:os) ps = verifyPS os ps

verifyGPG :: FilePath -> PackedString -> IO (Maybe PackedString)
verifyGPG goodkeys s =
    withOpenTemp $ \(th,tn) -> do
      hPutPS th s
      hClose th
      rval <- exec "gpg"  ["--batch","--no-default-keyring",
                           "--keyring",fix_path goodkeys, "--verify"]
                           (File tn, Null, Null)
      case rval of
          ExitSuccess -> return $ Just gpg_fixed_s
          _ -> return Nothing
      where gpg_fixed_s = let
                not_begin_signature x =
                    x /= packString "-----BEGIN PGP SIGNED MESSAGE-----"
                    &&
                    x /= packString "-----BEGIN PGP SIGNED MESSAGE-----\r"
                in unlinesPS $ map fix_line $ tail $ dropWhile not_begin_signature $ linesPS s
            fix_line x | lengthPS x < 3 = x
                       | takePS 3 x == packString "- -" = dropPS 2 x
                       | otherwise = x
#if defined(WIN32)
            fix_sep c | c=='/' = '\\'   | otherwise = c
            fix_path p = map fix_sep p
#else
            fix_path p = p
#endif

verifySSL :: FilePath -> PackedString -> IO (Maybe PackedString)
verifySSL goodkeys s = do
    certdata <- opensslPS ["smime", "-pk7out"] s
                >>= opensslPS ["pkcs7", "-print_certs"]
    cruddy_pk <- opensslPS ["x509", "-pubkey"] certdata
    let key_used = concatPS $ tail $
                   takeWhile (/= packString"-----END PUBLIC KEY-----")
                           $ linesPS cruddy_pk
        in do allowed_keys <- linesPS `liftM` readFilePS goodkeys
              if not $ key_used `elem` allowed_keys
                then return Nothing -- Not an allowed key!
                else withTemp $ \cert ->
                     withTemp $ \on ->
                     withOpenTemp $ \(th,tn) -> do
                     hPutPS th s
                     hClose th
                     writeFilePS cert certdata
                     rval <- exec "openssl" ["smime", "-verify", "-CAfile",
                                             cert, "-certfile", cert]
                                             (File tn, File on, Null)
                     case rval of
                       ExitSuccess -> Just `liftM` readFilePS on
                       _ -> return Nothing
    where opensslPS = execPSPipe "openssl"

can_execute :: (String,Int) -> String -> IO Bool
can_execute (arg,expected_return_value) exe = do
 withTemp $ \junk -> do
  ec <- system (unwords [exe,arg,">",junk])
  case ec of
    ExitSuccess | expected_return_value == 0 -> return True
    ExitFailure r | r == expected_return_value -> return True
    _ -> return False


{-
  - This function returns number of colors supported by current terminal
  - or -1 if color output not supported or error occured.
  - Terminal type determined by TERM env. variable.
  -}
getTermNColors :: IO CInt

#ifdef HAVE_CURSES

foreign import ccall "tgetnum" c_tgetnum :: CString -> IO CInt
foreign import ccall "tgetent" c_tgetent :: Ptr CChar -> CString -> IO CInt

termioBufSize :: Int
termioBufSize = 4096

getTermNColors = if not use_color
                 then return (-1)
                 else do term <- getEnv "TERM"
                         allocaBytes termioBufSize (getTermNColorsImpl term)
                      `catch` \_ -> return (-1)

getTermNColorsImpl :: String -> Ptr CChar -> IO CInt
getTermNColorsImpl term buf = do rc <- withCString term $
                                       \termp -> c_tgetent buf termp
                                 if (rc /= 1) then return (-1)  else withCString "Co" $ \capap -> c_tgetnum capap

#else

getTermNColors = return (-1)

#endif

viewDoc :: Doc -> IO ()
viewDoc = viewDocWith simplePrinters

viewDocWith :: Printers -> Doc -> IO ()
viewDocWith pr msg = do
  isTerminal <- hIsTerminalDevice stdout
  if isTerminal && lengthGreaterThan (20 :: Int) (lines $ renderString msg)
     then do viewer <- get_viewer
             pipeDocToPager viewer [] pr msg
               `ortryrunning` pipeDocToPager  "less" [] pr msg
               `ortryrunning` pipeDocToPager  "more" [] pr msg
               `ortryrunning` pipeDocToPager "" [] pr msg
     else pipeDocToPager "" [] pr msg
  return ()
              where lengthGreaterThan n _ | n <= 0 = True
                    lengthGreaterThan _ [] = False
                    lengthGreaterThan n (_:xs) = lengthGreaterThan (n-1) xs

pipeDocToPager :: String -> [String] -> Printers -> Doc -> IO ExitCode

pipeDocToPager "" _ pr inp = do
  putDocLnWith pr inp
  return ExitSuccess

pipeDocToPager c args pr inp = withoutNonBlock $ withoutProgress $ do
  tmp <- tempdir_loc
  bracket (openBinaryTempFile tmp "darcs-pager") cleanup $ \(fn,fh) ->
    do hPutDocWith pr fh inp
       hClose fh
       h <- openBinaryFile fn ReadMode
       x <- do pid <- runProcess c args Nothing Nothing (Just h) Nothing Nothing
               waitForProcess pid
       when (x == ExitFailure 127) $
            putStrLn $ "Command not found:\n   "++ show (c:args)
       return x
  where
    cleanup (f,h) = do try $ hClose h
                       removeFileMayNotExist f