diff --git a/Data/ByteString/Base64.hs b/Data/ByteString/Base64.hs
--- a/Data/ByteString/Base64.hs
+++ b/Data/ByteString/Base64.hs
@@ -19,226 +19,35 @@
     , joinWith
     ) where
 
-import Data.Bits ((.|.), (.&.), shiftL, shiftR)
+import Data.ByteString.Base64.Internal
 import qualified Data.ByteString as B
-import Data.ByteString.Internal (ByteString(..), mallocByteString, memcpy,
-                                 unsafeCreate)
-import Data.Word (Word8, Word16, Word32)
-import Foreign.ForeignPtr (ForeignPtr, withForeignPtr, castForeignPtr)
-import Foreign.Ptr (Ptr, castPtr, minusPtr, plusPtr)
-import Foreign.Storable (peek, peekElemOff, poke)
-import System.IO.Unsafe (unsafePerformIO)
-
-peek8 :: Ptr Word8 -> IO Word8
-peek8 = peek
-
-poke8 :: Ptr Word8 -> Word8 -> IO ()
-poke8 = poke
-
-peek8_32 :: Ptr Word8 -> IO Word32
-peek8_32 = fmap fromIntegral . peek8
+import Data.ByteString.Internal (ByteString(..))
+import Data.Word (Word8)
+import Foreign.ForeignPtr (ForeignPtr)
 
 -- | Encode a string into base64 form.  The result will always be a
 -- multiple of 4 bytes in length.
 encode :: ByteString -> ByteString
-encode (PS sfp soff slen)
-    | slen > maxBound `div` 4 =
-        error "Data.ByteString.Base64.encode: input too long"
-    | otherwise = unsafePerformIO $ do
-  let dlen = ((slen + 2) `div` 3) * 4
-  dfp <- mallocByteString dlen
-  withForeignPtr alfaFP $ \aptr ->
-    withForeignPtr encodeTable $ \ep ->
-      withForeignPtr sfp $ \sptr -> do
-        let aidx n = peek8 (aptr `plusPtr` n)
-            sEnd = sptr `plusPtr` (slen + soff)
-            fill !dp !sp
-              | sp `plusPtr` 2 >= sEnd = complete (castPtr dp) sp
-              | otherwise = {-# SCC "encode/fill" #-} do
-              i <- peek8_32 sp
-              j <- peek8_32 (sp `plusPtr` 1)
-              k <- peek8_32 (sp `plusPtr` 2)
-              let w = (i `shiftL` 16) .|. (j `shiftL` 8) .|. k
-                  enc = peekElemOff ep . fromIntegral
-              poke dp =<< enc (w `shiftR` 12)
-              poke (dp `plusPtr` 2) =<< enc (w .&. 0xfff)
-              fill (dp `plusPtr` 4) (sp `plusPtr` 3)
-            complete dp sp
-                | sp == sEnd = return ()
-                | otherwise  = {-# SCC "encode/complete" #-} do
-              let peekSP n f = (f . fromIntegral) `fmap` peek8 (sp `plusPtr` n)
-                  twoMore    = sp `plusPtr` 2 == sEnd
-                  equals     = 0x3d :: Word8
-                  {-# INLINE equals #-}
-              a <- peekSP 0 ((`shiftR` 2) . (.&. 0xfc))
-              b <- peekSP 0 ((`shiftL` 4) . (.&. 0x03))
-              !b' <- if twoMore
-                     then peekSP 1 ((.|. b) . (`shiftR` 4) . (.&. 0xf0))
-                     else return b
-              poke8 dp =<< aidx a
-              poke8 (dp `plusPtr` 1) =<< aidx b'
-              c <- if twoMore
-                   then aidx =<< peekSP 1 ((`shiftL` 2) . (.&. 0x0f))
-                   else return equals
-              poke8 (dp `plusPtr` 2) c
-              poke8 (dp `plusPtr` 3) equals
-        withForeignPtr dfp $ \dptr ->
-          fill (castPtr dptr) (sptr `plusPtr` soff)
-  return $! PS dfp 0 dlen
-
--- | Efficiently intersperse a terminator string into another at
--- regular intervals, and terminate the input with it.
---
--- Examples:
---
--- > joinWith "|" 2 "----" = "--|--|"
---
--- > joinWith "\r\n" 3 "foobarbaz" = "foo\r\nbar\r\nbaz\r\n"
-joinWith :: ByteString  -- ^ String to intersperse and end with
-         -> Int         -- ^ Interval at which to intersperse, in bytes
-         -> ByteString  -- ^ String to transform
-         -> ByteString
-joinWith brk@(PS bfp boff blen) every bs@(PS sfp soff slen)
-    | every <= 0 = error "invalid interval"
-    | blen <= 0  = bs
-    | B.null bs = brk
-    | otherwise =
-  unsafeCreate dlen $ \dptr ->
-    withForeignPtr bfp $ \bptr -> do
-      withForeignPtr sfp $ \sptr -> do
-          let bp = bptr `plusPtr` boff
-          let dEnd = dptr `plusPtr` dlen
-              loop !dp !sp | dp >= dEnd = return ()
-                           | otherwise = do
-                let n = min every (dEnd `minusPtr` dp)
-                memcpy dp sp (fromIntegral n)
-                memcpy (dp `plusPtr` n) bp (fromIntegral blen)
-                loop (dp `plusPtr` (n + blen)) (sp `plusPtr` every)
-          loop dptr (sptr `plusPtr` soff)
-  where dlen = slen + blen * numBreaks
-        numBreaks = slen `div` every
+encode = encodeWithAlphabet alphabet
 
 -- | Decode a base64-encoded string.  This function strictly follows
 -- the specification in RFC 4648,
 -- <http://www.apps.ietf.org/rfc/rfc4648.html>.
 decode :: ByteString -> Either String ByteString
-decode (PS sfp soff slen)
-    | drem /= 0 = Left "invalid padding"
-    | dlen <= 0 = Right B.empty
-    | otherwise = unsafePerformIO $ do
-  dfp <- mallocByteString dlen
-  withForeignPtr decodeFP $ \decptr -> do
-    let finish dbytes = return . Right $! if dbytes > 0
-                                          then PS dfp 0 dbytes
-                                          else B.empty
-        bail = return . Left
-    withForeignPtr sfp $ \sptr -> do
-      let sEnd = sptr `plusPtr` (slen + soff)
-          look p = do
-            ix <- fromIntegral `fmap` peek8 p
-            v <- peek8 (decptr `plusPtr` ix)
-            return $! fromIntegral v :: IO Word32
-          fill !dp !sp !n
-            | sp >= sEnd = finish n
-            | otherwise = {-# SCC "decode/fill" #-} do
-            a <- look sp
-            b <- look (sp `plusPtr` 1)
-            c <- look (sp `plusPtr` 2)
-            d <- look (sp `plusPtr` 3)
-            let w = (a `shiftL` 18) .|. (b `shiftL` 12) .|.
-                    (c `shiftL` 6) .|. d
-            if a == done || b == done
-              then bail $ "invalid padding near offset " ++
-                   show (sp `minusPtr` sptr)
-              else if a .|. b .|. c .|. d == x
-              then bail $ "invalid base64 encoding near offset " ++
-                   show (sp `minusPtr` sptr)
-              else do
-                poke8 dp $ fromIntegral (w `shiftR` 16)
-                if c == done
-                  then finish $ n + 1
-                  else do
-                    poke8 (dp `plusPtr` 1) $ fromIntegral (w `shiftR` 8)
-                    if d == done
-                      then finish $! n + 2
-                      else do
-                        poke8 (dp `plusPtr` 2) $ fromIntegral w
-                        fill (dp `plusPtr` 3) (sp `plusPtr` 4) (n+3)
-      withForeignPtr dfp $ \dptr ->
-        fill dptr (sptr `plusPtr` soff) 0
-  where (di,drem) = slen `divMod` 4
-        dlen = di * 3
-
-data D = D { dNext :: {-# UNPACK #-} !(Ptr Word8)
-           , dValue :: {-# UNPACK #-} !Word32 }
+decode = decodeWithTable decodeFP
 
 -- | Decode a base64-encoded string.  This function is lenient in
 -- following the specification from RFC 4648,
 -- <http://www.apps.ietf.org/rfc/rfc4648.html>, and will not generate
 -- parse errors no matter how poor its input.
 decodeLenient :: ByteString -> ByteString
-decodeLenient (PS sfp soff slen)
-    | dlen <= 0 = B.empty
-    | otherwise = unsafePerformIO $ do
-  dfp <- mallocByteString dlen
-  dbytes <- withForeignPtr decodeFP $ \decptr ->
-    withForeignPtr sfp $ \sptr -> do
-      let sEnd = sptr `plusPtr` (slen + soff)
-          fill !dp !sp !n
-            | sp >= sEnd = return n
-            | otherwise = {-# SCC "decodeLenient/fill" #-} do
-            let look skipPad = go
-                  where
-                    go p | p >= sEnd = return $! D (sEnd `plusPtr` (-1)) done
-                         | otherwise =  {-# SCC "decodeLenient/look" #-} do
-                      ix <- fromIntegral `fmap` peek8 p
-                      v <- peek8 (decptr `plusPtr` ix)
-                      if v == x || (v == done && skipPad)
-                        then go (p `plusPtr` 1)
-                        else return $! D (p `plusPtr` 1) (fromIntegral v)
-            !a <- look True  sp
-            !b <- look True  (dNext a)
-            !c <- look False (dNext b)
-            !d <- look False (dNext c)
-            let w = (dValue a `shiftL` 18) .|. (dValue b `shiftL` 12) .|.
-                    (dValue c `shiftL` 6) .|. dValue d
-            if dValue a == done || dValue b == done
-              then return n
-              else do
-                poke8 dp $ fromIntegral (w `shiftR` 16)
-                if dValue c == done
-                  then return $! n + 1
-                  else do
-                    poke8 (dp `plusPtr` 1) $ fromIntegral (w `shiftR` 8)
-                    if dValue d == done
-                      then return $! n + 2
-                      else do
-                        poke8 (dp `plusPtr` 2) $ fromIntegral w
-                        fill (dp `plusPtr` 3) (dNext d) (n+3)
-      withForeignPtr dfp $ \dptr ->
-        fill dptr (sptr `plusPtr` soff) 0
-  return $! if dbytes > 0
-            then PS dfp 0 dbytes
-            else B.empty
-  where dlen = ((slen + 3) `div` 4) * 3
+decodeLenient = decodeLenientWithTable decodeFP
 
+
 alphabet :: ByteString
-alfaFP :: ForeignPtr Word8
-alphabet@(PS alfaFP _ _) = B.pack $ [65..90] ++ [97..122] ++ [48..57] ++ [43,47]
+alphabet = B.pack $ [65..90] ++ [97..122] ++ [48..57] ++ [43,47]
 {-# NOINLINE alphabet #-}
 
--- The encoding table is constructed such that the expansion of a 12bit block
--- to a 16bit block can be done by a single Word16 copy from the correspoding
--- table entry to the target address. The 16bit blocks are stored in big-endian
--- order, as the indices into the table are built in big-endian order.
-encodeTable :: ForeignPtr Word16
-encodeTable = 
-    case table of PS fp _ _ -> castForeignPtr fp
-  where
-    ix    = fromIntegral . B.index alphabet
-    table = B.pack $ concat $ [ [ix j, ix k] | j <- [0..63], k <- [0..63] ]
-{-# NOINLINE encodeTable #-}
-
 decodeFP :: ForeignPtr Word8
 PS decodeFP _ _ = B.pack $ replicate 43 x ++ [62,x,x,x,63] ++ [52..61] ++ [x,x,
   x,done,x,x,x] ++ [0..25] ++ [x,x,x,x,x,x] ++ [26..51] ++ replicate 133 x
@@ -247,7 +56,3 @@
 x :: Integral a => a
 x = 255
 {-# INLINE x #-}
-
-done :: Integral a => a
-done = 99
-{-# INLINE done #-}
diff --git a/Data/ByteString/Base64/Internal.hs b/Data/ByteString/Base64/Internal.hs
new file mode 100644
--- /dev/null
+++ b/Data/ByteString/Base64/Internal.hs
@@ -0,0 +1,249 @@
+{-# LANGUAGE BangPatterns #-}
+
+-- |
+-- Module      : Data.ByteString.Base64.Internal
+-- Copyright   : (c) 2010 Bryan O'Sullivan
+--
+-- License     : BSD-style
+-- Maintainer  : bos@serpentine.com
+-- Stability   : experimental
+-- Portability : GHC
+--
+-- Fast and efficient encoding and decoding of base64-encoded strings.
+
+module Data.ByteString.Base64.Internal
+    (
+      encodeWithAlphabet
+    , decodeWithTable
+    , decodeLenientWithTable
+    , joinWith
+    , done
+    , peek8, poke8, peek8_32
+    ) where
+
+import Data.Bits ((.|.), (.&.), shiftL, shiftR)
+import qualified Data.ByteString as B
+import Data.ByteString.Internal (ByteString(..), mallocByteString, memcpy,
+                                 unsafeCreate)
+import Data.Word (Word8, Word16, Word32)
+import Foreign.ForeignPtr (ForeignPtr, withForeignPtr, castForeignPtr)
+import Foreign.Ptr (Ptr, castPtr, minusPtr, plusPtr)
+import Foreign.Storable (peek, peekElemOff, poke)
+import System.IO.Unsafe (unsafePerformIO)
+
+peek8 :: Ptr Word8 -> IO Word8
+peek8 = peek
+
+poke8 :: Ptr Word8 -> Word8 -> IO ()
+poke8 = poke
+
+peek8_32 :: Ptr Word8 -> IO Word32
+peek8_32 = fmap fromIntegral . peek8
+
+-- | Encode a string into base64 form.  The result will always be a
+-- multiple of 4 bytes in length. This function takes the encoding
+-- alphabet (for @base64@ or @base64url@) as the first paramert.
+encodeWithAlphabet :: ByteString -> ByteString -> ByteString
+encodeWithAlphabet alphabet@(PS alfaFP _ _) (PS sfp soff slen)
+    | slen > maxBound `div` 4 =
+        error "Data.ByteString.Base64.encode: input too long"
+    | otherwise = unsafePerformIO $ do
+  let dlen = ((slen + 2) `div` 3) * 4
+  dfp <- mallocByteString dlen
+  withForeignPtr alfaFP $ \aptr ->
+    withForeignPtr encodeTable $ \ep ->
+      withForeignPtr sfp $ \sptr -> do
+        let aidx n = peek8 (aptr `plusPtr` n)
+            sEnd = sptr `plusPtr` (slen + soff)
+            fill !dp !sp
+              | sp `plusPtr` 2 >= sEnd = complete (castPtr dp) sp
+              | otherwise = {-# SCC "encode/fill" #-} do
+              i <- peek8_32 sp
+              j <- peek8_32 (sp `plusPtr` 1)
+              k <- peek8_32 (sp `plusPtr` 2)
+              let w = (i `shiftL` 16) .|. (j `shiftL` 8) .|. k
+                  enc = peekElemOff ep . fromIntegral
+              poke dp =<< enc (w `shiftR` 12)
+              poke (dp `plusPtr` 2) =<< enc (w .&. 0xfff)
+              fill (dp `plusPtr` 4) (sp `plusPtr` 3)
+            complete dp sp
+                | sp == sEnd = return ()
+                | otherwise  = {-# SCC "encode/complete" #-} do
+              let peekSP n f = (f . fromIntegral) `fmap` peek8 (sp `plusPtr` n)
+                  twoMore    = sp `plusPtr` 2 == sEnd
+                  equals     = 0x3d :: Word8
+                  {-# INLINE equals #-}
+              a <- peekSP 0 ((`shiftR` 2) . (.&. 0xfc))
+              b <- peekSP 0 ((`shiftL` 4) . (.&. 0x03))
+              !b' <- if twoMore
+                     then peekSP 1 ((.|. b) . (`shiftR` 4) . (.&. 0xf0))
+                     else return b
+              poke8 dp =<< aidx a
+              poke8 (dp `plusPtr` 1) =<< aidx b'
+              c <- if twoMore
+                   then aidx =<< peekSP 1 ((`shiftL` 2) . (.&. 0x0f))
+                   else return equals
+              poke8 (dp `plusPtr` 2) c
+              poke8 (dp `plusPtr` 3) equals
+        withForeignPtr dfp $ \dptr ->
+          fill (castPtr dptr) (sptr `plusPtr` soff)
+  return $! PS dfp 0 dlen
+  where -- The encoding table is constructed such that the expansion of a 12bit block
+        -- to a 16bit block can be done by a single Word16 copy from the correspoding
+        -- table entry to the target address. The 16bit blocks are stored in big-endian
+        -- order, as the indices into the table are built in big-endian order.
+        encodeTable :: ForeignPtr Word16
+        encodeTable = 
+            case table of PS fp _ _ -> castForeignPtr fp
+          where
+            ix    = fromIntegral . B.index alphabet
+            table = B.pack $ concat $ [ [ix j, ix k] | j <- [0..63], k <- [0..63] ]
+        {-# NOINLINE encodeTable #-}
+
+-- | Efficiently intersperse a terminator string into another at
+-- regular intervals, and terminate the input with it.
+--
+-- Examples:
+--
+-- > joinWith "|" 2 "----" = "--|--|"
+--
+-- > joinWith "\r\n" 3 "foobarbaz" = "foo\r\nbar\r\nbaz\r\n"
+joinWith :: ByteString  -- ^ String to intersperse and end with
+         -> Int         -- ^ Interval at which to intersperse, in bytes
+         -> ByteString  -- ^ String to transform
+         -> ByteString
+joinWith brk@(PS bfp boff blen) every bs@(PS sfp soff slen)
+    | every <= 0 = error "invalid interval"
+    | blen <= 0  = bs
+    | B.null bs = brk
+    | otherwise =
+  unsafeCreate dlen $ \dptr ->
+    withForeignPtr bfp $ \bptr -> do
+      withForeignPtr sfp $ \sptr -> do
+          let bp = bptr `plusPtr` boff
+          let dEnd = dptr `plusPtr` dlen
+              loop !dp !sp | dp >= dEnd = return ()
+                           | otherwise = do
+                let n = min every (dEnd `minusPtr` dp)
+                memcpy dp sp (fromIntegral n)
+                memcpy (dp `plusPtr` n) bp (fromIntegral blen)
+                loop (dp `plusPtr` (n + blen)) (sp `plusPtr` every)
+          loop dptr (sptr `plusPtr` soff)
+  where dlen = slen + blen * numBreaks
+        numBreaks = slen `div` every
+
+-- | Decode a base64-encoded string.  This function strictly follows
+-- the specification in RFC 4648,
+-- <http://www.apps.ietf.org/rfc/rfc4648.html>.
+-- This function takes the decoding table (for @base64@ or @base64url@) as 
+-- the first paramert.
+decodeWithTable :: ForeignPtr Word8 -> ByteString -> Either String ByteString
+decodeWithTable decodeFP (PS sfp soff slen)
+    | drem /= 0 = Left "invalid padding"
+    | dlen <= 0 = Right B.empty
+    | otherwise = unsafePerformIO $ do
+  dfp <- mallocByteString dlen
+  withForeignPtr decodeFP $ \decptr -> do
+    let finish dbytes = return . Right $! if dbytes > 0
+                                          then PS dfp 0 dbytes
+                                          else B.empty
+        bail = return . Left
+    withForeignPtr sfp $ \sptr -> do
+      let sEnd = sptr `plusPtr` (slen + soff)
+          look p = do
+            ix <- fromIntegral `fmap` peek8 p
+            v <- peek8 (decptr `plusPtr` ix)
+            return $! fromIntegral v :: IO Word32
+          fill !dp !sp !n
+            | sp >= sEnd = finish n
+            | otherwise = {-# SCC "decodeWithTable/fill" #-} do
+            a <- look sp
+            b <- look (sp `plusPtr` 1)
+            c <- look (sp `plusPtr` 2)
+            d <- look (sp `plusPtr` 3)
+            let w = (a `shiftL` 18) .|. (b `shiftL` 12) .|.
+                    (c `shiftL` 6) .|. d
+            if a == done || b == done
+              then bail $ "invalid padding near offset " ++
+                   show (sp `minusPtr` sptr)
+              else if a .|. b .|. c .|. d == x
+              then bail $ "invalid base64 encoding near offset " ++
+                   show (sp `minusPtr` sptr)
+              else do
+                poke8 dp $ fromIntegral (w `shiftR` 16)
+                if c == done
+                  then finish $ n + 1
+                  else do
+                    poke8 (dp `plusPtr` 1) $ fromIntegral (w `shiftR` 8)
+                    if d == done
+                      then finish $! n + 2
+                      else do
+                        poke8 (dp `plusPtr` 2) $ fromIntegral w
+                        fill (dp `plusPtr` 3) (sp `plusPtr` 4) (n+3)
+      withForeignPtr dfp $ \dptr ->
+        fill dptr (sptr `plusPtr` soff) 0
+  where (di,drem) = slen `divMod` 4
+        dlen = di * 3
+
+data D = D { dNext :: {-# UNPACK #-} !(Ptr Word8)
+           , dValue :: {-# UNPACK #-} !Word32 }
+
+-- | Decode a base64-encoded string.  This function is lenient in
+-- following the specification from RFC 4648,
+-- <http://www.apps.ietf.org/rfc/rfc4648.html>, and will not generate
+-- parse errors no matter how poor its input.
+-- This function takes the decoding table (for @base64@ or @base64url@) as 
+-- the first paramert.
+decodeLenientWithTable :: ForeignPtr Word8 -> ByteString -> ByteString
+decodeLenientWithTable decodeFP (PS sfp soff slen)
+    | dlen <= 0 = B.empty
+    | otherwise = unsafePerformIO $ do
+  dfp <- mallocByteString dlen
+  dbytes <- withForeignPtr decodeFP $ \decptr ->
+    withForeignPtr sfp $ \sptr -> do
+      let sEnd = sptr `plusPtr` (slen + soff)
+          fill !dp !sp !n
+            | sp >= sEnd = return n
+            | otherwise = {-# SCC "decodeLenientWithTable/fill" #-} do
+            let look skipPad = go
+                  where
+                    go p | p >= sEnd = return $! D (sEnd `plusPtr` (-1)) done
+                         | otherwise =  {-# SCC "decodeLenient/look" #-} do
+                      ix <- fromIntegral `fmap` peek8 p
+                      v <- peek8 (decptr `plusPtr` ix)
+                      if v == x || (v == done && skipPad)
+                        then go (p `plusPtr` 1)
+                        else return $! D (p `plusPtr` 1) (fromIntegral v)
+            !a <- look True  sp
+            !b <- look True  (dNext a)
+            !c <- look False (dNext b)
+            !d <- look False (dNext c)
+            let w = (dValue a `shiftL` 18) .|. (dValue b `shiftL` 12) .|.
+                    (dValue c `shiftL` 6) .|. dValue d
+            if dValue a == done || dValue b == done
+              then return n
+              else do
+                poke8 dp $ fromIntegral (w `shiftR` 16)
+                if dValue c == done
+                  then return $! n + 1
+                  else do
+                    poke8 (dp `plusPtr` 1) $ fromIntegral (w `shiftR` 8)
+                    if dValue d == done
+                      then return $! n + 2
+                      else do
+                        poke8 (dp `plusPtr` 2) $ fromIntegral w
+                        fill (dp `plusPtr` 3) (dNext d) (n+3)
+      withForeignPtr dfp $ \dptr ->
+        fill dptr (sptr `plusPtr` soff) 0
+  return $! if dbytes > 0
+            then PS dfp 0 dbytes
+            else B.empty
+  where dlen = ((slen + 3) `div` 4) * 3
+
+x :: Integral a => a
+x = 255
+{-# INLINE x #-}
+
+done :: Integral a => a
+done = 99
+{-# INLINE done #-}
diff --git a/Data/ByteString/Base64/URL.hs b/Data/ByteString/Base64/URL.hs
new file mode 100644
--- /dev/null
+++ b/Data/ByteString/Base64/URL.hs
@@ -0,0 +1,58 @@
+{-# LANGUAGE BangPatterns #-}
+
+-- |
+-- Module      : Data.ByteString.Base64.URL
+-- Copyright   : (c) 2012 Deian Stefan
+--
+-- License     : BSD-style
+-- Maintainer  : deian@cs.stanford.edu
+-- Stability   : experimental
+-- Portability : GHC
+--
+-- Fast and efficient encoding and decoding of base64url-encoded strings.
+
+module Data.ByteString.Base64.URL
+    (
+      encode
+    , decode
+    , decodeLenient
+    , joinWith
+    ) where
+
+import Data.ByteString.Base64.Internal
+import qualified Data.ByteString as B
+import Data.ByteString.Internal (ByteString(..))
+import Data.Word (Word8)
+import Foreign.ForeignPtr (ForeignPtr)
+
+-- | Encode a string into base64url form.  The result will always be a
+-- multiple of 4 bytes in length.
+encode :: ByteString -> ByteString
+encode = encodeWithAlphabet alphabet
+
+-- | Decode a base64url-encoded string.  This function strictly follows
+-- the specification in RFC 4648,
+-- <http://www.apps.ietf.org/rfc/rfc4648.html>.
+decode :: ByteString -> Either String ByteString
+decode = decodeWithTable decodeFP
+
+-- | Decode a base64url-encoded string.  This function is lenient in
+-- following the specification from RFC 4648,
+-- <http://www.apps.ietf.org/rfc/rfc4648.html>, and will not generate
+-- parse errors no matter how poor its input.
+decodeLenient :: ByteString -> ByteString
+decodeLenient = decodeLenientWithTable decodeFP
+
+
+alphabet :: ByteString
+alphabet = B.pack $ [65..90] ++ [97..122] ++ [48..57] ++ [45,95]
+{-# NOINLINE alphabet #-}
+
+decodeFP :: ForeignPtr Word8
+PS decodeFP _ _ = B.pack $ replicate 45 x ++ [62,x,x] ++ [52..61] ++ [x,x,
+  x,done,x,x,x] ++ [0..25] ++ [x,x,x,x,63,x] ++ [26..51] ++ replicate 133 x
+{-# NOINLINE decodeFP #-}
+
+x :: Integral a => a
+x = 255
+{-# INLINE x #-}
diff --git a/base64-bytestring.cabal b/base64-bytestring.cabal
--- a/base64-bytestring.cabal
+++ b/base64-bytestring.cabal
@@ -1,5 +1,5 @@
 name:                base64-bytestring
-version:             0.1.0.3
+version:             0.1.1.0
 synopsis:            Fast base64 encoding and deconding for ByteStrings
 description:         Fast base64 encoding and deconding for ByteStrings
 homepage:            https://github.com/bos/base64-bytestring
@@ -8,20 +8,24 @@
 license-file:        LICENSE
 author:              Bryan O'Sullivan <bos@serpentine.com>
 maintainer:          Bryan O'Sullivan <bos@serpentine.com>
-copyright:           2010 Bryan O'Sullivan
+copyright:           2010-2011 Bryan O'Sullivan
 category:            Data
 build-type:          Simple
-extra-source-files:  README.markdown
+cabal-version:       >=1.8
 
 extra-source-files:
+  README.markdown
   tests/Transcode.hs
-
-Cabal-version:       >=1.6
+  tests/transcode.py
 
 library
   exposed-modules:
     Data.ByteString.Base64
+    Data.ByteString.Base64.URL
   
+  other-modules:
+    Data.ByteString.Base64.Internal
+
   build-depends:
     base == 4.*,
     bytestring == 0.9.*
@@ -29,10 +33,29 @@
   ghc-options: -Wall -funbox-strict-fields
   ghc-prof-options: -auto-all
 
+test-suite tests
+  type: exitcode-stdio-1.0
+  hs-source-dirs: tests
+  main-is: Tests.hs
+
+  ghc-options:
+    -Wall -threaded -rtsopts
+
+  build-depends:
+    QuickCheck,
+    HUnit,
+    base64-bytestring,
+    base,
+    containers,
+    bytestring,
+    test-framework,
+    test-framework-quickcheck2,
+    test-framework-hunit
+
 source-repository head
   type:     git
-  location: http://github.com/bos/base64-bytestring
+  location: git://github.com/bos/base64-bytestring
 
 source-repository head
   type:     mercurial
-  location: http://bitbucket.org/bos/base64-bytestring
+  location: https://bitbucket.org/bos/base64-bytestring
diff --git a/tests/transcode.py b/tests/transcode.py
new file mode 100644
--- /dev/null
+++ b/tests/transcode.py
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+
+import binascii, sys
+
+funcs = {
+    'decode': binascii.a2b_base64,
+    'encode': binascii.b2a_base64,
+    'read': lambda x:x,
+    }
+    
+f = funcs[sys.argv[1]]
+
+for n in sys.argv[2:]:
+    sys.stdout.write(f(open(n).read()))
