diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,6 @@
+# Changelog for curl-cookiejar
+
+## 0.1
+
+### 0.1.0.0
+* Initial version
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2019 Plow Technologies
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,5 @@
+# `curl-cookiejar`: Read cURL/wget cookie jar files
+
+This library provides parsers and pretty-printers which interface the `Cookie`
+`CookieJar` types defined by the `http-client` Haskell library with cookie jar
+files written and read buy the cURL and wget command line utilities.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/curl-cookiejar.cabal b/curl-cookiejar.cabal
new file mode 100644
--- /dev/null
+++ b/curl-cookiejar.cabal
@@ -0,0 +1,48 @@
+-- This file has been generated from package.yaml by hpack version 0.28.2.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 468b6900ea75fad340b259122f0afde0ec31969ba43a9b17b8a3c0aad88f5062
+
+name:           curl-cookiejar
+version:        0.1.0.0
+synopsis:       Parsing and pretty-printing of cURL/wget cookie jars
+description:    See README.md
+category:       Web
+homepage:       https://github.com/plow-technologies/curl-cookiejar#readme
+bug-reports:    https://github.com/plow-technologies/curl-cookiejar/issues
+author:         Edward Amsden
+maintainer:     edward.amsden@plowtech.net
+copyright:      2019 Plow Technologies
+license:        MIT
+license-file:   LICENSE
+build-type:     Simple
+cabal-version:  >= 1.10
+extra-source-files:
+    ChangeLog.md
+    README.md
+
+source-repository head
+  type: git
+  location: https://github.com/plow-technologies/curl-cookiejar
+
+library
+  exposed-modules:
+      Data.CURL.CookieJar
+      Data.CURL.CookieJar.Conduit
+      Data.CURL.CookieJar.Parser
+      Data.CURL.CookieJar.PrettyPrinter
+  other-modules:
+      Paths_curl_cookiejar
+  hs-source-dirs:
+      src
+  ghc-options: -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates
+  build-depends:
+      attoparsec
+    , base >=4.10 && <5
+    , bytestring
+    , conduit
+    , conduit-extra
+    , http-client
+    , time
+  default-language: Haskell2010
diff --git a/src/Data/CURL/CookieJar.hs b/src/Data/CURL/CookieJar.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/CURL/CookieJar.hs
@@ -0,0 +1,18 @@
+module Data.CURL.CookieJar 
+  ( readCookieJarFile
+  , writeCookieJarFile
+  , CookieJarHeader(..)
+  ) where
+
+import Conduit (runResourceT, runConduit)
+import Data.CURL.CookieJar.Conduit
+import Data.CURL.CookieJar.PrettyPrinter
+import Network.HTTP.Client (CookieJar)
+
+readCookieJarFile :: FilePath -> IO CookieJar
+readCookieJarFile path = runResourceT $ runConduit $ readCookieJarFileC path
+
+writeCookieJarFile :: CookieJarHeader -> FilePath -> CookieJar -> IO ()
+writeCookieJarFile header path jar = runResourceT $ runConduit $ writeCookieJarFileC header jar path
+
+
diff --git a/src/Data/CURL/CookieJar/Conduit.hs b/src/Data/CURL/CookieJar/Conduit.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/CURL/CookieJar/Conduit.hs
@@ -0,0 +1,186 @@
+{-|
+Conduit sources/sinks for Netscape/Mozilla cookie jar format.
+|-}
+module Data.CURL.CookieJar.Conduit
+  (
+  -- * Parsing cookie jars
+    parseCookiesC
+  , parseCookiesEitherC
+  , sinkCookieJar
+  , sinkCookieJarEither
+  , sourceCookieJarCookies
+  -- * Pretty-printing cookie jars
+  , prettyCookieC
+  , prettyCookiesC
+  , prettyCookieJarC
+  -- * Handle and file IO
+  , readCookieJarFileC
+  , readCookieJarHandleC
+  , sourceCookiesFile
+  , sourceCookiesHandle
+  , sinkCookiesFile
+  , sinkCookiesHandle
+  , writeCookieJarFileC
+  , writeCookieJarHandleC
+  ) where
+
+import Data.ByteString (ByteString)
+import Conduit
+  ( MonadThrow
+  , PrimMonad
+  , MonadIO
+  , MonadResource
+  , mapC
+  , ConduitT
+  , (.|)
+  , yield
+  , sourceFile
+  , sourceHandle
+  , sinkFile
+  , sinkHandle
+  )
+import System.IO (Handle)
+import Data.Conduit.List (sourceList, consume)
+import Data.Conduit.ByteString.Builder (builderToByteString)
+import Data.Conduit.Attoparsec
+  ( ParseError, conduitParser, conduitParserEither, sinkParser, sinkParserEither)
+import Data.CURL.CookieJar.Parser (cookieParser, cookieJarParser)
+import Data.CURL.CookieJar.PrettyPrinter (CookieJarHeader, prettyCookie, prettyCookieJarHeader)
+import Network.HTTP.Client (Cookie, CookieJar, createCookieJar, destroyCookieJar)
+
+-- | Parse cookies from a cookie jar
+--
+-- Note that this does not skip the header
+parseCookiesC :: MonadThrow m => ConduitT ByteString Cookie m ()
+parseCookiesC = conduitParser cookieParser .| mapC snd
+
+-- | Parse cookies from a cookie jar, returning errors as values
+parseCookiesEitherC :: Monad m => ConduitT ByteString (Either ParseError Cookie) m ()
+parseCookiesEitherC =  conduitParserEither cookieParser .| mapC (fmap snd)
+
+-- | Parse cookies into a cookie jar, and return the value
+sinkCookieJar :: MonadThrow m => ConduitT ByteString o m CookieJar
+sinkCookieJar = sinkParser cookieJarParser
+
+-- | Parse cookies into a cookie jar, and return the value, or a parse error
+sinkCookieJarEither :: Monad m => ConduitT ByteString o m (Either ParseError CookieJar)
+sinkCookieJarEither = sinkParserEither cookieJarParser
+
+-- | Pretty print a single cookie and stream out a chunked ByteString
+prettyCookieC
+  :: PrimMonad m
+  => Cookie
+  -> ConduitT i ByteString m ()
+prettyCookieC cookie =
+  (yield $ prettyCookie cookie) .| builderToByteString
+
+-- | Stream in cookies to pretty-print, along with a header
+prettyCookiesC
+  :: PrimMonad m
+  => CookieJarHeader
+  -> ConduitT Cookie ByteString m ()
+prettyCookiesC header =
+  cookiesAndHeader .| builderToByteString
+  where
+    cookiesAndHeader = do
+      yield $ prettyCookieJarHeader header
+      mapC prettyCookie
+
+-- | Pretty-print a cookie jar
+prettyCookieJarC
+  :: PrimMonad m
+  => CookieJarHeader
+  -> CookieJar
+  -> ConduitT i ByteString m ()
+prettyCookieJarC header jar =
+  sourceCookieJarCookies jar .| prettyCookiesC header
+
+-- | Read a cookie jar from a file
+readCookieJarFileC
+  :: MonadThrow m
+  => MonadResource m
+  => FilePath
+  -> ConduitT i o m CookieJar
+readCookieJarFileC path = sourceCookiesFile path .| sinkCookieJarCookies
+
+-- | Read a cookie jar from a handle
+readCookieJarHandleC
+  :: MonadIO m
+  => MonadThrow m
+  => Handle
+  -> ConduitT i o m CookieJar
+readCookieJarHandleC handle = sourceCookiesHandle handle .| sinkCookieJarCookies
+
+-- | Source cookies from a cookie jar
+sourceCookieJarCookies
+  :: Monad m
+  => CookieJar
+  -> ConduitT i Cookie m ()
+sourceCookieJarCookies =
+  sourceList . destroyCookieJar
+
+-- | Make a cookie jar from a finite stream of cookies
+sinkCookieJarCookies
+  :: Monad m
+  => ConduitT Cookie o m CookieJar
+sinkCookieJarCookies = createCookieJar <$> consume
+
+-- | Source cookies from a cookie jar file
+sourceCookiesFile
+  :: MonadThrow m
+  => MonadResource m
+  => FilePath
+  -> ConduitT i Cookie m ()
+sourceCookiesFile path =
+  sourceFile path .| parseCookiesC
+
+-- | Source cookies from a handle
+sourceCookiesHandle
+  :: MonadThrow m
+  => MonadIO m
+  => Handle
+  -> ConduitT i Cookie m ()
+sourceCookiesHandle handle =
+  sourceHandle handle .| parseCookiesC
+
+-- | Write cookies to a file
+sinkCookiesFile
+  :: MonadResource m
+  => PrimMonad m
+  => CookieJarHeader
+  -> FilePath
+  -> ConduitT Cookie o m ()
+sinkCookiesFile header path =
+  prettyCookiesC header .| sinkFile path
+
+-- | Write cookies to a handle
+sinkCookiesHandle
+  :: MonadIO m
+  => PrimMonad m
+  => CookieJarHeader
+  -> Handle
+  -> ConduitT Cookie o m ()
+sinkCookiesHandle header handle =
+  prettyCookiesC header .| sinkHandle handle
+
+-- | Write a cookie jar to a file
+writeCookieJarFileC
+  :: MonadResource m
+  => PrimMonad m
+  => CookieJarHeader
+  -> CookieJar
+  -> FilePath
+  -> ConduitT i o m ()
+writeCookieJarFileC header jar path =
+  sourceCookieJarCookies jar .| sinkCookiesFile header path
+
+writeCookieJarHandleC
+  :: MonadIO m
+  => PrimMonad m
+  => CookieJarHeader
+  -> CookieJar
+  -> Handle
+  -> ConduitT i o m ()
+writeCookieJarHandleC header jar handle =
+  sourceCookieJarCookies jar .| sinkCookiesHandle header handle
+
diff --git a/src/Data/CURL/CookieJar/Parser.hs b/src/Data/CURL/CookieJar/Parser.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/CURL/CookieJar/Parser.hs
@@ -0,0 +1,82 @@
+{-# LANGUAGE ApplicativeDo #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-| Attoparsec 'Parser' for a cookie jar, and convenience function to run it |-}
+module Data.CURL.CookieJar.Parser
+  ( cookieJarParser
+  , cookieParser
+  , parseCookieJar
+  ) where
+
+import Control.Applicative ((<|>))
+import Control.Monad (void)
+import Data.ByteString (ByteString)
+import Data.Char (ord)
+import Data.Attoparsec.ByteString.Char8
+  ( many'
+  , endOfLine
+  , endOfLine
+  , isEndOfLine
+  , takeWhile1
+  , decimal
+  , skipSpace
+  , skipWhile
+  , char
+  , parseOnly
+  , try
+  , Parser
+  )
+import Network.HTTP.Client (Cookie(..), CookieJar, createCookieJar)
+import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
+
+-- | Parse a cookie jar in the Netscape/Mozilla format
+parseCookieJar :: ByteString -> Either String CookieJar
+parseCookieJar = parseOnly cookieJarParser
+
+-- | Parser a cookie jar in the Netscape/Mozilla format
+cookieJarParser :: Parser CookieJar
+cookieJarParser = createCookieJar <$> many' cookieParser
+
+-- | Parser for one cookie/line in a cookie jar in the Netscape/Mozilla format
+-- This will also consume any comment lines preceding the cookie line.
+--
+-- This parser recognizes the magic prefix @#HttpOnly_# and sets the appropriate
+-- field in the Cookie datatype
+cookieParser :: Parser Cookie
+cookieParser = 
+  skipSpace *> httpOnlyLine <|> commentLine <|> cookieLine
+  where
+    httpOnlyLine = try $ "#HttpOnly_" *> cookieLineParser True
+    commentLine = "#" *> skipWhile notEndOfLine *> endOfLine *> cookieParser
+    cookieLine = cookieLineParser False
+    cookieLineParser cookie_http_only = do
+      let -- these are the fields not represented by the cookie jar format
+        cookie_creation_time = epoch
+        cookie_last_access_time = epoch
+        cookie_persistent = True
+      cookie_domain <- stringField
+      tab
+      cookie_host_only <- boolField
+      tab
+      cookie_path <- stringField
+      tab
+      cookie_secure_only <- boolField
+      tab
+      cookie_expiry_time <- timeField
+      tab
+      cookie_name <- stringField
+      tab
+      cookie_value <- lastField
+      (endOfLine <|> pure ())
+      pure $ Cookie {..}
+      where
+        tab = void $ char '\t'
+        stringField = takeWhile1 (/= '\t')
+        boolField = (True <$ "TRUE") <|> (False <$ "FALSE")
+        timeField = posixSecondsToUTCTime <$> fromInteger <$> decimal
+        lastField = takeWhile1 (notEndOfLine)
+        epoch = posixSecondsToUTCTime 0
+      
+
+notEndOfLine :: Char -> Bool
+notEndOfLine = not . isEndOfLine . fromIntegral . ord
diff --git a/src/Data/CURL/CookieJar/PrettyPrinter.hs b/src/Data/CURL/CookieJar/PrettyPrinter.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/CURL/CookieJar/PrettyPrinter.hs
@@ -0,0 +1,83 @@
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-|
+Pretty-printer to cookie jar format for the 'Cookie' and 'CookieJar' types
+from http-types.
+
+__WARNING__: This is not a full serialization of the 'Cookie' and 'CookieJar'
+types. The Netscape cookie jar format does not support all of the fields.
+
+In particular, the following fields of 'Cookie' are not represented:
+
+  * 'cookie_creation_time'
+  * 'cookie_last_access_time'
+  * 'cookie_persistent'
+  * 'cookie_http_only'
+|-}   
+module Data.CURL.CookieJar.PrettyPrinter
+  ( prettyCookieJar
+  , CookieJarHeader(..)
+  , prettyCookieJarHeader
+  , prettyCookie
+  -- |
+  -- * Re-export
+  -- This is re-exported from 'Data.ByteString.Builder' for convenience
+  , toLazyByteString
+  ) where
+
+import Data.Semigroup ((<>))
+import Network.HTTP.Client (Cookie(..), CookieJar, destroyCookieJar)
+import Data.ByteString.Builder
+  ( Builder
+  , byteString
+  , integerDec
+  , char7
+  , toLazyByteString
+  )
+import Data.Foldable (foldMap)
+import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)
+
+-- | Specify what header, if any, to print at the top of the cookie jar
+--
+-- Some other parsers (like Perl's) require the header to be present
+data CookieJarHeader
+  = NoHeader
+  | HTTPHeader
+  | NetscapeHeader
+
+-- | Print a cookie jar in the Netscape/Mozilla format, with optional
+-- leading header
+prettyCookieJar :: CookieJarHeader -> CookieJar -> Builder
+prettyCookieJar header jar =
+     prettyCookieJarHeader header
+  <> foldMap ((<> "\n") . prettyCookie) (destroyCookieJar jar)
+
+-- | Print a cookie jar header
+prettyCookieJarHeader :: CookieJarHeader -> Builder
+prettyCookieJarHeader NoHeader = mempty
+prettyCookieJarHeader HTTPHeader = "# HTTP Cookie File\n"
+prettyCookieJarHeader NetscapeHeader = "# Netscape HTTP Cookie File\n"
+
+-- | Print an individual cookie on a single line
+prettyCookie :: Cookie -> Builder
+prettyCookie (Cookie {..}) =
+     byteString cookie_domain
+  <> tab
+  <> bool cookie_host_only
+  <> tab
+  <> byteString cookie_path
+  <> tab
+  <> bool cookie_secure_only
+  <> tab
+  <> unixTime cookie_expiry_time
+  <> tab
+  <> byteString cookie_name
+  <> tab
+  <> byteString cookie_value
+  where
+    bool True = "TRUE"
+    bool False = "FALSE"
+    unixTime = integerDec . round . utcTimeToPOSIXSeconds
+    tab = char7 '\t'
+
+
