diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+The following license covers this documentation, and the source code, except
+where otherwise indicated.
+
+Copyright 2010, Suite Solutions. 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.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "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 COPYRIGHT HOLDERS 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/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs
new file mode 100644
--- /dev/null
+++ b/Network/Wai/Handler/Warp.hs
@@ -0,0 +1,346 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE BangPatterns #-}
+---------------------------------------------------------
+-- |
+-- Module        : Network.Wai.Handler.Warp
+-- Copyright     : Michael Snoyman
+-- License       : BSD3
+--
+-- Maintainer    : Michael Snoyman <michael@snoyman.com>
+-- Stability     : Stable
+-- Portability   : portable
+--
+-- A fast, light-weight HTTP server handler for WAI.
+--
+---------------------------------------------------------
+module Network.Wai.Handler.Warp
+    ( run
+    , sendResponse
+    , parseRequest
+#if TEST
+    , takeLineMax
+    , takeHeaders
+    , InvalidRequest (..)
+#endif
+    ) where
+
+import Prelude hiding (catch)
+import Network.Wai
+import qualified System.IO
+
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as S
+import qualified Data.ByteString.Unsafe as SU
+import qualified Data.ByteString.Char8 as B
+import qualified Data.ByteString.Lazy as L
+import Network
+    ( listenOn, sClose, PortID(PortNumber), Socket
+    , withSocketsDo)
+import Network.Socket
+    ( accept, SockAddr
+    )
+import qualified Network.Socket.ByteString as Sock
+import Control.Exception (bracket, finally, Exception, SomeException, catch)
+import Control.Concurrent (forkIO)
+import Data.Maybe (fromMaybe)
+
+import Data.Typeable (Typeable)
+
+import Data.Enumerator (($$), (>>==))
+import qualified Data.Enumerator as E
+import qualified Data.Enumerator.List as EL
+import qualified Data.Enumerator.Binary as EB
+import Blaze.ByteString.Builder.Enumerator (builderToByteString)
+import Blaze.ByteString.Builder.HTTP
+    (chunkedTransferEncoding, chunkedTransferTerminator)
+import Blaze.ByteString.Builder
+    (copyByteString, Builder, toLazyByteString, toByteStringIO)
+import Blaze.ByteString.Builder.Char8 (fromChar, fromShow)
+import Data.Monoid (mappend, mconcat)
+import Network.Socket.SendFile (sendFile)
+import Network.Socket.Enumerator (iterSocket)
+
+import Control.Monad.IO.Class (liftIO)
+import System.Timeout (timeout)
+import Data.Word (Word8)
+import Data.List (foldl')
+
+run :: Port -> Application -> IO ()
+run port = withSocketsDo . -- FIXME should this be called by client user instead?
+    bracket
+        (listenOn $ PortNumber $ fromIntegral port)
+        sClose .
+        serveConnections port
+type Port = Int
+
+serveConnections :: Port -> Application -> Socket -> IO ()
+serveConnections port app socket = do
+    (conn, sa) <- accept socket
+    _ <- forkIO $ serveConnection port app conn sa
+    serveConnections port app socket
+
+serveConnection :: Port -> Application -> Socket -> SockAddr -> IO ()
+serveConnection port app conn remoteHost' = do
+    catch
+        (finally
+          (E.run_ $ fromClient $$ serveConnection')
+          (sClose conn))
+        ignoreAll
+  where
+    ignoreAll :: SomeException -> IO ()
+    ignoreAll _ = return ()
+    fromClient = enumSocket bytesPerRead conn
+    serveConnection' = do
+        (enumeratee, env) <- parseRequest port remoteHost'
+        res <- E.joinI $ enumeratee $$ app env
+        keepAlive <- liftIO $ sendResponse env (httpVersion env) conn res
+        if keepAlive then serveConnection' else return ()
+
+parseRequest :: Port -> SockAddr -> E.Iteratee S.ByteString IO (E.Enumeratee ByteString ByteString IO a, Request)
+parseRequest port remoteHost' = do
+    headers' <- takeHeaders
+    parseRequest' port headers' remoteHost'
+
+-- FIXME come up with good values here
+maxHeaders, maxHeaderLength, bytesPerRead, readTimeout :: Int
+maxHeaders = 30
+maxHeaderLength = 1024
+bytesPerRead = 4096
+readTimeout = 3000000
+
+data InvalidRequest =
+    NotEnoughLines [String]
+    | BadFirstLine String
+    | NonHttp
+    | TooManyHeaders
+    | IncompleteHeaders
+    | OverLargeHeader
+    | SocketTimeout
+    deriving (Show, Typeable, Eq)
+instance Exception InvalidRequest
+
+-- | Parse a set of header lines and body into a 'Request'.
+parseRequest' :: Port
+              -> [ByteString]
+              -> SockAddr
+              -> E.Iteratee S.ByteString IO (E.Enumeratee S.ByteString S.ByteString IO a, Request)
+parseRequest' _ [] _ = E.throwError $ NotEnoughLines []
+parseRequest' port (firstLine:otherLines) remoteHost' = do
+    (method, rpath', gets, httpversion) <- parseFirst firstLine
+    let rpath =
+            if S.null rpath'
+                then "/"
+                else rpath'
+    let heads = map parseHeaderNoAttr otherLines
+    let host = fromMaybe "" $ lookup "host" heads
+    let len =
+            case lookup "content-length" heads of
+                Nothing -> 0
+                Just bs ->
+                    case reads $ B.unpack bs of -- FIXME could probably be optimized
+                        (x, _):_ -> x
+                        [] -> 0
+    let serverName' = takeUntil 58 host -- ':'
+    -- FIXME isolate takes an Integer instead of Int or Int64. If this is a
+    -- performance penalty, we may need our own version.
+    return (EB.isolate len, Request
+                { requestMethod = method
+                , httpVersion = httpversion
+                , pathInfo = rpath
+                , queryString = gets
+                , serverName = serverName'
+                , serverPort = port
+                , requestHeaders = heads
+                , isSecure = False
+                , errorHandler = System.IO.hPutStr System.IO.stderr
+                , remoteHost = remoteHost'
+                })
+
+
+takeUntil :: Word8 -> ByteString -> ByteString
+takeUntil c bs =
+    case S.elemIndex c bs of
+       Just !idx -> SU.unsafeTake idx bs
+       Nothing -> bs
+{-# INLINE takeUntil #-}
+
+parseFirst :: ByteString
+           -> E.Iteratee S.ByteString IO (ByteString, ByteString, ByteString, HttpVersion)
+parseFirst s = do
+    let pieces = S.split 32 s  -- ' '
+    (method, query, http') <-
+        case pieces of
+            [x, y, z] -> return (x, y, z)
+            _ -> E.throwError $ BadFirstLine $ B.unpack s
+    let (hfirst, hsecond) = B.splitAt 5 http'
+    if (hfirst == "HTTP/")
+        then
+            let (rpath, qstring) = B.break (== '?') query
+             in return (method, rpath, qstring, hsecond)
+        else E.throwError NonHttp
+{-# INLINE parseFirst #-} -- FIXME is this inline necessary? the function is only called from one place and not exported
+
+httpBuilder, spaceBuilder, newlineBuilder, transferEncodingBuilder
+           , colonSpaceBuilder :: Builder
+httpBuilder = copyByteString "HTTP/"
+spaceBuilder = fromChar ' '
+newlineBuilder = copyByteString "\r\n"
+transferEncodingBuilder = copyByteString "Transfer-Encoding: chunked\r\n\r\n"
+colonSpaceBuilder = copyByteString ": "
+
+headers :: HttpVersion -> Status -> ResponseHeaders -> Bool -> Builder
+headers !httpversion !status !responseHeaders !isChunked' = {-# SCC "headers" #-}
+    let !start = httpBuilder
+                `mappend` copyByteString httpversion
+                `mappend` spaceBuilder
+                `mappend` fromShow (statusCode status)
+                `mappend` spaceBuilder
+                `mappend` copyByteString (statusMessage status)
+                `mappend` newlineBuilder
+        !start' = foldl' responseHeaderToBuilder start responseHeaders
+        !end = if isChunked'
+                 then transferEncodingBuilder
+                 else newlineBuilder
+    in start' `mappend` end
+
+responseHeaderToBuilder :: Builder -> (CIByteString, ByteString) -> Builder
+responseHeaderToBuilder b (x, y) = b
+  `mappend` (copyByteString $ ciOriginal x)
+  `mappend` colonSpaceBuilder
+  `mappend` copyByteString y
+  `mappend` newlineBuilder
+
+isChunked :: HttpVersion -> Bool
+isChunked = (==) http11
+
+hasBody :: Status -> Request -> Bool
+hasBody s req = s /= (Status 204 "") && requestMethod req /= "HEAD"
+
+sendResponse :: Request -> HttpVersion -> Socket -> Response -> IO Bool
+sendResponse req hv socket (ResponseFile s hs fp) = do
+    Sock.sendMany socket $ L.toChunks $ toLazyByteString $ headers hv s hs False
+    if hasBody s req
+        then do
+            sendFile socket fp
+            return $ lookup "content-length" hs /= Nothing
+        else return True
+sendResponse req hv socket (ResponseBuilder s hs b)
+    | hasBody s req = do
+          toByteStringIO (Sock.sendAll socket) b'
+          return isKeepAlive
+    | otherwise = do
+        Sock.sendMany socket
+            $ L.toChunks
+            $ toLazyByteString
+            $ headers hv s hs False
+        return True
+  where
+    headers' = headers hv s hs isChunked'
+    b' =
+        if isChunked'
+            then headers'
+                 `mappend` chunkedTransferEncoding b
+                 `mappend` chunkedTransferTerminator
+            else headers hv s hs False `mappend` b
+    hasLength = lookup "content-length" hs /= Nothing
+    isChunked' = isChunked hv && not hasLength
+    isKeepAlive = isChunked' || hasLength
+sendResponse req hv socket (ResponseEnumerator res) =
+    res go
+  where
+    -- FIXME perhaps alloca a buffer per thread and reuse that in all functiosn below. Should lessen greatly the GC burden (I hope)
+    go s hs
+        | not (hasBody s req) = do
+            liftIO $ Sock.sendMany socket
+                   $ L.toChunks $ toLazyByteString
+                   $ headers hv s hs False
+            return True
+    go s hs =
+            chunk'
+          $ E.enumList 1 [headers hv s hs isChunked']
+         $$ E.joinI $ builderToByteString -- FIXME unsafeBuilderToByteString
+         $$ (iterSocket socket >> return isKeepAlive)
+      where
+        hasLength = lookup "content-length" hs /= Nothing
+        isChunked' = isChunked hv && not hasLength
+        isKeepAlive = isChunked' || hasLength
+        chunk' i =
+            if isChunked'
+                then E.joinI $ chunk $$ i
+                else i
+        chunk :: E.Enumeratee Builder Builder IO Bool
+        chunk = E.checkDone $ E.continue . step
+        step k E.EOF = k (E.Chunks [chunkedTransferTerminator]) >>== return
+        step k (E.Chunks []) = E.continue $ step k
+        step k (E.Chunks [x]) = k (E.Chunks [chunkedTransferEncoding x]) >>== chunk
+        step k (E.Chunks xs) = k (E.Chunks [chunkedTransferEncoding $ mconcat xs]) >>== chunk
+
+parseHeaderNoAttr :: ByteString -> (CIByteString, ByteString)
+parseHeaderNoAttr s =
+    let (k, rest) = S.breakByte 58 s -- ':'
+        restLen = S.length rest
+        -- FIXME check for colon without following space?
+        rest' = if restLen > 1 && SU.unsafeTake 2 rest == ": "
+                    then SU.unsafeDrop 2 rest
+                    else rest
+     in (mkCIByteString k, rest')
+
+-- FIXME when we switch to Jeremy's timeout code, we can probably start using
+-- network-enumerator's enumSocket
+enumSocket :: Int -> Socket -> E.Enumerator ByteString IO a
+enumSocket len socket (E.Continue k) = do
+#if NO_TIMEOUT_PROTECTION
+    bs <- liftIO $ Sock.recv socket len
+    go bs
+#else
+    mbs <- liftIO $ timeout readTimeout $ Sock.recv socket len
+    case mbs of
+        Nothing -> E.throwError SocketTimeout
+        Just bs -> go bs
+#endif
+  where
+    go bs
+        | S.length bs == 0 = E.continue k
+        | otherwise = k (E.Chunks [bs]) >>== enumSocket len socket
+enumSocket _ _ step = E.returnI step
+
+------ The functions below are not warp-specific and could be split out into a
+--separate package.
+
+takeHeaders :: E.Iteratee ByteString IO [ByteString]
+takeHeaders = takeUntilBlank 0 id
+
+takeUntilBlank :: Int
+               -> ([ByteString] -> [ByteString])
+               -> E.Iteratee S.ByteString IO [ByteString]
+takeUntilBlank count _
+    | count > maxHeaders = E.throwError TooManyHeaders
+takeUntilBlank count front = do
+    l <- takeLineMax 0 id
+    if B.null l
+        then return $ front []
+        else takeUntilBlank (count + 1) $ front . (:) l
+
+takeLineMax :: Int
+            -> ([ByteString] -> [ByteString])
+            -> E.Iteratee ByteString IO ByteString
+takeLineMax len front = do
+    mbs <- EL.head
+    case mbs of
+        Nothing -> E.throwError IncompleteHeaders
+        Just bs -> do
+            let (x, y) = S.breakByte 10 bs
+                x' = if S.length x > 0 && S.last x == 13
+                        then S.init x
+                        else x
+            let len' = len + B.length x
+            case () of
+                ()
+                    | len' > maxHeaderLength -> E.throwError OverLargeHeader
+                    | B.null y -> takeLineMax len' $ front . (:) x
+                    | otherwise -> do
+                        E.yield () $ E.Chunks [B.drop 1 y]
+                        return $ B.concat $ front [x']
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,7 @@
+#!/usr/bin/env runhaskell
+
+> module Main where
+> import Distribution.Simple
+
+> main :: IO ()
+> main = defaultMain
diff --git a/warp.cabal b/warp.cabal
new file mode 100644
--- /dev/null
+++ b/warp.cabal
@@ -0,0 +1,41 @@
+Name:                warp
+Version:             0.3.0
+Synopsis:            A fast, light-weight web server for WAI applications.
+License:             BSD3
+License-file:        LICENSE
+Author:              Michael Snoyman, Matt Brown
+Maintainer:          michael@snoyman.com
+Homepage:            http://github.com/snoyberg/warp
+Category:            Web, Yesod
+Build-Type:          Simple
+Cabal-Version:       >=1.6
+Stability:           Stable
+
+flag timeout-protection
+    Description: Use timeouts (very performance-costly) to protect against DOS attacks.
+    Default: True
+flag network-bytestring
+
+Library
+  Build-Depends:     base                          >= 3        && < 5
+                   , bytestring                    >= 0.9      && < 0.10
+                   , wai                           >= 0.3.0    && < 0.4
+                   , blaze-builder-enumerator      >= 0.2      && < 0.3
+                   , transformers                  >= 0.2      && < 0.3
+                   , enumerator                    >= 0.4.5    && < 0.5
+                   , blaze-builder                 >= 0.2.1.4  && < 0.3
+                   , sendfile                      >= 0.7.2    && < 0.8
+                   , network-enumerator            >= 0.1.1    && < 0.2
+  if flag(network-bytestring)
+      build-depends: network               >= 2.2.1   && < 2.2.3
+                   , network-bytestring    >= 0.1.3   && < 0.1.4
+  else
+      build-depends: network               >= 2.3     && < 2.4
+  Exposed-modules:   Network.Wai.Handler.Warp
+  ghc-options:       -Wall
+  if !flag(timeout-protection)
+      Cpp-options: -DNO_TIMEOUT_PROTECTION
+
+source-repository head
+  type:     git
+  location: git://github.com/snoyberg/warp.git
