diff --git a/snap-core.cabal b/snap-core.cabal
--- a/snap-core.cabal
+++ b/snap-core.cabal
@@ -1,5 +1,5 @@
 name:           snap-core
-version:        0.4.3
+version:        0.5.0
 synopsis:       Snap: A Haskell Web Framework (Core)
 
 description:
@@ -108,7 +108,6 @@
     build-depends: bytestring-mmap >= 0.2.2 && <0.3
 
   exposed-modules:
-    Data.CIByteString,
     Snap.Types,
     Snap.Iteratee,
     Snap.Internal.Debug,
@@ -117,7 +116,8 @@
     Snap.Internal.Parsing,
     Snap.Util.FileServe,
     Snap.Util.FileUploads,
-    Snap.Util.GZip
+    Snap.Util.GZip,
+    Snap.Util.Readable
 
   other-modules:
     Snap.Internal.Instances,
@@ -133,6 +133,7 @@
     blaze-builder >= 0.2.1.4 && <0.4,
     bytestring,
     bytestring-nums,
+    case-insensitive >= 0.2 && < 0.3,
     containers,
     deepseq >= 1.1 && <1.2,
     directory,
diff --git a/src/Data/CIByteString.hs b/src/Data/CIByteString.hs
deleted file mode 100644
--- a/src/Data/CIByteString.hs
+++ /dev/null
@@ -1,79 +0,0 @@
-{-# LANGUAGE BangPatterns #-}
-{-# LANGUAGE OverloadedStrings #-}
-
-------------------------------------------------------------------------------
--- | "Data.CIByteString" is a module containing 'CIByteString', a wrapper for
--- 'ByteString' which provides case-insensitive (ASCII-wise) 'Ord' and 'Eq'
--- instances.
---
--- 'CIByteString' also has an 'IsString' instance, so if you use the
--- \"OverloadedStrings\" LANGUAGE pragma you can write case-insensitive string
--- literals, e.g.:
---
--- @
--- \> let a = \"Foo\" in
---   putStrLn $ (show $ unCI a) ++ \"==\\\"FoO\\\" is \" ++
---              show (a == \"FoO\")
--- \"Foo\"==\"FoO\" is True
--- @
-
-module Data.CIByteString
- ( CIByteString
- , toCI
- , unCI
- , ciToLower
- ) where
-
-
-------------------------------------------------------------------------------
--- for IsString instance
-import           Data.ByteString.Char8 ()
-import           Data.ByteString (ByteString)
-import           Data.ByteString.Internal (c2w, w2c)
-import qualified Data.ByteString as S
-import           Data.Char
-import           Data.String
-
-
-------------------------------------------------------------------------------
--- | A case-insensitive newtype wrapper for 'ByteString'
-data CIByteString = CIByteString { unCI        :: !ByteString
-                                 , _lowercased :: !ByteString }
-
-
-------------------------------------------------------------------------------
-toCI :: ByteString -> CIByteString
-toCI s = CIByteString s t
-  where
-    t = lowercase s
-
-
-------------------------------------------------------------------------------
-ciToLower :: CIByteString -> ByteString
-ciToLower = _lowercased
-
-
-------------------------------------------------------------------------------
-instance Show CIByteString where
-    show (CIByteString s _) = show s
-
-
-------------------------------------------------------------------------------
-lowercase :: ByteString -> ByteString
-lowercase = S.map (c2w . toLower . w2c)
-
-
-------------------------------------------------------------------------------
-instance Eq CIByteString where
-    (CIByteString _ a) == (CIByteString _ b) = a == b
-    (CIByteString _ a) /= (CIByteString _ b) = a /= b
-
-
-------------------------------------------------------------------------------
-instance Ord CIByteString where
-    (CIByteString _ a) <= (CIByteString _ b) = a <= b
-
-
-------------------------------------------------------------------------------
-instance IsString CIByteString where
-    fromString = toCI . fromString
diff --git a/src/Snap/Internal/Http/Types.hs b/src/Snap/Internal/Http/Types.hs
--- a/src/Snap/Internal/Http/Types.hs
+++ b/src/Snap/Internal/Http/Types.hs
@@ -57,7 +57,8 @@
 #endif
 
 ------------------------------------------------------------------------------
-import           Data.CIByteString
+import           Data.CaseInsensitive   (CI)
+import qualified Data.CaseInsensitive as CI
 import           Snap.Iteratee (Enumerator)
 import qualified Snap.Iteratee as I
 
@@ -84,7 +85,7 @@
 
 ------------------------------------------------------------------------------
 -- | A type alias for a case-insensitive key-value mapping.
-type Headers = Map CIByteString [ByteString]
+type Headers = Map (CI ByteString) [ByteString]
 
 
 ------------------------------------------------------------------------------
@@ -102,33 +103,33 @@
 -- | Adds a header key-value-pair to the 'HasHeaders' datatype. If a header
 -- with the same name already exists, the new value is appended to the headers
 -- list.
-addHeader :: (HasHeaders a) => CIByteString -> ByteString -> a -> a
+addHeader :: (HasHeaders a) => CI ByteString -> ByteString -> a -> a
 addHeader k v = updateHeaders $ Map.insertWith' (++) k [v]
 
 
 ------------------------------------------------------------------------------
 -- | Sets a header key-value-pair in a 'HasHeaders' datatype. If a header with
 -- the same name already exists, it is overwritten with the new value.
-setHeader :: (HasHeaders a) => CIByteString -> ByteString -> a -> a
+setHeader :: (HasHeaders a) => CI ByteString -> ByteString -> a -> a
 setHeader k v = updateHeaders $ Map.insert k [v]
 
 
 ------------------------------------------------------------------------------
 -- | Gets all of the values for a given header.
-getHeaders :: (HasHeaders a) => CIByteString -> a -> Maybe [ByteString]
+getHeaders :: (HasHeaders a) => CI ByteString -> a -> Maybe [ByteString]
 getHeaders k a = Map.lookup k $ headers a
 
 
 ------------------------------------------------------------------------------
 -- | Gets a header value out of a 'HasHeaders' datatype. If many headers came
 -- in with the same name, they will be catenated together.
-getHeader :: (HasHeaders a) => CIByteString -> a -> Maybe ByteString
+getHeader :: (HasHeaders a) => CI ByteString -> a -> Maybe ByteString
 getHeader k a = liftM (S.intercalate " ") (Map.lookup k $ headers a)
 
 
 ------------------------------------------------------------------------------
 -- | Clears a header value from a 'HasHeaders' datatype.
-deleteHeader :: (HasHeaders a) => CIByteString -> a -> a
+deleteHeader :: (HasHeaders a) => CI ByteString -> a -> a
 deleteHeader k = updateHeaders $ Map.delete k
 
 
@@ -303,7 +304,7 @@
       beginheaders  =
           "Headers:\n      ========================================"
       endheaders    = "  ========================================"
-      hdrs' (a,b)   = (B.unpack $ unCI a) ++ ": " ++ (show (map B.unpack b))
+      hdrs' (a,b)   = (B.unpack $ CI.original a) ++ ": " ++ (show (map B.unpack b))
       hdrs          = "      " ++ (concat $ intersperse "\n " $
                                    map hdrs' (Map.toAscList $ rqHeaders r))
       contentlength = concat [ "content-length: "
diff --git a/src/Snap/Internal/Parsing.hs b/src/Snap/Internal/Parsing.hs
--- a/src/Snap/Internal/Parsing.hs
+++ b/src/Snap/Internal/Parsing.hs
@@ -6,7 +6,8 @@
 import           Data.ByteString.Char8 (ByteString)
 import qualified Data.ByteString.Char8 as S
 import qualified Data.ByteString.Lazy.Char8 as L
-import           Data.CIByteString
+import qualified Data.CaseInsensitive as CI
+import           Data.CaseInsensitive (CI)
 import           Data.Char (isAlpha, isAscii, isControl)
 import           Control.Applicative
 import           Control.Monad
@@ -185,21 +186,21 @@
 
 
 ------------------------------------------------------------------------------
-pValueWithParameters :: Parser (ByteString, [(CIByteString, ByteString)])
+pValueWithParameters :: Parser (ByteString, [(CI ByteString, ByteString)])
 pValueWithParameters = do
     value  <- liftM trim (pSpaces *> takeWhile (/= ';'))
     params <- many pParam
-    return (value, map (first toCI) params)
+    return (value, map (first CI.mk) params)
   where
     pParam = pSpaces *> char ';' *> pSpaces *> pParameter
 
 ------------------------------------------------------------------------------
 pContentTypeWithParameters ::
-    Parser (ByteString, [(CIByteString, ByteString)])
+    Parser (ByteString, [(CI ByteString, ByteString)])
 pContentTypeWithParameters = do
     value  <- liftM trim (pSpaces *> takeWhile (not . isSep))
     params <- many (pSpaces *> satisfy isSep *> pSpaces *> pParameter)
-    return (value, map (first toCI) params)
+    return (value, map (first CI.mk) params)
   where
     isSep c = c == ';' || c == ','
 
diff --git a/src/Snap/Internal/Types.hs b/src/Snap/Internal/Types.hs
--- a/src/Snap/Internal/Types.hs
+++ b/src/Snap/Internal/Types.hs
@@ -19,7 +19,7 @@
 import           Data.ByteString.Char8 (ByteString)
 import qualified Data.ByteString.Char8 as S
 import qualified Data.ByteString.Lazy.Char8 as L
-import qualified Data.CIByteString as CIB
+import           Data.CaseInsensitive (CI) 
 import           Data.Int
 import           Data.IORef
 import           Data.Maybe
@@ -32,8 +32,9 @@
 
 ------------------------------------------------------------------
 import           Snap.Internal.Http.Types
-import           Snap.Iteratee
 import           Snap.Internal.Iteratee.Debug
+import           Snap.Util.Readable
+import           Snap.Iteratee
 
 
 ------------------------------------------------------------------------------
@@ -422,6 +423,19 @@
 
 
 ------------------------------------------------------------------------------
+-- | Runs a 'Snap' monad action only when the first path component is
+-- successfully parsed as the argument to the supplied handler function.
+pathArg :: (Readable a, MonadSnap m)
+        => (a -> m b)
+        -> m b
+pathArg f = do
+    req <- getRequest
+    let (p,_) = S.break (=='/') (rqPathInfo req)
+    a <- fromBS p
+    localRequest (updateContextPath $ S.length p) (f a)
+    
+
+------------------------------------------------------------------------------
 -- | Runs a 'Snap' monad action only when 'rqPathInfo' is empty.
 ifTop :: MonadSnap m => m a -> m a
 ifTop = path ""
@@ -677,7 +691,7 @@
 -- address can get it in a uniform manner. It has specifically limited
 -- functionality to ensure that its transformation can be trusted,
 -- when used correctly.
-ipHeaderFilter' :: MonadSnap m => CIB.CIByteString -> m ()
+ipHeaderFilter' :: MonadSnap m => CI ByteString -> m ()
 ipHeaderFilter' header = do
     headerContents <- getHeader header <$> getRequest
 
@@ -814,6 +828,15 @@
           -> m (Maybe Cookie)
 getCookie name = withRequest $
     return . listToMaybe . filter (\c -> cookieName c == name) . rqCookies
+
+
+------------------------------------------------------------------------------
+-- | Gets the HTTP 'Cookie' with the specified name and decodes it.  If the
+-- decoding fails, the handler calls pass.
+readCookie :: (MonadSnap m, Readable a)
+           => ByteString
+           -> m a
+readCookie name = maybe pass (fromBS . cookieValue) =<< getCookie name
 
 
 ------------------------------------------------------------------------------
diff --git a/src/Snap/Types.hs b/src/Snap/Types.hs
--- a/src/Snap/Types.hs
+++ b/src/Snap/Types.hs
@@ -22,6 +22,7 @@
   , method
   , methods
   , path
+  , pathArg
   , dir
   , ifTop
   , route
@@ -103,6 +104,7 @@
   , deleteResponseCookie
   , modifyResponseCookie
   , getCookie
+  , readCookie
   , setContentLength
   , clearContentLength
   , redirect
diff --git a/src/Snap/Util/FileUploads.hs b/src/Snap/Util/FileUploads.hs
--- a/src/Snap/Util/FileUploads.hs
+++ b/src/Snap/Util/FileUploads.hs
@@ -72,10 +72,10 @@
 import qualified Data.Attoparsec.Char8 as Atto
 import           Data.Attoparsec.Char8 hiding (many, Result(..))
 import           Data.Attoparsec.Enumerator
-import           Data.CIByteString
 import qualified Data.ByteString.Char8 as S
 import           Data.ByteString.Char8 (ByteString)
 import           Data.ByteString.Internal (c2w)
+import qualified Data.CaseInsensitive as CI
 import qualified Data.DList as D
 import           Data.Enumerator.Binary (iterHandle)
 import           Data.IORef
@@ -789,7 +789,7 @@
 toHeaders :: [(ByteString,ByteString)] -> Headers
 toHeaders kvps = foldl' f Map.empty kvps'
   where
-    kvps'     = map (first toCI . second (:[])) kvps
+    kvps'     = map (first CI.mk . second (:[])) kvps
     f m (k,v) = Map.insertWith' (flip (++)) k v m
 
 
diff --git a/src/Snap/Util/Readable.hs b/src/Snap/Util/Readable.hs
new file mode 100644
--- /dev/null
+++ b/src/Snap/Util/Readable.hs
@@ -0,0 +1,36 @@
+module Snap.Util.Readable
+  ( Readable(..)
+  ) where
+
+------------------------------------------------------------------------------
+import           Data.ByteString.Char8 (ByteString)
+import           Data.Text (Text)
+import qualified Data.Text as T
+import           Data.Text.Encoding
+import           Data.Text.Read
+
+
+------------------------------------------------------------------------------
+-- | Runs a 'Snap' monad action only when 'rqPathInfo' is empty.
+class Readable a where
+    fromBS :: Monad m => ByteString -> m a
+
+
+------------------------------------------------------------------------------
+-- | Fails if the input wasn't parsed completely.
+checkComplete :: Monad m => (t, Text) -> m t
+checkComplete (a,rest)
+  | T.null rest = return a
+  | otherwise   = fail "Readable: could not parse completely"
+
+
+instance Readable ByteString where
+    fromBS = return
+instance Readable Text where
+    fromBS = return . decodeUtf8
+instance Readable Int where
+    fromBS = either fail checkComplete . decimal . decodeUtf8
+instance Readable Integer where
+    fromBS = either fail checkComplete . decimal . decodeUtf8
+instance Readable Double where
+    fromBS = either fail checkComplete . double . decodeUtf8
diff --git a/test/runTestsAndCoverage.sh b/test/runTestsAndCoverage.sh
--- a/test/runTestsAndCoverage.sh
+++ b/test/runTestsAndCoverage.sh
@@ -31,7 +31,6 @@
 mkdir -p $DIR
 
 EXCLUDES='Main
-Data.CIByteString
 Snap.Internal.Debug
 Snap.Internal.Iteratee.Debug
 Snap.Iteratee.Tests
diff --git a/test/snap-core-testsuite.cabal b/test/snap-core-testsuite.cabal
--- a/test/snap-core-testsuite.cabal
+++ b/test/snap-core-testsuite.cabal
@@ -28,6 +28,7 @@
     blaze-builder >= 0.2.1.4 && <0.4,
     bytestring,
     bytestring-nums,
+    case-insensitive >= 0.2 && < 0.3,
     cereal == 0.3.*,
     containers,
     deepseq >= 1.1 && <1.2,
