diff --git a/gopher-proxy.cabal b/gopher-proxy.cabal
--- a/gopher-proxy.cabal
+++ b/gopher-proxy.cabal
@@ -2,7 +2,7 @@
 -- documentation, see http://haskell.org/cabal/users-guide/
 
 name:                gopher-proxy
-version:             0.1.0.1
+version:             0.1.0.2
 synopsis:            proxy gopher over http
 description:         A simple gopher-over-http proxy to http-ify gopher spaces.
 license:             GPL-3
@@ -22,6 +22,9 @@
   main-is:             Main.hs
   default-extensions:  CPP
   other-modules:       Paths_gopher_proxy
+                     , GopherProxy.Types
+                     , GopherProxy.Protocol
+                     , GopherProxy.Params
   build-depends:       base >=4.9 && <4.10
                      , wai
                      , warp
diff --git a/src/GopherProxy/Params.hs b/src/GopherProxy/Params.hs
new file mode 100644
--- /dev/null
+++ b/src/GopherProxy/Params.hs
@@ -0,0 +1,79 @@
+{-# LANGUAGE DeriveGeneric     #-}
+{-# LANGUAGE OverloadedStrings #-}
+module GopherProxy.Params
+  ( Params (..)
+  , params
+  , helpfulParams
+  ) where
+
+import qualified Data.ByteString as BS
+import Data.Maybe (fromMaybe)
+import Data.Monoid ((<>))
+import Data.Text (Text ())
+import Network.Mime (MimeType (), defaultMimeType)
+import Network.Socket (HostName (), PortNumber ())
+import Options.Applicative
+
+data Params
+  = Params
+    { hostname     :: HostName
+    , port         :: PortNumber
+    , httpPort     :: Int
+    , cssPath      :: Maybe FilePath
+    , cssUrl       :: BS.ByteString
+    , baseUrl      :: Text
+    , listenPublic :: Bool
+    , defaultMime  :: MimeType
+    , timeoutms    :: Int
+    }
+
+helpfulParams :: ParserInfo Params
+helpfulParams = info (helper <*> params) fullDesc
+
+params :: Parser Params
+params = Params
+  <$> strOption
+    (long "host"
+    <> metavar "HOSTNAME"
+    <> help "hostname of the target gopher server")
+  <*> optionalWithDefault 70 (option auto
+    (long "port"
+    <> metavar "PORT"
+    <> help "port of the target gopher server"))
+  <*> option auto
+    (long "http-port"
+    <> metavar "PORT"
+    <> help "port gopher-proxy should listen on")
+  <*> optional (strOption
+    (long "css-path"
+    <> metavar "PATH"
+    <> help "path of the css to be used"))
+  <*> optionalWithDefault "/gopher-proxy.css" (option auto
+    (long "css-url"
+    <> metavar "PATH"
+    <> help "absolute location of the css on the web server, defaults to \"/gopher-proxy.css\""))
+  <*> optionalWithDefault "/" (option auto
+    (long "base-url"
+    <> metavar "PATH"
+    <> help "base url where gopher-proxy is running, defaults to \"/\""))
+  <*> switch
+    (long "listen-public"
+    <> help "wether gopher-proxy should accept connection on public IP addresses.")
+  <*> optionalWithDefault defaultMimeType (option auto
+    (long "default-mime-type"
+    <> metavar "MIMETYPE"
+    <> help "spacecookie uses this mimetype, if it can't guess the type, defaults to application/octet-stream"))
+  <*> optionalWithDefault 10000000 (option auto
+    (long "timeout"
+    <> metavar "MILLISECONDS"
+    <> help "timeout for connecting to the specified gopher server, defaults to 10s."))
+
+
+optionalWithDefault :: a -> Parser a -> Parser a
+optionalWithDefault def p = fromMaybe def <$> optional p
+
+-- Thanks enum! <3
+#if !MIN_VERSION_network(2,6,3)
+instance Read PortNumber where
+  readsPrec i str = fmap (\(i,str) -> (fromInteger i,str)) $ readsPrec i str
+#endif
diff --git a/src/GopherProxy/Protocol.hs b/src/GopherProxy/Protocol.hs
new file mode 100644
--- /dev/null
+++ b/src/GopherProxy/Protocol.hs
@@ -0,0 +1,43 @@
+{-# LANGUAGE OverloadedStrings #-}
+module GopherProxy.Protocol where
+
+import GopherProxy.Types
+import Control.Applicative ((<|>), some)
+import Control.Error.Safe (readErr)
+import Data.Attoparsec.ByteString.Lazy
+import qualified Data.ByteString.Lazy as B
+import Data.Char (chr)
+import Data.Text (Text ())
+import qualified Data.Text as T
+import Data.Text.Encoding (decodeUtf8)
+
+itemValue = takeTill (inClass "\t\r\n")
+
+gopherMenuParser :: Parser GopherResponse
+gopherMenuParser = MenuResponse <$> some gopherMenuLine
+
+gopherMenuLine :: Parser MenuItem
+gopherMenuLine = do
+  char <- chr . fromIntegral <$> anyWord8
+  title <- B.fromStrict <$> itemValue
+  string "\t"
+  filePath <- B.fromStrict <$> itemValue
+  string "\t"
+  hostName <- itemValue
+  string "\t"
+  port <- T.unpack . decodeUtf8 <$> itemValue
+  string "\n" <|> string "\r\n"
+
+  case (readErr "port" port) of
+    Left e
+      -> fail e
+    Right p
+      -> pure $ MenuItem char title filePath (T.unpack (decodeUtf8 hostName)) p
+
+gopherFileParser :: Parser GopherResponse
+gopherFileParser = FileResponse <$> takeLazyByteString
+
+gopherResponseParser :: Maybe GopherResponseType -> Parser GopherResponse
+gopherResponseParser (Just Menu) = gopherMenuParser
+gopherResponseParser (Just File) = gopherFileParser
+gopherResponseParser Nothing = gopherMenuParser <|> gopherFileParser
diff --git a/src/GopherProxy/Types.hs b/src/GopherProxy/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/GopherProxy/Types.hs
@@ -0,0 +1,18 @@
+module GopherProxy.Types where
+
+import Data.ByteString.Lazy (ByteString ())
+import Network.Socket (HostName (), PortNumber ())
+
+data GopherResponse
+  = MenuResponse [MenuItem]
+  | FileResponse ByteString
+  deriving Show
+
+data GopherResponseType
+  = Menu
+  | File
+
+data MenuItem
+  = MenuItem Char ByteString ByteString HostName PortNumber
+  deriving Show
+
