packages feed

network-simple-tls-0.1.0.0: examples/https-client.hs

{-# LANGUAGE OverloadedStrings #-}

module Main (main) where

import           Control.Applicative        ((<$>), (<*>), pure)
import qualified Data.ByteString.Char8      as B
import qualified Data.ByteString.Lazy.Char8 as BL ()
import           Data.Certificate.X509      (X509)
import           Data.CertificateStore      (CertificateStore
                                            ,makeCertificateStore)
import           Data.Maybe                 (maybeToList)
import           Data.Monoid                ((<>))
import qualified Network.Simple.TCP.TLS     as Z
import qualified Network.Socket             as NS
import qualified Network.TLS                as T
import           Network.TLS.Extra          as TE
import           System.Certificate.X509    (getSystemCertificateStore)
import           System.Console.GetOpt
import           System.Environment         (getProgName, getArgs)

client :: CertificateStore -> [Z.Credential] -> NS.HostName
       -> NS.ServiceName -> IO ()
client cStore creds host port = do
    Z.connect csettings host port $ \(ctx,_) -> do
       T.sendData ctx "GET / HTTP/1.0\r\n\r\n"
       consume ctx B.putStr >> putStrLn ""
  where
    csettings = Z.makeClientSettings creds (Just host) cStore

-- | Repeatedly receive data from the given 'T.Context' until exhausted,
-- performing the given action on each received chunk.
consume :: T.Context -> (B.ByteString -> IO ()) -> IO ()
consume ctx k = do
    mbs <- Z.recv ctx
    case mbs of
      Nothing -> return ()
      Just bs -> k bs >> consume ctx k


main :: IO ()
main = do
    args <- getArgs
    case getOpt RequireOrder options args of
      (actions, [hostname,port], _) -> do
        opts <- foldl (>>=) (return defaultOptions) actions
        cStore <- case optCACert opts of
          Nothing -> getSystemCertificateStore
          Just ca -> return $ makeCertificateStore [ca]
        let cred = Z.Credential <$> optClientCert opts
                                <*> optClientKey opts
                                <*> pure []
        client cStore (maybeToList cred) hostname port
      (_,_,msgs) -> do
        pn <- getProgName
        let header = "Usage: " <> pn <> " [OPTIONS] HOSTNAME PORT"
        error $ concat msgs ++ usageInfo header options

--------------------------------------------------------------------------------
-- The boring stuff below is related to command line parsing

data Options = Options
  { optClientCert :: Maybe X509
  , optClientKey  :: Maybe T.PrivateKey
  , optCACert     :: Maybe X509
  } deriving (Show)

defaultOptions :: Options
defaultOptions = Options
  { optClientCert = Nothing
  , optClientKey  = Nothing
  , optCACert     = Nothing
  }

options :: [OptDescr (Options -> IO Options)]
options =
  [ Option [] ["cert"]   (OptArg readClientCert "FILE") "Client certificate"
  , Option [] ["key"]    (OptArg readClientKey  "FILE") "Client private key"
  , Option [] ["cacert"] (OptArg readCACert     "FILE") "CA certificate"
  ]


readClientCert :: Maybe FilePath -> Options -> IO Options
readClientCert Nothing    opt = return opt
readClientCert (Just arg) opt = do
    cert <- TE.fileReadCertificate arg
    return $ opt { optClientCert = Just cert }

readClientKey :: Maybe FilePath -> Options -> IO Options
readClientKey Nothing    opt = return opt
readClientKey (Just arg) opt = do
    key <- TE.fileReadPrivateKey arg
    return $ opt { optClientKey = Just key }

readCACert :: Maybe FilePath -> Options -> IO Options
readCACert Nothing    opt = return opt
readCACert (Just arg) opt = do
    cert <- TE.fileReadCertificate arg
    return $ opt { optCACert = Just cert }