diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,32 @@
+Copyright 2001-2010, The University Court of the University of
+Glasgow, Bjorn Bringert, Andy Gill, Anders Kaseorg, Ian Lynagh, Erik
+Meijer, Sven Panne, Jeremy Shaw
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+- Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+- Neither name of the University nor the names of its contributors may be
+used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY COURT OF THE UNIVERSITY OF
+GLASGOW AND THE CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+UNIVERSITY COURT OF THE UNIVERSITY OF GLASGOW OR THE CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
diff --git a/Network/Multipart.hs b/Network/Multipart.hs
new file mode 100644
--- /dev/null
+++ b/Network/Multipart.hs
@@ -0,0 +1,219 @@
+-- #hide
+
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Network.CGI.Multipart
+-- Copyright   :  (c) Peter Thiemann 2001,2002
+--                (c) Bjorn Bringert 2005-2006
+-- License     :  BSD-style
+--
+-- Maintainer  :  Anders Kaseorg <andersk@mit.edu>
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Parsing of the multipart format from RFC2046.
+-- Partly based on code from WASHMail.
+--
+-----------------------------------------------------------------------------
+module Network.Multipart
+    (
+     -- * Multi-part messages
+     MultiPart(..), BodyPart(..)
+    , parseMultipartBody, hGetMultipartBody
+    , showMultipartBody
+     -- * Headers
+    , ContentType(..), ContentTransferEncoding(..)
+    , ContentDisposition(..)
+    , parseContentType
+    , getContentType
+    , getContentTransferEncoding
+    , getContentDisposition
+    ) where
+
+import Control.Monad
+import Data.Int (Int64)
+import Data.List (intersperse)
+import Data.Maybe
+import System.IO (Handle)
+
+import Network.Multipart.Header
+
+import qualified Data.ByteString.Lazy.Char8 as BS
+import Data.ByteString.Lazy.Char8 (ByteString)
+
+--
+-- * Multi-part stuff.
+--
+
+data MultiPart = MultiPart [BodyPart]
+               deriving (Show, Eq, Ord)
+
+data BodyPart = BodyPart Headers ByteString
+                deriving (Show, Eq, Ord)
+
+-- | Read a multi-part message from a 'ByteString'.
+parseMultipartBody :: String -- ^ Boundary
+                   -> ByteString -> MultiPart
+parseMultipartBody b =
+    MultiPart . mapMaybe parseBodyPart . splitParts (BS.pack b)
+
+-- | Read a multi-part message from a 'Handle'.
+--   Fails on parse errors.
+hGetMultipartBody :: String -- ^ Boundary
+                  -> Handle
+                  -> IO MultiPart
+hGetMultipartBody b = liftM (parseMultipartBody b) . BS.hGetContents
+
+
+
+parseBodyPart :: ByteString -> Maybe BodyPart
+parseBodyPart s =
+    do
+    let (hdr,bdy) = splitAtEmptyLine s
+    hs <- parseM pHeaders "<input>" (BS.unpack hdr)
+    return $ BodyPart hs bdy
+
+showMultipartBody :: String -> MultiPart -> ByteString
+showMultipartBody b (MultiPart bs) =
+    unlinesCRLF $ foldr (\x xs -> d:showBodyPart x:xs) [c,BS.empty] bs
+ where d = BS.pack ("--" ++ b)
+       c = BS.pack ("--" ++ b ++ "--")
+
+showBodyPart :: BodyPart -> ByteString
+showBodyPart (BodyPart hs c) =
+    unlinesCRLF $ [BS.pack (n++": "++v) | (HeaderName n,v) <- hs] ++ [BS.empty,c]
+
+
+--
+-- * Splitting into multipart parts.
+--
+
+-- | Split a multipart message into the multipart parts.
+splitParts :: ByteString -- ^ The boundary, without the initial dashes
+           -> ByteString
+           -> [ByteString]
+splitParts b = spl . dropPreamble b
+  where
+  spl x = case splitAtBoundary b x of
+            Nothing -> []
+            Just (s1,d,s2) | isClose b d -> [s1]
+                           | otherwise -> s1:spl s2
+
+-- | Drop everything up to and including the first line starting
+--   with the boundary.
+dropPreamble :: ByteString -- ^ The boundary, without the initial dashes
+             -> ByteString
+             -> ByteString
+dropPreamble b s | BS.null s = BS.empty
+                 | isBoundary b s = dropLine s
+                 | otherwise = dropPreamble b (dropLine s)
+
+-- | Split a string at the first boundary line.
+splitAtBoundary :: ByteString -- ^ The boundary, without the initial dashes
+                -> ByteString -- ^ String to split.
+                -> Maybe (ByteString,ByteString,ByteString)
+                   -- ^ The part before the boundary, the boundary line,
+                   --   and the part after the boundary line. The CRLF
+                   --   before and the CRLF (if any) after the boundary line
+                   --   are not included in any of the strings returned.
+                   --   Returns 'Nothing' if there is no boundary.
+splitAtBoundary b s = spl 0
+  where
+  spl i = case findCRLF (BS.drop i s) of
+              Nothing -> Nothing
+              Just (j,l) | isBoundary b s2 -> Just (s1,d,s3)
+                         | otherwise -> spl (i+j+l)
+                  where
+                  s1 = BS.take (i+j) s
+                  s2 = BS.drop (i+j+l) s
+                  (d,s3) = splitAtCRLF s2
+
+-- | Check whether a string starts with two dashes followed by
+--   the given boundary string.
+isBoundary :: ByteString -- ^ The boundary, without the initial dashes
+           -> ByteString
+           -> Bool
+isBoundary b s = startsWithDashes s && b `BS.isPrefixOf` BS.drop 2 s
+
+-- | Check whether a string for which 'isBoundary' returns true
+--   has two dashes after the boudary string.
+isClose :: ByteString -- ^ The boundary, without the initial dashes
+        -> ByteString
+        -> Bool
+isClose b s = startsWithDashes (BS.drop (2+BS.length b) s)
+
+-- | Checks whether a string starts with two dashes.
+startsWithDashes :: ByteString -> Bool
+startsWithDashes s = BS.pack "--" `BS.isPrefixOf` s
+
+
+--
+-- * RFC 2046 CRLF
+--
+
+crlf :: ByteString
+crlf = BS.pack "\r\n"
+
+unlinesCRLF :: [ByteString] -> ByteString
+unlinesCRLF = BS.concat . intersperse crlf
+
+-- | Drop everything up to and including the first CRLF.
+dropLine :: ByteString -> ByteString
+dropLine s = snd (splitAtCRLF s)
+
+-- | Split a string at the first empty line. The CRLF (if any) before the
+--   empty line is included in the first result. The CRLF after the
+--   empty line is not included in the result.
+--   If there is no empty line, the entire input is returned
+--   as the first result.
+splitAtEmptyLine :: ByteString -> (ByteString, ByteString)
+splitAtEmptyLine s | startsWithCRLF s = (BS.empty, dropCRLF s)
+                   | otherwise = spl 0
+  where
+  spl i = case findCRLF (BS.drop i s) of
+              Nothing -> (s, BS.empty)
+              Just (j,l) | startsWithCRLF s2 -> (s1, dropCRLF s2)
+                         | otherwise -> spl (i+j+l)
+                where (s1,s2) = BS.splitAt (i+j+l) s
+
+-- | Split a string at the first CRLF. The CRLF is not included
+--   in any of the returned strings.
+--   If there is no CRLF, the entire input is returned
+--   as the first string.
+splitAtCRLF :: ByteString -- ^ String to split.
+            -> (ByteString,ByteString)
+splitAtCRLF s = case findCRLF s of
+                  Nothing -> (s,BS.empty)
+                  Just (i,l) -> (s1, BS.drop l s2)
+                      where (s1,s2) = BS.splitAt i s
+
+-- | Get the index and length of the first CRLF, if any.
+findCRLF :: ByteString -- ^ String to split.
+         -> Maybe (Int64,Int64)
+findCRLF s =
+    case findCRorLF s of
+              Nothing -> Nothing
+              Just j | BS.null (BS.drop (j+1) s) -> Just (j,1)
+              Just j -> case (BS.index s j, BS.index s (j+1)) of
+                           ('\n','\r') -> Just (j,2)
+                           ('\r','\n') -> Just (j,2)
+                           _           -> Just (j,1)
+
+findCRorLF :: ByteString -> Maybe Int64
+findCRorLF s = BS.findIndex (\c -> c == '\n' || c == '\r') s
+
+startsWithCRLF :: ByteString -> Bool
+startsWithCRLF s = not (BS.null s) && (c == '\n' || c == '\r')
+  where c = BS.index s 0
+
+-- | Drop an initial CRLF, if any. If the string is empty,
+--   nothing is done. If the string does not start with CRLF,
+--   the first character is dropped.
+dropCRLF :: ByteString -> ByteString
+dropCRLF s | BS.null s = BS.empty
+           | BS.null (BS.drop 1 s) = BS.empty
+           | c0 == '\n' && c1 == '\r' = BS.drop 2 s
+           | c0 == '\r' && c1 == '\n' = BS.drop 2 s
+           | otherwise = BS.drop 1 s
+  where c0 = BS.index s 0
+        c1 = BS.index s 1
diff --git a/Network/Multipart/Header.hs b/Network/Multipart/Header.hs
new file mode 100644
--- /dev/null
+++ b/Network/Multipart/Header.hs
@@ -0,0 +1,251 @@
+-- #hide
+
+-----------------------------------------------------------------------------
+-- |
+-- Module      :  Network.CGI.Header
+-- Copyright   :  (c) Peter Thiemann 2001,2002
+--                (c) Bjorn Bringert 2005-2006
+-- License     :  BSD-style
+--
+-- Maintainer  :  Anders Kaseorg <andersk@mit.edu>
+-- Stability   :  experimental
+-- Portability :  non-portable
+--
+-- Parsing of HTTP headers (name, value pairs)
+-- Partly based on code from WASHMail.
+--
+-----------------------------------------------------------------------------
+module Network.Multipart.Header (
+                              -- * Headers
+                              Headers,
+                              HeaderName(..),
+                              HeaderValue(..),
+                              pHeaders,
+
+                              -- * Content-type
+                              ContentType(..),
+                              getContentType,
+                              parseContentType,
+                              showContentType,
+
+                              -- * Content-transfer-encoding
+                              ContentTransferEncoding(..),
+                              getContentTransferEncoding,
+
+                              -- * Content-disposition
+                              ContentDisposition(..),
+                              getContentDisposition,
+
+                              -- * Utilities
+                              parseM,
+                              caseInsensitiveEq,
+                              caseInsensitiveCompare,
+                              lexeme, ws1, p_token
+                          ) where
+
+import Control.Monad
+import Data.Char
+import Data.List
+
+import Network.CGI.Protocol
+import Network.CGI
+
+import Text.ParserCombinators.Parsec
+
+--
+-- * Headers
+--
+
+class HeaderValue a where
+    parseHeaderValue :: Parser a
+    prettyHeaderValue :: a -> String
+
+pHeaders :: Parser Headers
+pHeaders = many pHeader
+
+pHeader :: Parser (HeaderName, String)
+pHeader =
+    do name <- many1 headerNameChar
+       _ <- char ':'
+       _ <- many ws1
+       line <- lineString
+       _ <- crLf
+       extraLines <- many extraFieldLine
+       return (HeaderName name, concat (line:extraLines))
+
+extraFieldLine :: Parser String
+extraFieldLine =
+    do sp <- ws1
+       line <- lineString
+       _ <- crLf
+       return (sp:line)
+
+getHeaderValue :: (Monad m, HeaderValue a) => String -> Headers -> m a
+getHeaderValue h hs = lookupM (HeaderName h) hs >>= parseM parseHeaderValue h
+
+--
+-- * Parameters (for Content-type etc.)
+--
+
+showParameters :: [(String,String)] -> String
+showParameters = concatMap f
+    where f (n,v) = "; " ++ n ++ "=\"" ++ concatMap esc v ++ "\""
+          esc '\\' = "\\\\"
+          esc '"'  = "\\\""
+          esc c | c `elem` ['\\','"'] = '\\':[c]
+                | otherwise = [c]
+
+p_parameter :: Parser (String,String)
+p_parameter = try $
+  do _ <- lexeme $ char ';'
+     p_name <- lexeme $ p_token
+     -- Don't allow parameters named q. This is needed for parsing Accept-X
+     -- headers. From RFC 2616 14.1:
+     --    Note: Use of the "q" parameter name to separate media type
+     --    parameters from Accept extension parameters is due to historical
+     --    practice. Although this prevents any media type parameter named
+     --    "q" from being used with a media range, such an event is believed
+     --    to be unlikely given the lack of any "q" parameters in the IANA
+     --    media type registry and the rare usage of any media type
+     --    parameters in Accept. Future media types are discouraged from
+     --    registering any parameter named "q".
+     when (p_name == "q") pzero
+     _ <- lexeme $ char '='
+     -- Workaround for seemingly standardized web browser bug
+     -- where nothing is escaped in the filename parameter
+     -- of the content-disposition header in multipart/form-data
+     let litStr = if p_name == "filename"
+                   then buggyLiteralString
+                   else literalString
+     p_value <- litStr <|> p_token
+     return (map toLower p_name, p_value)
+
+instance HeaderValue ContentType where
+    parseHeaderValue =
+        do _ <- many ws1
+           c_type <- p_token
+           _ <- char '/'
+           c_subtype <- lexeme $ p_token
+           c_parameters <- many p_parameter
+           return $ ContentType (map toLower c_type) (map toLower c_subtype) c_parameters
+    prettyHeaderValue (ContentType x y ps) = x ++ "/" ++ y ++ showParameters ps
+
+
+getContentType :: Monad m => Headers -> m ContentType
+getContentType = getHeaderValue "content-type"
+
+--
+-- * Content transfer encoding
+--
+
+data ContentTransferEncoding =
+	ContentTransferEncoding String
+    deriving (Show, Read, Eq, Ord)
+
+instance HeaderValue ContentTransferEncoding where
+    parseHeaderValue =
+        do _ <- many ws1
+           c_cte <- p_token
+           return $ ContentTransferEncoding (map toLower c_cte)
+    prettyHeaderValue (ContentTransferEncoding s) = s
+
+getContentTransferEncoding :: Monad m => Headers -> m ContentTransferEncoding
+getContentTransferEncoding = getHeaderValue "content-transfer-encoding"
+
+--
+-- * Content disposition
+--
+
+data ContentDisposition =
+	ContentDisposition String [(String, String)]
+    deriving (Show, Read, Eq, Ord)
+
+instance HeaderValue ContentDisposition where
+    parseHeaderValue =
+        do _ <- many ws1
+           c_cd <- p_token
+           c_parameters <- many p_parameter
+           return $ ContentDisposition (map toLower c_cd) c_parameters
+    prettyHeaderValue (ContentDisposition t hs) =
+        t ++ concat ["; " ++ n ++ "=" ++ quote v | (n,v) <- hs]
+            where quote x = "\"" ++ x ++ "\"" -- NOTE: silly, but de-facto standard
+
+getContentDisposition :: Monad m => Headers -> m ContentDisposition
+getContentDisposition = getHeaderValue "content-disposition"
+
+--
+-- * Utilities
+--
+
+parseM :: Monad m => Parser a -> SourceName -> String -> m a
+parseM p n inp =
+  case parse p n inp of
+    Left e -> fail (show e)
+    Right x -> return x
+
+lookupM :: (Monad m, Eq a, Show a) => a -> [(a,b)] -> m b
+lookupM n = maybe (fail ("No such field: " ++ show n)) return . lookup n
+
+caseInsensitiveEq :: String -> String -> Bool
+caseInsensitiveEq x y = map toLower x == map toLower y
+
+caseInsensitiveCompare :: String -> String -> Ordering
+caseInsensitiveCompare x y = map toLower x `compare` map toLower y
+
+--
+-- * Parsing utilities
+--
+
+-- | RFC 822 LWSP-char
+ws1 :: Parser Char
+ws1 = oneOf " \t"
+
+lexeme :: Parser a -> Parser a
+lexeme p = do x <- p; _ <- many ws1; return x
+
+-- | RFC 822 CRLF (but more permissive)
+crLf :: Parser String
+crLf = try (string "\n\r" <|> string "\r\n") <|> string "\n" <|> string "\r"
+
+-- | One line
+lineString :: Parser String
+lineString = many (noneOf "\n\r")
+
+literalString :: Parser String
+literalString = do _ <- char '\"'
+		   str <- many (noneOf "\"\\" <|> quoted_pair)
+		   _ <- char '\"'
+		   return str
+
+-- No web browsers seem to implement RFC 2046 correctly,
+-- since they do not escape double quotes and backslashes
+-- in the filename parameter in multipart/form-data.
+--
+-- Note that this eats everything until the last double quote on the line.
+buggyLiteralString :: Parser String
+buggyLiteralString =
+    do _ <- char '\"'
+       str <- manyTill anyChar (try lastQuote)
+       return str
+  where lastQuote = do _ <- char '\"'
+                       notFollowedBy (try (many (noneOf "\"") >> char '\"'))
+
+headerNameChar :: Parser Char
+headerNameChar = noneOf "\n\r:"
+
+tspecials, tokenchar :: [Char]
+tspecials = "()<>@,;:\\\"/[]?="
+tokenchar = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" \\ tspecials
+
+p_token :: Parser String
+p_token = many1 (oneOf tokenchar)
+
+text_chars :: [Char]
+text_chars = map chr ([1..9] ++ [11,12] ++ [14..127])
+
+p_text :: Parser Char
+p_text = oneOf text_chars
+
+quoted_pair :: Parser Char
+quoted_pair = do _ <- char '\\'
+		 p_text
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,6 @@
+module Main (main) where
+
+import Distribution.Simple
+
+main :: IO ()
+main = defaultMain
diff --git a/multipart.cabal b/multipart.cabal
new file mode 100644
--- /dev/null
+++ b/multipart.cabal
@@ -0,0 +1,30 @@
+name:                multipart
+version:             0.1
+copyright:           Bjorn Bringert, Andy Gill, Anders Kaseorg, Ian Lynagh, Erik Meijer, Sven Panne, Jeremy Shaw
+category:            Network
+maintainer:          code@silk.co
+author:              Silk B.V.
+homepage:            http://www.github.com/silkapp/multipart
+license:             BSD3
+license-file:        LICENSE
+synopsis:            A partial fork of the cgi package exposing the multipart module
+build-type:          Simple
+cabal-version:       >=1.10
+
+source-repository head
+  type:              git
+  location:          https://github.com/silkapp/multipart.git
+
+library
+  default-language:  Haskell2010
+  ghc-options:       -Wall
+  exposed-modules:   Network.Multipart
+  other-modules:     Network.Multipart.Header
+  build-depends:
+      base >= 3 && < 5
+      -- Reject versions depending on monads-tf
+    , MonadCatchIO-mtl == 0.3.0.5
+    , MonadCatchIO-transformers == 0.3.0.*
+    , bytestring
+    , cgi >= 3001.1.8 && < 3001.1.9
+    , parsec >= 2.0
