packages feed

moesocks-0.1.0.0: src/Network/MoeSocks/Helper.hs

{-# LANGUAGE ExistentialQuantification #-}

module Network.MoeSocks.Helper where

import Control.Concurrent
import Control.Exception
import Control.Lens
import Control.Monad.IO.Class
import Data.Binary
import Data.Binary.Put
import Data.ByteString (ByteString)
import Data.Monoid
import Data.Text (Text)
import Data.Text.Lens
import Network.Socket
import Prelude hiding (take, (-)) 
import System.IO
import System.IO.Streams (InputStream)
import System.IO.Streams.Attoparsec
import System.IO.Streams.List
import System.IO.Unsafe
import qualified Data.ByteString as S
import qualified Data.ByteString.Builder as B
import qualified Data.ByteString.Builder.Extra as BE
import qualified Data.ByteString.Lazy as LB
import qualified Prelude as P
import qualified System.IO.Streams as Stream

-- BEGIN backports

infixr 0 -
(-) :: (a -> b) -> a -> b
(-) = ($)

-- END backports

_Debug :: Bool
_Debug = True

syncLock :: MVar ()
syncLock = unsafePerformIO - newEmptyMVar

sync :: IO a -> IO a
sync io = do
  putMVar syncLock ()
  io <* takeMVar syncLock

puts :: String -> IO ()
puts 
  | _Debug = sync . putStrLn
  | otherwise = const - pure ()

pute :: String -> IO ()
pute = sync . hPutStrLn stderr

showBytes :: ByteString -> String
showBytes = show . S.unpack

fromWord8 :: forall t. Binary t => [Word8] -> t
fromWord8 = decode . runPut . mapM_ put
      
safeSocketHandler :: String -> (Socket -> IO a) -> Socket -> IO a
safeSocketHandler aID f aSocket =
  catch (f aSocket) - \e -> do
      pute - "Exception in " <> aID <> ": " <> show (e :: SomeException)
      sClose aSocket
      throw e

catchAll :: IO a -> IO ()
catchAll io = catch (() <$ io) - \e -> 
                pute - "CatcheAll: " <> show (e :: SomeException)

catchIO:: IO a -> IO ()
catchIO io = catch (() <$ io) - \e ->
                pute - "Catch IO: " <> show (e :: IOException)
                

waitBoth :: IO a -> IO b -> IO ()
waitBoth x y = do
  (xThreadID, xLock) <- do
    _lock <- newEmptyMVar
    _threadID <- 
      forkFinally x - const - putMVar _lock ()

    return (_threadID, _lock)

  yThreadID <- 
    forkFinally y - const - killThread xThreadID 
                
  takeMVar xLock 
  killThread yThreadID

pushStream :: (Stream.OutputStream ByteString) -> B.Builder -> IO ()
pushStream s b = do
  _builderStream <- Stream.builderStream s 
  Stream.write (Just b) _builderStream
  Stream.write (Just BE.flush) _builderStream

tryAddr :: Text -> Int -> (SockAddr -> IO a) -> IO ()
tryAddr aHostName aPort = tryAddr' (Just aHostName) (Just aPort) 
                    (Just - defaultHints { addrSocketType = Stream })

tryAddr' :: Maybe Text -> Maybe Int -> Maybe AddrInfo -> 
              (SockAddr -> IO a) -> IO ()
tryAddr' aHostName aPort aHint f = do
  addrInfo <- getAddrInfo 
                    aHint
                    (fmap (view _Text) aHostName) 
                    (fmap show aPort)
  
  {-puts - "tryAddr':" <> show addrInfo-}
  let maybeAddr = addrInfo ^? traverse . to addrAddress
  case maybeAddr of 
    Nothing -> pute - "Can not resolve: " <> show aHostName
    Just _addr -> () <$ f _addr


tryParse :: IO a -> IO ()
tryParse io = flip catch (\e -> puts - show (e :: ParseException)) - () <$ io

withSocket :: Socket -> (Socket -> IO ()) -> IO ()
withSocket aSocket f = do
  f aSocket 
  sClose aSocket


sockAddr_To_AddrFamily :: SockAddr -> Family
sockAddr_To_AddrFamily = f where
    f (SockAddrInet  {}) = AF_INET
    f (SockAddrInet6 {}) = AF_INET6
    f (SockAddrUnix  {}) = AF_UNIX
    f _ = AF_INET

sockAddr_To_Port :: SockAddr -> String
sockAddr_To_Port = f where
    f (SockAddrInet  p _) = show p
    f (SockAddrInet6 p _ _ _) = show p
    f (SockAddrUnix {}) = ""
    f _ = ""

sockAddr_To_Host :: SockAddr -> String
sockAddr_To_Host = f where
    f s@(SockAddrInet  _ _) = P.takeWhile (/= ':') - show s
    f s@(SockAddrInet6 _ _ _ _) = reverse -
                                  P.dropWhile (== ':') -
                                  P.dropWhile (/= ':') - 
                                  reverse - show s
    f (SockAddrUnix s) = s
    f _ = ""

initSocketForType :: SockAddr -> SocketType -> IO Socket 
initSocketForType aSockAddr aSocketType = 
    socket (sockAddr_To_AddrFamily aSockAddr) aSocketType defaultProtocol


initSocket :: SockAddr -> IO Socket 
initSocket = flip initSocketForType Stream

clamp :: (Integral i) => i -> ByteString -> ByteString
clamp i x = S.take (fromIntegral i) - x <> S.replicate (fromIntegral i) 0

-- first bit is length
splitTokens :: Int -> ByteString -> [ByteString]
splitTokens l x  
  | l <= 1 = [x]
  | x == mempty = []
  | otherwise = 
      let 
          byteLength = l + (-1)
          (y, z) = S.splitAt byteLength x
          length_y_byte = fromIntegral - S.length y
      in
      clamp l (S.cons length_y_byte y) : splitTokens l z

tokenizeStream :: (Integral n) => n -> (ByteString -> ByteString) ->
                    InputStream ByteString -> IO (InputStream ByteString)
tokenizeStream n f input = 
  Stream.map (splitTokens - fromIntegral n) input 
      >>= concatLists >>= Stream.map f

decodeToken :: ByteString -> ByteString
decodeToken x
  | x == mempty = mempty
  | otherwise = S.take (fromIntegral - S.head x) - S.tail x

chunkStream :: (Integral n) => n -> InputStream ByteString -> 
                        IO (InputStream ByteString)
chunkStream n input = Stream.fromGenerator - go
  where
    l = fromIntegral n 
    go = liftIO (Stream.read input) >>= maybe (return $! ()) chunk
    chunk x 
      | S.length x >= l = Stream.yield (S.take l x) >> chunk (S.drop l x)
      | otherwise = 
          liftIO (Stream.read input) >>= 
            maybe (Stream.yield x) (chunk . (x <>))

detokenizeStream :: (Integral n) => n -> (ByteString -> ByteString) -> 
                                      InputStream ByteString -> 
                                      IO (InputStream ByteString)
detokenizeStream n f input = Stream.map (decodeToken . f) =<< 
                                    chunkStream n input

builder_To_ByteString :: B.Builder -> ByteString
builder_To_ByteString = LB.toStrict . B.toLazyByteString