diff --git a/BSD3 b/BSD3
new file mode 100644
--- /dev/null
+++ b/BSD3
@@ -0,0 +1,26 @@
+Copyright (c) Ian Lynagh.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. 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.
+3. The names of the author may not be used to endorse or promote
+   products derived from this software without specific prior written
+   permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND 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 REGENTS OR 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/COPYING b/COPYING
new file mode 100644
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,6 @@
+
+Copyright (c) Ian Lynagh, 2005, 2007.
+
+This package can be used under either the GPL v2, as in ./GPL-2, or the
+3-clause BSD, as in ./BSD3, license.
+
diff --git a/Codec/Binary/EncodingQ/String.hs b/Codec/Binary/EncodingQ/String.hs
new file mode 100644
--- /dev/null
+++ b/Codec/Binary/EncodingQ/String.hs
@@ -0,0 +1,49 @@
+
+-- XXX Write tests
+
+-- Defined in RFC 2047
+-- We assume we have US-ASCII characters.
+
+module Codec.Binary.EncodingQ.String ( encode, decode) where
+
+import Codec.MIME.String.Internal.Utils
+import Data.Bits
+import Data.Char
+
+-- len is the maximum length the encoded text in a single block is
+-- allowed to be
+encode :: Int -> String -> [String]
+encode _   "" = []
+encode len xs = enc len 0 id id xs
+
+-- The Int is the number of characters on this line so far
+-- 76 is the maximum we can have no one line, and 3 is the most
+-- generated for 1 input char (but we also need space for a trailing
+-- '=' for a soft line break).
+
+enc :: Int -> Int -- Length stuff
+    -> ([String] -> [String]) -> (String -> String) -- accumulators
+    -> String -- input
+    -> [String]
+enc _   _      acc_list acc_string ""     = acc_list [acc_string ""]
+enc len so_far acc_list acc_string (c:cs)
+ = if so_far' > len
+   then enc len new_len (acc_list . (acc_string "" :)) id cs
+   else enc len new_len acc_list (acc_string . (encoded ++)) cs
+    where encoded = if isAsciiPrint c && (c /= ' ') && (c /= '?')
+                    then [c]
+                    else ['=', x1, x2]
+          new_len = length encoded
+          so_far' = so_far + new_len
+          o = ord c
+          x1 = toUpper $ intToDigit (o `shiftR` 4)
+          x2 = toUpper $ intToDigit (o .&. 0xF)
+
+decode :: String -> String
+decode ('=':c1:c2:cs)
+ | isAsciiHexDigit c1 && isAsciiHexDigit c2
+    = chr ((digitToInt c1 `shiftL` 4)  + digitToInt c2):decode cs
+decode ('_':cs) = ' ':decode cs
+decode (c:cs) = c:decode cs
+decode "" = ""
+
diff --git a/Codec/MIME/String.hs b/Codec/MIME/String.hs
new file mode 100644
--- /dev/null
+++ b/Codec/MIME/String.hs
@@ -0,0 +1,21 @@
+
+module Codec.MIME.String (
+    module Codec.MIME.String.ContentDisposition,
+    module Codec.MIME.String.Date,
+    module Codec.MIME.String.EncodedWord,
+    module Codec.MIME.String.Flatten,
+    module Codec.MIME.String.Headers,
+    module Codec.MIME.String.Parse,
+    module Codec.MIME.String.QuotedPrintable,
+    module Codec.MIME.String.Types,
+ ) where
+
+import Codec.MIME.String.ContentDisposition
+import Codec.MIME.String.Date
+import Codec.MIME.String.EncodedWord
+import Codec.MIME.String.Flatten
+import Codec.MIME.String.Headers
+import Codec.MIME.String.Parse
+import Codec.MIME.String.QuotedPrintable
+import Codec.MIME.String.Types
+
diff --git a/Codec/MIME/String/ContentDisposition.hs b/Codec/MIME/String/ContentDisposition.hs
new file mode 100644
--- /dev/null
+++ b/Codec/MIME/String/ContentDisposition.hs
@@ -0,0 +1,115 @@
+
+-- We assume we have US-ASCII characters
+
+module Codec.MIME.String.ContentDisposition
+      (
+       ContentDisposition(ContentDisposition),
+       DispositionType(Inline, Attachment),
+       DispositionParameter(..),
+       get_content_disposition,
+      ) where
+
+import Codec.MIME.String.Internal.Utils
+import Codec.MIME.String.Internal.ABNF
+      (
+       Parser, parse, nested_parse,
+       (<$>), (<$), (<*>), (<*), (<|>),
+       pEOI, pPred, pChar, pMany, pAtLeast,
+      )
+import Codec.MIME.String.Date (FullDate, p_date_time)
+import Codec.MIME.String.Headers
+      (
+       Parameter, p_parameter,
+       cws, p_ci_string, p_extension_token, p_value, p_quoted_string,
+      )
+
+data ContentDisposition = ContentDisposition DispositionType
+                                             [DispositionParameter]
+    deriving (Show, Read)
+
+data DispositionType = Inline | Attachment
+    deriving (Show, Read)
+
+data DispositionParameter = Filename String
+                          | CreationDate FullDate
+                          | ModificationDate FullDate
+                          | ReadDate FullDate
+                          | Size Integer
+                          | DispositionParameter Parameter
+    deriving (Show, Read)
+
+get_content_disposition :: String -> Maybe ContentDisposition
+get_content_disposition xs
+ = case parse ph_content_disposition xs of
+       Left cd -> Just cd
+       Right _ -> Nothing
+
+ph_content_disposition :: Parser Char ContentDisposition
+ph_content_disposition = ContentDisposition
+                     <$  cws
+                     <*> p_disposition_type
+                     <*> pMany (    id
+                                <$  cws
+                                <*  pChar ';'
+                                <*  cws
+                                <*> p_disposition_parm)
+                     <*  cws
+                     <*  pEOI
+
+p_disposition_type :: Parser Char DispositionType
+p_disposition_type = Inline <$  p_ci_string "inline"
+                 <|> Attachment <$  p_ci_string "attachment"
+                 <|> Attachment <$  p_extension_token
+
+p_disposition_parm :: Parser Char DispositionParameter
+p_disposition_parm = p_filename_parm
+                 <|> p_creation_date_parm
+                 <|> p_modification_date_parm
+                 <|> p_read_date_parm
+                 <|> p_size_parm
+                 <|> DispositionParameter <$> p_parameter
+
+p_filename_parm :: Parser Char DispositionParameter
+p_filename_parm = Filename
+              <$  p_ci_string "filename"
+              <*  cws
+              <*  pChar '='
+              <*  cws
+              <*> p_value
+
+p_creation_date_parm :: Parser Char DispositionParameter
+p_creation_date_parm = CreationDate
+                   <$  p_ci_string "creation-date"
+                   <*  cws
+                   <*  pChar '='
+                   <*  cws
+                   <*> p_quoted_date_time
+
+p_modification_date_parm :: Parser Char DispositionParameter
+p_modification_date_parm = ModificationDate
+                       <$  p_ci_string "modification-date"
+                       <*  cws
+                       <*  pChar '='
+                       <*  cws
+                       <*> p_quoted_date_time
+
+p_read_date_parm :: Parser Char DispositionParameter
+p_read_date_parm = ReadDate
+               <$  p_ci_string "read-date"
+               <*  cws
+               <*  pChar '='
+               <*  cws
+               <*> p_quoted_date_time
+
+p_size_parm :: Parser Char DispositionParameter
+p_size_parm = (Size . read)
+          <$  p_ci_string "size"
+          <*  cws
+          <*  pChar '='
+          <*  cws
+          <*> pAtLeast 1 (pPred isAsciiDigit)
+
+p_quoted_date_time :: Parser Char FullDate
+p_quoted_date_time = nested_parse p_quoted_string
+                                  (id <$> p_date_time <*  pEOI)
+
diff --git a/Codec/MIME/String/Date.hs b/Codec/MIME/String/Date.hs
new file mode 100644
--- /dev/null
+++ b/Codec/MIME/String/Date.hs
@@ -0,0 +1,241 @@
+
+module Codec.MIME.String.Date
+      (FullDate(FullDate), DOW(..), Date(Date), Day, Month(..), Year,
+       Time(Time), Zone, TimeOfDay(TimeOfDay), Hour, Minute, Second,
+       show_full_date, show_mbox_full_date, get_date, p_date_time,
+       get_current_date, epochDate,
+      )
+      where
+
+import Codec.MIME.String.Internal.ABNF
+      (
+       Parser, parse,
+       (<$>), (<$), (<*>), (<*), (<|>),
+       pEOI, pPred, pChar, pAtLeast, pFromTo, pExactly, pMaybe,
+      )
+import Codec.MIME.String.Headers (cws, p_ci_string)
+import Codec.MIME.String.Internal.Utils
+
+import Control.Monad.Trans (MonadIO, liftIO)
+import System.Time hiding (Month(May), Day)
+import qualified System.Time as Time (Month(May))
+
+data FullDate = FullDate (Maybe DOW) Date Time
+    deriving (Show, Read)
+
+data DOW = Mon | Tue | Wed | Thu | Fri | Sat | Sun
+    deriving (Show, Read)
+
+data Date = Date Day Month Year
+    deriving (Show, Read)
+type Day = Int
+data Month = Jan | Feb | Mar | Apr | May | Jun
+           | Jul | Aug | Sep | Oct | Nov | Dec
+    deriving (Show, Read)
+type Year = Int
+
+data Time = Time TimeOfDay Zone
+    deriving (Show, Read)
+type Zone = Int
+data TimeOfDay = TimeOfDay Hour Minute (Maybe Second)
+    deriving (Show, Read)
+type Hour = Int
+type Minute = Int
+type Second = Int
+
+epochDate :: FullDate
+epochDate = FullDate (Just Thu) (Date 01 Jan 1970)
+                     (Time (TimeOfDay 0 0 (Just 0)) 0)
+
+------------------------ Showing
+
+show_full_date :: FullDate -> String
+show_full_date (FullDate m_dow date time)
+ = shown_dow ++ show_date date ++ " " ++ show_time time
+    where shown_dow = case m_dow of
+                          Nothing -> ""
+                          Just dow -> show dow ++ ", "
+
+show_date :: Date -> String
+show_date (Date day month year)
+  = show_int 2 day ++ " " ++ show month ++ " " ++ show year
+
+show_time :: Time -> String
+show_time (Time tod zone) = show_tod tod ++ " " ++ show_zone zone
+
+show_tod :: TimeOfDay -> String
+show_tod (TimeOfDay h m m_s)
+ = show_int 2 h ++ ":" ++ show_int 2 m ++ shown_s
+    where shown_s = case m_s of
+                        Nothing -> ""
+                        Just s -> ":" ++ show_int 2 s
+
+show_zone :: Zone -> String
+show_zone z = (if z < 0 then '-' else '+'):show_int 4 (abs z)
+
+show_int :: Int -> Int -> String
+show_int digits int = let s = show int
+                      in replicate (digits - length s) '0' ++ s
+
+-- Showing for the "From " line in mboxes is sadly a slightly different format
+
+show_mbox_full_date :: FullDate -> String
+show_mbox_full_date (FullDate m_dow (Date day month year) (Time tod _))
+ = shown_dow ++ show month ++ " " ++ show_int 2 day ++ " " ++
+   show_tod tod ++ " " ++ show year
+    where shown_dow = case m_dow of
+                          Nothing -> ""
+                          Just dow -> show dow ++ " "
+
+------------------------ Parsing
+
+get_date :: String -> Maybe FullDate
+get_date xs
+ = case parse ph_date xs of
+       Left f -> Just f
+       Right _ -> Nothing
+
+ph_date :: Parser Char FullDate
+ph_date = id
+      <$  cws
+      <*> p_date_time
+      <*  cws
+      <*  pEOI
+
+p_date_time :: Parser Char FullDate
+p_date_time = FullDate
+          <$> pMaybe (    id
+                      <$> p_dow
+                      <*  cws
+                      <*  pChar ','
+                      <*  cws)
+          <*> p_date
+          <*  cws
+          <*> p_time
+
+p_dow :: Parser Char DOW
+p_dow = Mon <$  p_ci_string "Mon"
+    <|> Tue <$  p_ci_string "Tue"
+    <|> Wed <$  p_ci_string "Wed"
+    <|> Thu <$  p_ci_string "Thu"
+    <|> Fri <$  p_ci_string "Fri"
+    <|> Sat <$  p_ci_string "Sat"
+    <|> Sun <$  p_ci_string "Sun"
+
+p_date :: Parser Char Date
+p_date = Date
+     <$> p_day
+     <*  cws
+     <*> p_month
+     <*  cws
+     <*> p_year
+
+-- obs-year merged in
+p_year :: Parser Char Year
+p_year = (\ds -> let y = read ds
+                 in case ds of
+                        [_, _, _] -> 2000 + y
+                        [_, _]
+                         | y < 50 -> 1900 + y
+                         | otherwise -> 2000 + y
+                        _ -> y)
+     <$> pAtLeast 2 (pPred isAsciiDigit)
+
+p_month :: Parser Char Month
+p_month = Jan <$  p_ci_string "Jan"
+      <|> Feb <$  p_ci_string "Feb"
+      <|> Mar <$  p_ci_string "Mar"
+      <|> Apr <$  p_ci_string "Apr"
+      <|> May <$  p_ci_string "May"
+      <|> Jun <$  p_ci_string "Jun"
+      <|> Jul <$  p_ci_string "Jul"
+      <|> Aug <$  p_ci_string "Aug"
+      <|> Sep <$  p_ci_string "Sep"
+      <|> Oct <$  p_ci_string "Oct"
+      <|> Nov <$  p_ci_string "Nov"
+      <|> Dec <$  p_ci_string "Dec"
+
+p_day :: Parser Char Day
+p_day = read <$> pFromTo 1 2 (pPred isAsciiDigit)
+
+p_time :: Parser Char Time
+p_time = Time
+     <$> p_time_of_day
+     <*  cws
+     <*> p_zone
+
+p_time_of_day :: Parser Char TimeOfDay
+p_time_of_day = TimeOfDay
+            <$> p_hour
+            <*  cws
+            <*  pChar ':'
+            <*  cws
+            <*> p_minute
+            <*> pMaybe (    id
+                        <$  cws
+                        <*  pChar ':'
+                        <*  cws
+                        <*> p_second)
+
+p_hour :: Parser Char Hour
+p_hour = read <$> pExactly 2 (pPred isAsciiDigit)
+
+p_minute :: Parser Char Minute
+p_minute = read <$> pExactly 2 (pPred isAsciiDigit)
+
+p_second :: Parser Char Second
+p_second = read <$> pExactly 2 (pPred isAsciiDigit)
+
+p_zone :: Parser Char Zone
+p_zone = (\f n -> f $ read n)
+     <$> (id <$  pChar '+' <|> negate <$  pChar '-')
+     <*> pExactly 4 (pPred isAsciiDigit)
+ <|> p_obs_zone
+
+p_obs_zone :: Parser Char Zone
+p_obs_zone = 0 <$  p_ci_string "UT"
+         <|> 0 <$  p_ci_string "GMT"
+         <|> -500 <$  p_ci_string "EST"
+         <|> -400 <$  p_ci_string "EDT"
+         <|> -600 <$  p_ci_string "CST"
+         <|> -500 <$  p_ci_string "CDT"
+         <|> -700 <$  p_ci_string "MST"
+         <|> -600 <$  p_ci_string "MDT"
+         <|> -800 <$  p_ci_string "PST"
+         <|> -700 <$  p_ci_string "PDT"
+         -- Military time zones. Strictly we shouldn't accept [jJ]
+         -- but no harm done.
+         -- 'they SHOULD all be considered equivalent to "-0000"' as
+         -- RFC 822 defined them incorrectly.
+         <|> 0 <$  pPred isAsciiAlpha
+
+get_current_date :: MonadIO m => m FullDate
+get_current_date
+ = do clt <- liftIO getClockTime
+      cat <- liftIO $ toCalendarTime clt
+      let fd = FullDate (Just dow) date time
+          get_dow Sunday    = Sun
+          get_dow Monday    = Mon
+          get_dow Tuesday   = Tue
+          get_dow Wednesday = Wed
+          get_dow Thursday  = Thu
+          get_dow Friday    = Fri
+          get_dow Saturday  = Sat
+          get_month January   = Jan
+          get_month February  = Feb
+          get_month March     = Mar
+          get_month April     = Apr
+          get_month Time.May  = May
+          get_month June      = Jun
+          get_month July      = Jul
+          get_month August    = Aug
+          get_month September = Sep
+          get_month October   = Oct
+          get_month November  = Nov
+          get_month December  = Dec
+          dow = get_dow (ctWDay cat)
+          date = Date (ctDay cat) (get_month (ctMonth cat)) (ctYear cat)
+          time = Time tod (ctTZ cat `div` 36)
+          tod = TimeOfDay (ctHour cat) (ctMin cat) (Just (ctSec cat))
+      return fd
+
diff --git a/Codec/MIME/String/EncodedWord.hs b/Codec/MIME/String/EncodedWord.hs
new file mode 100644
--- /dev/null
+++ b/Codec/MIME/String/EncodedWord.hs
@@ -0,0 +1,8 @@
+
+module Codec.MIME.String.EncodedWord (base64_encode) where
+
+import Codec.Binary.Base64.String (encode)
+
+base64_encode :: String -> String -> String
+base64_encode charset xs = "=?" ++ charset ++ "?B?" ++ encode xs ++ "?="
+
diff --git a/Codec/MIME/String/Flatten.hs b/Codec/MIME/String/Flatten.hs
new file mode 100644
--- /dev/null
+++ b/Codec/MIME/String/Flatten.hs
@@ -0,0 +1,111 @@
+
+module Codec.MIME.String.Flatten (flatten) where
+
+import qualified Codec.Binary.Base64.String as Base64 (encode)
+import Codec.MIME.String.Date (FullDate, show_full_date, show_mbox_full_date)
+import Codec.MIME.String.EncodedWord (base64_encode)
+import qualified Codec.MIME.String.QuotedPrintable as QP (encode)
+import Codec.MIME.String.Internal.Utils
+
+import Data.List (intersperse)
+import Network.BSD (getHostName)
+import System.Locale (defaultTimeLocale)
+import System.Random (randomIO)
+import System.Time (getClockTime, toCalendarTime, formatCalendarTime)
+
+-- XXX This should take a headers structure
+-- Flatten {meta info, message body and list of attachments} to a raw message
+flatten :: String -> String -> String -> FullDate
+        -> String -> [(String, FilePath)]
+        -> IO String
+flatten from_name from_email subject date body attachments
+ = do msgid <- mk_msgid
+      let -- XXX This (\n) could be prettier - check llengths of bits
+          from_full = encode_name from_name ++ "\n <" ++ from_email ++ ">"
+          boundary = "=:"
+          part_start = "\n--" ++ boundary ++ "\n"
+          parts_end  = "\n--" ++ boundary ++ "--\n"
+          text_content = unlines [
+              "Content-type: text/plain; charset=utf-8",
+              "Content-transfer-encoding: quoted-printable",
+              "Content-Disposition: inline"]
+          common_headers = unlines [
+              "From " ++ from_email ++ " " ++ show_mbox_full_date date,
+              "Date: " ++ show_full_date date,
+              "Message-ID: " ++ msgid,
+              "From: " ++ from_full,
+              "Subject: " ++ mk_subject subject,
+              "MIME-Version: 1.0"]
+          -- This is overly paranoid
+          safe_char c = isAsciiAlphaNum c || (c `elem` " .-_")
+          mime_attachment (a, fn)
+              = part_start
+                -- XXX Can we give a better MIME type than this?
+             ++ "Content-type: application/octet-stream\n"
+             ++ "Content-transfer-encoding: base64\n"
+             ++ "Content-Disposition: attachment; filename=\""
+                    ++ (case reverse $ filter safe_char
+                                     $ takeWhile ('/' /=)
+                                     $ reverse fn of
+                            [] -> "unknown"
+                            fn' -> fn')
+                    ++ "\"\n"
+             ++ "\n"
+             ++ Base64.encode a
+          msg = if single_part
+                then common_headers
+                  ++ text_content
+                  ++ "\n"
+                  ++ QP.encode (my_lines body)
+                else common_headers
+                  ++ "Content-type: multipart/mixed; boundary=\""
+                                                ++ boundary ++ "\"\n"
+                  ++ "\n"
+                  ++ "This is a multi-part message in MIME format.\n"
+                  ++ part_start
+                  ++ text_content
+                  ++ "\n"
+                  ++ QP.encode (my_lines body)
+                  ++ concatMap mime_attachment attachments
+                  ++ parts_end
+      return msg
+    where single_part = null attachments
+
+-- XXX The IO calls used to use liftIOErr. We could use something similar
+-- but require we are called in a MonadError m?
+-- XXX We should possibly be including a program name
+mk_msgid :: IO String
+mk_msgid
+ = do hostname <- getHostName
+      clock_time <- getClockTime
+      calendar_time <- toCalendarTime clock_time
+      r <- randomIO
+      let timestamp = formatCalendarTime defaultTimeLocale
+                                         "%Y%m%d%H%M%S" calendar_time
+          lhs = concat $ intersperse "."
+                [timestamp, show (r :: Int)]
+      return (lhs ++ "@" ++ hostname)
+
+mk_subject :: String -> String
+mk_subject subject
+ = if can_send_clear subject &&
+      length_at_most (76 - length "Subject: ") subject
+   then subject
+   else encode_string (length "Subject: ") subject
+
+can_send_clear :: String -> Bool
+can_send_clear = all isAsciiPrint
+
+length_at_most :: Int -> [a] -> Bool
+length_at_most _ [] = True
+length_at_most 0 _ = False
+length_at_most i (_:xs) = length_at_most (i-1) xs
+
+encode_name :: String -> String
+encode_name rn = encode_string (length "From: ") rn
+
+encode_string :: Int -> String -> String
+encode_string n s
+ = concat $ intersperse "\n " $ map (base64_encode "utf-8") (splits n' s)
+    where n' = ((76 - n - length "=?utf-8?B??=") * 3) `div` 4
+
diff --git a/Codec/MIME/String/Headers.hs b/Codec/MIME/String/Headers.hs
new file mode 100644
--- /dev/null
+++ b/Codec/MIME/String/Headers.hs
@@ -0,0 +1,635 @@
+
+-- We assume we have US-ASCII characters
+
+module Codec.MIME.String.Headers
+      (
+       Domain(Domain, LiteralDomain),
+       Mailbox(Mailbox),
+       RoutedEmailAddress(RoutedEmailAddress, NormalEmailAddress),
+       EmailAddress(EmailAddress), get_addr_spec,
+       Address(Address, Group),
+       ContentType(ContentType), get_content_type,
+       ContentDescription(ContentDescription),
+       get_content_description,
+       ContentTransferEncoding(ContentTransferEncoding),
+       get_content_transfer_encoding,
+       ContentID(ContentID), get_content_id,
+       MessageID(MessageID),
+       MIMEVersion(MIMEVersion), get_mime_version,
+       Parameter(Parameter), p_parameter,
+       From(From), To(To), Subject(Subject),
+       get_from,   get_to, get_subject,
+       get_boundary, p_extension_token, p_value, p_quoted_string,
+       cws, p_ci_string,
+      )
+      where
+
+import Text.Iconv
+import Codec.MIME.String.Internal.ABNF
+      (
+       Parser, parse, pSucceed, pFail,
+       (<$>), (<$), (<*>), (<*), (<|>), (<|), (<!>),
+       pEOI, pPred, pChar, pMany, pAtLeast, pMaybe, pOptDef, pString,
+      )
+import qualified Codec.Binary.Base64.String as Base64 (decode)
+import qualified Codec.Binary.EncodingQ.String as EncodingQ (decode)
+import Codec.MIME.String.Internal.Utils
+
+import Data.Char
+import Data.List
+
+-----------------------
+-- Utils
+
+ignore :: Parser inp a -> Parser inp ()
+ignore p = () <$  p
+
+boxp :: Parser inp a -> Parser inp [a]
+boxp p = box <$> p
+
+-----------------------
+-- RFC 2234
+
+p_CTL :: Parser Char Char
+p_CTL = pPred (\c -> ord c < 32 || ord c == 127)
+
+p_SP :: Parser Char Char
+p_SP = pChar ' '
+
+p_HTAB :: Parser Char Char
+p_HTAB = pChar '\t'
+
+p_WSP :: Parser Char Char
+p_WSP = p_SP <|> p_HTAB
+
+-----------------------
+-- RFC 2822
+
+-- Case insensitive strings, written "Foo"
+p_ci_string :: String -> Parser Char String
+p_ci_string s = s <$  f s
+    where f "" = pSucceed ()
+          f (c:cs) = let p = if isAsciiAlpha c
+                             then pChar (toLower c) <|  pChar (toUpper c)
+                             else pChar c
+                     in () <$  p <*  f cs
+
+p_NO_WS_CTL :: Parser Char Char
+p_NO_WS_CTL = pPred (\c -> let o = ord c in 1 <= o && o <= 8
+                                         || o == 11
+                                         || o == 12
+                                         || 14 <= o && o <= 31
+                                         || o == 127)
+
+-- If we follow the spec precisely then we get pMany (pMany), and hence
+-- non-termination, so we merge the definition of p_obs_text in.
+p_text :: Parser Char String
+p_text = concat
+     <$> pMany (
+                    p_encoded_words
+                <|  boxp (pPred (\c -> let o = ord c in 0 <= o && o <= 9
+                                                     || o == 11
+                                                     || o == 12
+                                                     || 14 <= o && o <= 127))
+               )
+
+-- We are lax about checking they have any necessary surrounding
+-- whitespace
+p_encoded_words :: Parser Char String
+p_encoded_words = (\x xs -> x ++ concat xs)
+              <$> p_encoded_word
+              <*> pMany (id <$  cws <*> p_encoded_word)
+
+p_encoded_word :: Parser Char String
+p_encoded_word = (\cs dec text -> case convert Fuzzy cs "utf8" (dec text) of
+                                      ConversionSuccess xs -> xs
+                                      ConversionFailed -> error "p_encoded_word: Can't happen XXX"
+                                      ConversionUnsupported -> error "p_encoded_word: Unknown charset XXX")
+             <$  pString "=?"
+             <*> p_charset
+             <*  pChar '?'
+             <*> p_encoding
+             <*  pChar '?'
+             <*> p_encoded_text
+             <*  pString "?="
+
+-- token definition inlined as they use a different one to p_token.
+p_charset :: Parser Char String
+p_charset = pAtLeast 1 (pPred isAscii <!> (p_SP <|> p_CTL <|> p_especials))
+
+p_especials :: Parser Char Char
+p_especials = pPred (`elem` "()<>@,;:\\\"/[]?.=")
+
+-- This is much stricter than specified, but if it's not [qQbB] then
+-- we'd want to fall back to showing it as a string anyway.
+p_encoding :: Parser Char (String -> String)
+p_encoding = EncodingQ.decode <$  (pChar 'Q' <|> pChar 'q')
+         <|> Base64.decode <$  (pChar 'B' <|> pChar 'b')
+
+p_encoded_text :: Parser Char String
+p_encoded_text = pMany (pPred (\c -> isAsciiPrint c && c /= '?' && c /= ' '))
+
+p_quoted_pair :: Parser Char String
+p_quoted_pair = id <$  pChar '\\' <*> p_text <|> (boxp p_obs_qp)
+
+p_obs_qp :: Parser Char Char
+p_obs_qp = id <$  pChar '\\' <*> pPred isAscii
+
+-- Done differently as the newlines are already gone
+p_FWS :: Parser Char String
+p_FWS = pMany p_WSP
+
+p_ctext :: Parser Char Char
+p_ctext = p_NO_WS_CTL
+      <|> pPred (\c -> let o = ord c in 33 <= o && o <= 39
+                                     || 42 <= o && o <= 91
+                                     || 93 <= o && o <= 126)
+
+p_ccontent :: Parser Char ()
+p_ccontent = ignore p_ctext <|> ignore p_quoted_pair <|> p_comment
+
+p_comment :: Parser Char ()
+p_comment = ()
+        <$  pChar '('
+        <*  pMany (() <$  pMany p_NO_WS_CTL <*  p_ccontent)
+        <*  pMany p_NO_WS_CTL
+        <*  pChar ')'
+
+-- We might want to keep the result. If we do then we also need to
+-- handle encoded words properly.
+-- This isn't quite CFWS as we need to be able to accept "1.0"
+-- as a MIME version with cws between all the characters.
+-- Also, we've already removed all the newlines in the headers.
+cws :: Parser Char ()
+cws = ignore $ pMany (ignore (pAtLeast 1 p_WSP) <|> p_comment)
+
+p_qtext :: Parser Char Char
+p_qtext = p_NO_WS_CTL
+      <|> pPred (\c -> let o = ord c in o == 33
+                                     || 35 <= o && o <= 91
+                                     || 93 <= o && o <= 126)
+
+p_qcontent :: Parser Char String
+p_qcontent = boxp p_qtext
+         <|> p_quoted_pair
+
+p_quoted_string :: Parser Char String
+p_quoted_string = (++)
+              <$  cws
+              <*  pChar '"'
+              <*> (concat <$> pMany ((++) <$> pOptDef "" p_FWS <*> p_qcontent))
+              <*> pOptDef "" p_FWS
+              <*  pChar '"'
+
+p_dcontent :: Parser Char String
+p_dcontent = boxp p_dtext <|> p_quoted_pair
+
+p_dtext :: Parser Char Char
+p_dtext = p_NO_WS_CTL
+      <|> pPred (\c -> let o = ord c in 33 <= o && o <= 90
+                                     || 94 <= o && o <= 126)
+
+data MessageID = MessageID String Domain
+    deriving (Show, Read)
+
+p_msg_id :: Parser Char MessageID
+p_msg_id = MessageID
+       <$  cws
+       <*  pChar '<'
+       <*> p_id_left
+       <*  pChar '@'
+       <*> p_id_right
+       <*  pChar '>'
+       <*  cws
+
+p_atom :: Parser Char String
+p_atom = id
+     <$  cws
+     <*> pAtLeast 1 p_atext
+     <*  cws
+
+p_atext :: Parser Char Char
+p_atext = pPred (\c -> isAsciiAlphaNum c || c `elem` "!#$%&'+-/=?^_`{|}~")
+
+p_dot_atom :: Parser Char String
+p_dot_atom = id 
+         <$  cws
+         <*> p_dot_atom_text
+         <*  cws
+
+p_word :: Parser Char String
+p_word = p_atom <|> p_quoted_string
+
+-- This incorporates obs-phrase
+p_phrase :: Parser Char [String]
+p_phrase = (:)
+       <$> (p_encoded_words <|  p_word)
+       <*> pMany (id <$  cws <*> (p_encoded_words <|  p_word <|  pString "."))
+   <|> boxp p_quoted_string
+
+p_dot_atom_text :: Parser Char String
+p_dot_atom_text = (\x xs -> x ++ concat xs)
+              <$> pAtLeast 1 p_atext
+              <*> pMany ((:) <$> pChar '.' <*> pAtLeast 1 p_atext)
+
+p_id_left :: Parser Char String
+p_id_left = p_dot_atom_text <|> p_no_fold_quote <|> p_obs_id_left
+
+p_id_right :: Parser Char Domain
+p_id_right = Domain <$> p_dot_atom_text
+         <|> p_no_fold_literal
+         <|> p_obs_id_right
+
+p_obs_id_left :: Parser Char String
+p_obs_id_left = p_local_part
+
+p_local_part :: Parser Char String
+p_local_part = p_dot_atom <|> p_quoted_string <|> p_obs_local_part
+
+p_obs_local_part :: Parser Char String
+p_obs_local_part = (\x xs -> x ++ concat xs)
+               <$> p_word
+               <*> pMany ((:) <$> pChar '.' <*> p_word)
+
+p_domain :: Parser Char Domain
+p_domain = Domain <$> p_dot_atom <|> p_domain_literal <|> p_obs_domain
+
+p_domain_literal :: Parser Char Domain
+p_domain_literal = (LiteralDomain . concat)
+               <$  cws
+               <*  pChar '['
+               <*> pMany (    id
+                          <$  p_FWS
+                          <*> p_dcontent)
+               <*  p_FWS
+               <*  pChar ']'
+               <*  cws
+
+p_obs_domain :: Parser Char Domain
+p_obs_domain = (\x xs -> Domain (x ++ concat xs))
+           <$> p_atom
+           <*> pMany ((:) <$> pChar '.' <*> p_atom)
+
+p_obs_id_right :: Parser Char Domain
+p_obs_id_right = p_domain
+
+p_no_fold_quote :: Parser Char String
+p_no_fold_quote = concat
+              <$  pChar '"'
+              <*> pMany (boxp p_qtext <|> p_quoted_pair)
+              <*  pChar '"'
+
+data Domain = Domain String | LiteralDomain String
+    deriving (Show, Read, Eq)
+
+p_no_fold_literal :: Parser Char Domain
+p_no_fold_literal = LiteralDomain . concat
+                <$  pChar '['
+                <*> pMany (boxp p_dtext <|> p_quoted_pair)
+                <*  pChar ']'
+
+data Subject = Subject String
+    deriving (Show, Read)
+
+get_subject :: String -> Maybe Subject
+get_subject xs
+ = case parse ph_subject xs of
+       Left cd -> Just cd
+       Right _ -> Nothing
+
+-- This is actually the RFC822 definition, as otherwise things get very
+-- confusing.
+-- Would be pMany, but p_text already does that for us
+ph_subject :: Parser Char Subject
+ph_subject = Subject <$> p_text <*  pEOI
+
+data From = From [Mailbox]
+    deriving (Show, Read, Eq)
+
+get_from :: String -> Maybe From
+get_from xs
+ = case parse ph_from xs of
+       Left f -> Just f
+       Right _ -> Nothing
+
+ph_from :: Parser Char From
+ph_from = From <$  cws <*> p_mailbox_list <*  cws <*  pEOI
+
+data To = To [Address]
+    deriving (Show, Read)
+
+data Address = Address Mailbox
+             | Group String [Mailbox]
+    deriving (Show, Read)
+
+get_to :: String -> Maybe To
+get_to xs
+ = case parse ph_to xs of
+       Left t -> Just t
+       Right _ -> Nothing
+
+ph_to :: Parser Char To
+ph_to = To <$  cws <*> p_address_list <*  cws <*  pEOI
+
+-- obs-addr-list merged in
+p_address_list :: Parser Char [Address]
+p_address_list = (:)
+             <$  pMany (() <$  pChar ',' <*  cws)
+             <*> p_address
+             <*> pMany (    id
+                        <$  pAtLeast 1 (() <$  cws <*  pChar ',')
+                        <*  cws
+                        <*> p_address)
+             <*  pMany (() <$  cws <*  pChar ',')
+
+p_address :: Parser Char Address
+p_address = Address <$> p_mailbox
+        <|> p_group
+
+p_group :: Parser Char Address
+p_group = Group
+      <$> p_display_name 
+      <*  cws
+      <*  pChar ':'
+      <*  cws
+      <*> pOptDef [] p_mailbox_list
+      <*  cws
+      <*  pChar ';'
+
+-- obs-mbox-list merged in
+p_mailbox_list :: Parser Char [Mailbox]
+p_mailbox_list = (:)
+             <$  pMany (() <$  pChar ',' <*  cws)
+             <*> p_mailbox
+             <*> pMany (    id
+                        <$  pAtLeast 1 (() <$  cws <*  pChar ',')
+                        <*  cws
+                        <*> p_mailbox)
+             <*  pMany (() <$  cws <*  pChar ',')
+
+data Mailbox = Mailbox (Maybe String) RoutedEmailAddress
+    deriving (Show, Read, Eq)
+
+p_mailbox :: Parser Char Mailbox
+p_mailbox = p_name_addr
+        <|> (Mailbox Nothing . NormalEmailAddress) <$> p_addr_spec
+
+p_name_addr :: Parser Char Mailbox
+p_name_addr = Mailbox
+          <$> pMaybe p_display_name
+          <*  cws
+          <*> p_angle_addr
+
+data EmailAddress = EmailAddress String Domain
+    deriving (Show, Read, Eq)
+
+data RoutedEmailAddress = NormalEmailAddress          EmailAddress
+                        | RoutedEmailAddress [Domain] EmailAddress
+    deriving (Show, Read, Eq)
+
+p_angle_addr :: Parser Char RoutedEmailAddress
+p_angle_addr = ($)
+           <$  pChar '<'
+           <*  cws
+           -- This next makes us also satisfy obs-angle-addr
+           <*> pOptDef NormalEmailAddress
+                       (RoutedEmailAddress <$> p_obs_route <*  cws)
+           <*> p_addr_spec
+           <*  cws
+           <*  pChar '>'
+
+get_addr_spec :: String -> Maybe EmailAddress
+get_addr_spec xs
+ = case parse p_addr_spec xs of
+       Left e -> Just e
+       Right _ -> Nothing
+
+p_addr_spec :: Parser Char EmailAddress
+p_addr_spec  = EmailAddress
+           <$> p_local_part
+           <*  cws
+           <*  pChar '@'
+           <*  cws
+           <*> p_domain
+
+p_display_name :: Parser Char String
+p_display_name = (concat . intersperse " ") <$> p_phrase
+
+p_obs_route :: Parser Char [Domain]
+p_obs_route = id <$> p_obs_domain_list <*  pChar ':'
+
+p_obs_domain_list :: Parser Char [Domain]
+p_obs_domain_list = (:)
+                <$  pChar '@'
+                <*  cws
+                <*> p_domain
+                <*> pMany (    id
+                           <$  pMaybe (() <$  cws <*  pChar ',')
+                           <*  cws
+                           <*  pChar '@'
+                           <*  cws
+                           <*> p_domain)
+
+-----------------------
+-- RFC 2045
+
+data MIMEVersion = MIMEVersion Integer Integer
+    deriving (Show, Read)
+
+get_mime_version :: String -> Maybe MIMEVersion
+get_mime_version xs = case parse ph_mime_version xs of
+                          Left ct -> Just ct
+                          Right _ -> Nothing
+
+ph_mime_version :: Parser Char MIMEVersion
+ph_mime_version = MIMEVersion
+              <$  cws
+              <*> (read <$> pMany (pPred isAsciiDigit))
+              <*  cws
+              <*  pChar '.'
+              <*  cws
+              <*> (read <$> pMany (pPred isAsciiDigit))
+              <*  cws
+              <*  pEOI
+
+data ContentType = ContentType String -- Case insensitive: lower-cased
+                               String -- Case insensitive: lower-cased
+                               [Parameter]
+    deriving (Show, Read)
+data Parameter = Parameter String -- Case insensitive: lower-cased
+                           String
+    deriving (Show, Read)
+
+get_content_type :: String -> Maybe ContentType
+get_content_type xs = case parse ph_content_type xs of
+                          Left ct -> Just ct
+                          Right _ -> Nothing
+
+ph_content_type :: Parser Char ContentType
+ph_content_type = ContentType
+              <$  cws
+              <*> p_type
+              <*  cws
+              <*  pChar '/'
+              <*  cws
+              <*> p_subtype
+              <*> pMany (id
+                     <$  cws
+                     <*  pChar ';'
+                     <*  cws
+                     <*> p_parameter)
+              <*  cws
+              <*  pEOI
+
+
+-- For type and subtypes, allow anything that matches a regexp that
+-- subsumes the currently allowed values
+p_type :: Parser Char String
+p_type = pAtLeast 1 (pPred (\c -> isAsciiAlphaNum c || c `elem` "-.+"))
+
+p_subtype :: Parser Char String
+p_subtype = pAtLeast 1 (pPred (\c -> isAsciiAlphaNum c || c `elem` "-.+"))
+
+{-
+p_type :: Parser Char String
+p_type = p_discrete_type <|> p_composite_type
+
+p_subtype :: Parser Char String
+p_subtype = map asciiToLower <$> (p_extension_token <|> p_iana_token)
+
+p_discrete_type :: Parser Char String
+p_discrete_type = p_ci_string "text"
+              <|> p_ci_string "image"
+              <|> p_ci_string "audio"
+              <|> p_ci_string "video"
+              <|> p_ci_string "application"
+              <|> map asciiToLower <$> p_extension_token
+
+p_composite_type :: Parser Char String
+p_composite_type = p_ci_string "message"
+               <|> p_ci_string "multipart"
+               <|> map asciiToLower <$> p_extension_token
+
+p_iana_token :: Parser Char String
+p_iana_token = pFail
+-}
+
+p_extension_token :: Parser Char String
+p_extension_token = p_ietf_token <|> p_x_token
+
+p_ietf_token :: Parser Char String
+p_ietf_token = pFail
+
+p_x_token :: Parser Char String
+p_x_token = (\x t -> x:'-':t)
+        <$> (pChar 'X' <|> pChar 'x')
+        <*  pChar '-'
+        <*> p_token
+
+p_parameter :: Parser Char Parameter
+p_parameter = Parameter
+          <$> p_attribute
+          <*  cws
+          <*  pChar '='
+          <*  cws
+          <*> p_value
+
+p_attribute :: Parser Char String
+p_attribute = map asciiToLower <$> p_token
+
+p_value :: Parser Char String
+p_value = p_token <|> p_quoted_string
+
+p_token :: Parser Char String
+p_token = pAtLeast 1 (pPred isAscii <!> (p_SP <|> p_CTL <|> p_tspecials))
+
+p_tspecials :: Parser Char Char
+p_tspecials = pPred (`elem` "()<>@,;:\\\"/[]?=")
+
+-----
+
+data ContentTransferEncoding
+        = ContentTransferEncoding String -- Case insensitive: lower-cased
+    deriving (Show, Read)
+
+get_content_transfer_encoding :: String -> Maybe ContentTransferEncoding
+get_content_transfer_encoding xs
+ = case parse ph_content_transfer_encoding xs of
+       Left cte -> Just cte
+       Right _ -> Nothing
+
+ph_content_transfer_encoding :: Parser Char ContentTransferEncoding
+ph_content_transfer_encoding
+    = ContentTransferEncoding
+  <$  cws
+  <*> p_mechanism
+  <*  cws
+  <*  pEOI
+
+p_mechanism :: Parser Char String
+p_mechanism = p_ci_string "7bit"
+          <|> p_ci_string "8bit"
+          <|> p_ci_string "binary"
+          <|> p_ci_string "quoted-printable"
+          <|> p_ci_string "base64"
+          <|> map asciiToLower <$> p_ietf_token
+          <|> map asciiToLower <$> p_x_token
+
+data ContentID = ContentID MessageID
+    deriving (Show, Read)
+
+get_content_id :: String -> Maybe ContentID
+get_content_id xs
+ = case parse ph_content_id xs of
+       Left ci -> Just ci
+       Right _ -> Nothing
+
+ph_content_id :: Parser Char ContentID
+ph_content_id
+    = ContentID
+  <$  cws
+  <*> p_msg_id
+  <*  cws
+  <*  pEOI
+
+data ContentDescription = ContentDescription String
+    deriving (Show, Read)
+
+get_content_description :: String -> Maybe ContentDescription
+get_content_description xs
+ = case parse ph_content_description xs of
+       Left cd -> Just cd
+       Right _ -> Nothing
+
+ph_content_description :: Parser Char ContentDescription
+ph_content_description
+    = ContentDescription
+  <$  cws
+  <*> p_text -- would be pMany, but p_text already does that for us
+  <*  cws
+  <*  pEOI
+
+-----------------------
+-- RFC 2046
+
+-- Not really a header as such
+
+get_boundary :: String -> Maybe String
+get_boundary xs
+ = case parse p_boundary xs of
+       Left b -> Just b
+       Right _ -> Nothing
+
+-- We are very flexible here
+p_boundary :: Parser Char String
+p_boundary = (\ss b bs -> dropFromEndWhile (' ' ==) (ss ++ [b] ++ bs))
+         <$> pMany (pChar ' ')
+         <*> p_bchars
+         <*> pMany p_bchars
+
+p_bchars :: Parser Char Char
+p_bchars = p_bcharsnospace <|> pChar ' '
+
+p_bcharsnospace :: Parser Char Char
+p_bcharsnospace = pPred (\c -> isAsciiAlphaNum c || c `elem` "'()+_,-./:=?")
+
diff --git a/Codec/MIME/String/Internal/ABNF.hs b/Codec/MIME/String/Internal/ABNF.hs
new file mode 100644
--- /dev/null
+++ b/Codec/MIME/String/Internal/ABNF.hs
@@ -0,0 +1,173 @@
+
+module Codec.MIME.String.Internal.ABNF
+      (Parser, apply, parse,
+       pPred, pSucceed, pFail, pEOI, (<*>), (<|>), (<| ), (<!>), nested_parse,
+       pChar, pString, (<$>), (<$ ), (<* ), ( |>),
+       pMany, pAtLeast, pAtMost, pExactly, pFromTo, pOptDef, pMaybe
+      ) where
+
+import Data.Word
+
+newtype Parser inp res = Parser ([(inp, Pos)] -> ParseResult inp res)
+
+data ParseResult inp res = Success res [(inp, Pos)] !Pos
+                         | Fail !Pos
+
+type Line = Integer
+type Column = Integer
+data Pos = Pos !Line !Column
+         | EOI
+    deriving (Eq, Ord)
+
+get_pos :: [(a, Pos)] -> Pos
+get_pos [] = EOI
+get_pos ((_, p):_) = p
+
+show_pos :: Pos -> String
+show_pos EOI = "End of input"
+show_pos (Pos l c) = "Line " ++ show l ++ ", column " ++ show c
+
+infixl 6 <$>, <$, <*>, <*
+infixr 3 <|>, <|, |>
+
+posify :: String -> [(Char, Pos)]
+posify = f 1 1
+    where f _ _ []        = []
+          f l c ('\n':xs) = ('\n', Pos l c):f (l+1) 1     xs
+          f l c (x   :xs) = (x,    Pos l c):f l     (c+1) xs
+
+apply :: Parser Char a -> String -> Either (a, String) String
+apply (Parser p) xs
+ = case p $ posify xs of
+       Success res ys _ -> Left (res, map fst ys)
+       Fail pos -> Right ("Error: Failed at " ++ show_pos pos)
+
+parse :: Parser Char a -> String -> Either a String
+parse (Parser p) xs
+ = case p $ posify xs of
+       Success res [] _ -> Left res
+       Success _ ((_, pos):_) _ ->
+           Right ("Error: Only consumed up to " ++ show_pos pos)
+       Fail pos ->
+           Right ("Error: Failed at " ++ show_pos pos)
+
+-- Primitive combinators
+
+pPred :: (inp -> Bool) -> Parser inp inp
+pPred p = Parser
+        $ \inp -> case inp of
+                      ((x, pos):inp')
+                       | p x -> Success x inp' pos
+                      _ -> Fail (get_pos inp)
+
+pSucceed :: res -> Parser a res
+pSucceed x = Parser $ \inp -> Success x inp (get_pos inp)
+
+pFail :: Parser a res
+pFail = Parser $ \inp -> Fail (get_pos inp)
+
+pEOI :: Parser a ()
+pEOI = Parser $ \inp -> case inp of
+                            [] -> Success () [] EOI
+                            _ -> Fail (get_pos inp)
+
+(<*>) :: Parser inp (a -> b) -> Parser inp a -> Parser inp b
+Parser p <*> Parser q = Parser $ \inp ->
+                        case p inp of
+                            Fail pos -> Fail pos
+                            Success f inp' pos ->
+                                case q inp' of
+                                    Fail pos' -> Fail (pos `max` pos')
+                                    Success x inp'' pos' ->
+                                        Success (f x) inp'' (pos `max` pos')
+
+(<|>) :: Parser inp a -> Parser inp a -> Parser inp a
+Parser p <|> Parser q = Parser $ \inp ->
+                        case (p inp, q inp) of
+                            (Fail posp, Fail posq) -> Fail (posp `max` posq)
+                            (Fail posp, Success x inp' posq) ->
+                                Success x inp' (posp `max` posq)
+                            (Success x inp' posp, Fail posq) ->
+                                Success x inp' (posp `max` posq)
+                            (rp@(Success _ _ posp), rq@(Success _ _ posq))
+                                -> if posp >= posq then rp else rq
+
+(<| ) :: Parser inp a -> Parser inp a -> Parser inp a
+Parser p <|  Parser q = Parser $ \inp ->
+                        case p inp of
+                            Fail posp ->
+                                case q inp of
+                                    Fail posq -> Fail (posp `max` posq)
+                                    Success x inp' posq ->
+                                        Success x inp' (posp `max` posq)
+                            s -> s
+
+(<!>) :: Parser inp a -> Parser inp b -> Parser inp a
+Parser p <!> Parser q = Parser $ \inp -> case q inp of
+                                             Fail _ ->
+                                                 p inp
+                                             Success _ _ pos -> Fail pos
+
+check_fails_empty :: Parser inp a -> ()
+check_fails_empty (Parser p) = case p [] of
+                                   Fail _ -> ()
+                                   _ -> error "check_fails_empty failed"
+
+nested_parse :: Parser Char String -> Parser Char a -> Parser Char a
+nested_parse (Parser p1) (Parser p2)
+ = Parser
+ $ \inp -> case p1 inp of
+               Fail pos -> Fail pos
+               Success inp' rem_inp pos ->
+                   case p2 $ posify inp' of
+                       Fail pos' -> Fail (pos `max` pos')
+                       Success x [] pos' -> Success x rem_inp (pos `max` pos')
+                       Success _ _ pos' -> Fail (pos `max` pos')
+
+-- Derived combinators
+
+pChar :: Char -> Parser Char Char
+pChar c = pPred (c ==)
+
+pString :: String -> Parser Char String
+pString "" = pSucceed ""
+pString (c:cs) = (:) <$> pChar c <*> pString cs
+
+(<$>) :: (a -> b) -> Parser inp a -> Parser inp b
+x <$> q = pSucceed x <*> q
+
+(<$ ) :: a -> Parser inp b -> Parser inp a
+x <$  q = pSucceed x <*  q
+
+(<* ) :: Parser inp a -> Parser inp b -> Parser inp a
+p <*  q = (\x _ -> x) <$> p <*> q
+
+( |>) :: Parser inp a -> Parser inp a -> Parser inp a
+p  |> q = q <|  p
+
+pMany :: Parser inp a -> Parser inp [a]
+pMany p = check_fails_empty p `seq` ((:) <$> p <*> pMany p) <|  pSucceed []
+
+pAtLeast :: Word -> Parser inp a -> Parser inp [a]
+pAtLeast 0 p = pMany p
+pAtLeast n p = ((:) <$> p <*> pAtLeast (n-1) p)
+
+pAtMost :: Word -> Parser inp a -> Parser inp [a]
+pAtMost 0 _ = pSucceed []
+pAtMost n p = ((:) <$> p <*> pAtMost (n-1) p) <|  pSucceed []
+
+pExactly :: Word -> Parser inp a -> Parser inp [a]
+pExactly 0 _ = pSucceed []
+pExactly n p = ((:) <$> p <*> pExactly (n-1) p)
+
+pFromTo :: Word -> Word -> Parser inp a -> Parser inp [a]
+pFromTo 0 t p = pAtMost t p
+pFromTo _ 0 _ = error "Codec.MIME.String.Internal.ABNF.pFromTo: Bad arguments"
+pFromTo f t p = ((:) <$> p <*> pFromTo (f-1) (t-1) p)
+
+pOptDef :: a -> Parser inp a -> Parser inp a
+pOptDef x p = p <|  pSucceed x
+
+pMaybe :: Parser inp a -> Parser inp (Maybe a)
+pMaybe p = Just <$> p <|  pSucceed Nothing
+
diff --git a/Codec/MIME/String/Internal/Utils.hs b/Codec/MIME/String/Internal/Utils.hs
new file mode 100644
--- /dev/null
+++ b/Codec/MIME/String/Internal/Utils.hs
@@ -0,0 +1,58 @@
+
+module Codec.MIME.String.Internal.Utils where
+
+import Data.Char
+
+isAsciiDigit :: Char -> Bool
+isAsciiDigit c = isAscii c && isDigit c
+
+isAsciiHexDigit :: Char -> Bool
+isAsciiHexDigit c = isAscii c && isHexDigit c
+
+isAsciiPrint :: Char -> Bool
+isAsciiPrint c = isAscii c && isPrint c
+
+isAsciiAlpha :: Char -> Bool
+isAsciiAlpha c = isAscii c && isAlpha c
+
+isAsciiAlphaNum :: Char -> Bool
+isAsciiAlphaNum c = isAscii c && isAlphaNum c
+
+asciiToLower :: Char -> Char
+asciiToLower c | isAscii c = toLower c
+               | otherwise = c
+
+asciiToUpper :: Char -> Char
+asciiToUpper c | isAscii c = toUpper c
+               | otherwise = c
+
+splits :: Int -> [a] -> [[a]]
+splits _ [] = []
+splits n xs = case splitAt n xs of
+                  (ys, zs) -> ys:splits n zs
+
+box :: a -> [a]
+box x = [x]
+
+dropFromEndWhile :: (a -> Bool) -> [a] -> [a]
+dropFromEndWhile p = foldr (\x xs -> if null xs && p x then [] else x:xs) []
+
+-- We are generous about what we allow as line terminators. Any of
+-- \r \n \r\n \n\r will satisfy us.
+my_lines :: String -> [String]
+my_lines xs = case get_line xs of
+                  (ys, Nothing) -> [ys]
+                  (ys, Just zs) -> ys:my_lines zs
+
+get_line :: String -> (String, Maybe String)
+get_line xs = case break is_cr_or_lf xs of
+                  (ys, z1:z2:zs)
+                   | is_cr_or_lf z2 && z1 /= z2 -> (ys, Just zs)
+                  (ys, _:zs) -> (ys, Just zs)
+                  (ys, "") -> (ys, Nothing)
+
+is_cr_or_lf :: Char -> Bool
+is_cr_or_lf '\r' = True
+is_cr_or_lf '\n' = True
+is_cr_or_lf _ = False
+
diff --git a/Codec/MIME/String/Parse.hs b/Codec/MIME/String/Parse.hs
new file mode 100644
--- /dev/null
+++ b/Codec/MIME/String/Parse.hs
@@ -0,0 +1,309 @@
+
+module Codec.MIME.String.Parse where
+
+import Text.Iconv
+import qualified Codec.Binary.Base64.String as Base64 (decode)
+import Codec.MIME.String.ContentDisposition
+      (
+       ContentDisposition(ContentDisposition), get_content_disposition,
+       DispositionType(Inline, Attachment),
+       DispositionParameter(..),
+      )
+import Codec.MIME.String.Date (get_date)
+import Codec.MIME.String.Headers
+      (
+       ContentTransferEncoding(ContentTransferEncoding),
+       get_content_transfer_encoding,
+       ContentType(ContentType), get_content_type,
+       get_content_description, get_boundary,
+       Parameter(Parameter),
+       MIMEVersion(MIMEVersion), get_mime_version,
+       Subject(Subject),
+       get_subject, get_from, get_to,
+      )
+import qualified Codec.MIME.String.QuotedPrintable as QuotedPrintable (decode)
+import Codec.MIME.String.Types
+      (
+       ParseM,
+       Header(Header, h_raw_header, h_raw_name, h_name, h_body),
+       Headers,
+       Message(Message),
+       MessageInfo(..),
+       Multipart(Multipart),
+       MessageContent(NoContent, Mixed, Alternative,
+                      Parallel, Digest, RFC822),
+       mkData, mkBody,
+       digest_content_type, ascii_text_content_type,
+      )
+import Codec.MIME.String.Internal.Utils
+
+import Control.Monad (liftM)
+import Control.Monad.State (evalState, get, put)
+import Data.Maybe (fromMaybe)
+
+mkMessage :: MessageInfo -> ParseM MessageContent -> ParseM Message
+mkMessage mi f_mc
+ = do pn <- get
+      put (pn + 1)
+      -- This is done slightly oddly so we get numbering in the natural
+      -- order, which in turn is important when looking for a part number
+      mc <- f_mc
+      return $ Message pn mi mc
+
+parse :: String -> Message
+parse msg = evalState (parse_message msg) 1
+
+parse_message :: String -> ParseM Message
+parse_message msg
+ = let (headers, m_body) = parse_headers msg
+   in case get_header headers "mime-version:" get_mime_version of
+          -- We only try and be clever if the MIME version is 1.0
+          Just (MIMEVersion 1 0) ->
+              parse_mime_message ascii_text_content_type msg
+          _ ->
+              do let m_from = get_header headers "from:" get_from
+                     m_subject = get_header headers "subject:" (Just . Subject)
+                     m_to = get_header headers "to:" get_to
+                     m_date = get_header headers "date:" get_date
+                     mi = MessageInfo {
+                              mi_headers = headers,
+                              mi_from = m_from,
+                              mi_subject = m_subject,
+                              mi_to = m_to,
+                              mi_date = m_date,
+                              mi_content_description = Nothing
+                          }
+                     mc = case m_body of
+                              Just body ->
+                                  mkBody ascii_text_content_type "unknown"
+                                         (convertAsciiToUtf8 body)
+                              Nothing ->
+                                  return $ NoContent ascii_text_content_type
+                 mkMessage mi mc
+
+convertAsciiToUtf8 :: String -> String
+convertAsciiToUtf8 xs
+ = case convert Fuzzy "US-ASCII" "utf8" xs of
+   ConversionSuccess xs' -> xs'
+   ConversionFailed ->
+       error "convertAsciiToUtf8: Can't happen XXX: Fuzzy conversion failed"
+   ConversionUnsupported ->
+       error "convertAsciiToUtf8: Can't happen XXX: Conversion unsupported"
+
+parse_mime_message :: ContentType -> String -> ParseM Message
+parse_mime_message def_content_type msg
+ = let (headers, m_body) = parse_headers msg
+       mi = make_mime_message_info headers
+       mc = case m_body of
+                Nothing ->
+                    do let m_ct = get_header headers "content-type:"
+                                                  get_content_type
+                           content_type = fromMaybe def_content_type m_ct
+                       return $ NoContent content_type
+                Just body ->
+                    make_mime_message_content def_content_type headers body
+   in mkMessage mi mc
+
+make_mime_message_info :: Headers -> MessageInfo
+make_mime_message_info headers
+ = let m_content_description = get_header headers "content-description:"
+                                               get_content_description
+       m_from = get_header headers "from:" get_from
+       m_subject = get_header headers "subject:" get_subject
+       m_to = get_header headers "to:" get_to
+       m_date = get_header headers "date:" get_date
+   in MessageInfo {
+          mi_headers = headers,
+          mi_from = m_from,
+          mi_subject = m_subject,
+          mi_to = m_to,
+          mi_date = m_date,
+          mi_content_description = m_content_description
+      }
+
+make_mime_message_content :: ContentType -> Headers -> String
+                          -> ParseM MessageContent
+make_mime_message_content def_content_type headers body
+ = -- We accept illegal combinations of content type and
+   -- encoding, so just decode whatever it says
+   case m_decoded_body of
+       Nothing ->
+           mkData (Just cte) content_type filename body
+       Just decoded_body ->
+           case content_disposition of
+               ContentDisposition Attachment _ ->
+                   mkData Nothing content_type filename decoded_body
+               ContentDisposition Inline _ ->
+                   case content_type of
+                   ContentType "text" _ ps ->
+                       let charset = fromMaybe "US-ASCII"
+                                   $ lookup_param "charset" ps
+                       in case convert Fuzzy charset "utf8" decoded_body of
+                          ConversionSuccess xs ->
+                              mkBody content_type filename xs
+                          ConversionUnsupported ->
+                              mkData Nothing content_type filename decoded_body
+                          ConversionFailed ->
+                              error "make_mime_message_content: Fuzzy conversion failed: Can't happen XXX"
+                   ContentType "multipart" st ps
+                    | Just raw_b <- lookup_param "boundary" ps,
+                      Just b <- get_boundary raw_b
+                       -> do let (preamble, parts, epilogue)
+                                     = get_parts b decoded_body
+                                 ct = case st of
+                                          "digest" -> digest_content_type
+                                          _ -> ascii_text_content_type
+                                 constr = case st of
+                                              "alternative" -> Alternative
+                                              "parallel" -> Parallel
+                                              "digest" -> Digest
+                                              _ -> Mixed
+                             ms <- mapM (parse_mime_message ct) parts
+                             return $ constr (Multipart preamble ms epilogue)
+                   ContentType "message" "rfc822" _ ->
+                       liftM (RFC822 decoded_body filename)
+                             (parse_message decoded_body)
+                   -- Anything else is treated like an
+                   -- application/octet-stream
+                   _ -> mkData Nothing content_type filename decoded_body
+ where m_cd = get_header headers "content-disposition:"
+                              get_content_disposition
+       content_disposition = fromMaybe (ContentDisposition Inline []) m_cd
+       m_ct = get_header headers "content-type:"
+                              get_content_type
+       content_type = fromMaybe def_content_type m_ct
+       filename = get_filename content_disposition content_type
+       m_ce = get_header headers "content-transfer-encoding:"
+                              get_content_transfer_encoding
+       cte@(ContentTransferEncoding content_transfer_encoding)
+           = fromMaybe (ContentTransferEncoding "7bit") m_ce
+       m_decoded_body
+        = if content_transfer_encoding == "base64" then
+              Just (Base64.decode body)
+          else if content_transfer_encoding == "quoted-printable" then
+              Just (QuotedPrintable.decode $ my_lines body)
+          else if content_transfer_encoding `elem`
+                                    ["7bit", "8bit", "binary"] then
+              -- Don't worry if 8-bit data is in 7-bit transfer-encoded data
+              Just body
+          else Nothing
+
+get_filename :: ContentDisposition -> ContentType -> FilePath
+get_filename (ContentDisposition _ params) (ContentType _ _ params')
+ = loop params
+    where loop [] = -- Look up legacy name parameter in content type
+                    case lookup_param "name" params' of
+                        Nothing -> default_filename
+                        Just f -> sanitise f
+          loop (Filename f:_) = sanitise f
+          loop (_:ps) = loop ps
+          sanitise f = case reverse $ takeWhile ('/' /=) $ reverse f of
+                           "" -> default_filename
+                           f' -> f'
+          default_filename = "unknown"
+
+get_parts :: String -> String -> (String, [String], String)
+get_parts boundary body
+ = case gps [] body of
+       (pre:parts, epi) -> (pre, parts, epi)
+       _ -> error "Parse.get_parts: Can't happen XXX"
+    where gps acc "" = ([from_acc acc], "")
+          gps acc xs
+           = case fmap (dropWhile isWSP) $ after dd_boundary
+                                         $ snd $ read_line_ending xs of
+                 Just "--" -> ([from_acc acc], "")
+                 Just ('-':'-':cs@(c:_))
+                  | is_cr_or_lf c -> ([from_acc acc], cs)
+                 Just "" -> ([from_acc acc], "")
+                 Just (c1:cs)
+                       | is_cr_or_lf c1
+                          -> let cs' = case cs of
+                                           c2:cs''
+                                            | is_cr_or_lf c2 && c1 /= c2
+                                               -> cs''
+                                           _ -> cs
+                             in case gps [] cs' of
+                                    (ps, ep) -> (from_acc acc:ps, ep)
+                 _ -> case read_line_ending xs of
+                          (le, xs') ->
+                              case break is_cr_or_lf xs' of
+                                  (ys, zs) -> gps (ys:le:acc) zs
+          from_acc acc = concat $ reverse acc
+          read_line_ending cs = case cs of
+                                    (c1:cs1)
+                                     | is_cr_or_lf c1 ->
+                                        case cs1 of
+                                            (c2:cs2)
+                                             | is_cr_or_lf c2 && c1 /= c2 ->
+                                                ([c1, c2], cs2)
+                                            _ -> ([c1], cs1)
+                                    _ -> ("", cs)
+          after "" ys = Just ys
+          after (x:xs) (y:ys)
+           | x == y = after xs ys
+           | otherwise = Nothing
+          after (_:_) "" = Nothing
+          dd_boundary = "--" ++ boundary
+          isWSP ' ' = True
+          isWSP '\t' = True
+          isWSP _ = False
+
+-- Returns the value of the first parameter of the name (must be lower cased)
+lookup_param :: String -> [Parameter] -> Maybe String
+lookup_param _ [] = Nothing
+lookup_param name (Parameter n v:ps)
+ | name == n = Just v
+ | otherwise = lookup_param name ps
+
+get_header :: Headers -> String -> (String -> Maybe a) -> Maybe a
+get_header hs name getter
+ = case filter ((name ==) . h_name) hs of
+       [h] -> getter (h_body h)
+       _ -> Nothing
+
+-- We skip over leading white space lines, both for resilience and
+-- because this is allowed for MIME part headers.
+parse_headers :: String -> (Headers, Maybe String)
+parse_headers msg = skip_whitespace (Just msg)
+    where skip_whitespace :: Maybe String -> ([Header], Maybe String)
+          skip_whitespace Nothing = ([], Nothing)
+          skip_whitespace (Just xs) = case get_line xs of
+                                          ([], m_zs) -> ([], m_zs)
+                                          (ys, m_zs) ->
+                                              case dropWhile isWhite ys of
+                                                  [] -> skip_whitespace m_zs
+                                                  ys' -> gather [ys'] m_zs
+          gather :: [String] -> Maybe String -> ([Header], Maybe String)
+          gather acc Nothing = ([mk_header acc], Nothing)
+          gather acc (Just xs)
+              = case get_line xs of
+                    ([], m_zs) -> ([mk_header acc], m_zs)
+                    (ys, m_zs)
+                     | starts_with_white ys -> gather (ys:acc) m_zs
+                     | otherwise -> let (hs, m_rest) = gather [ys] m_zs
+                                    in (mk_header acc:hs, m_rest)
+          starts_with_white (c:_) = isWhite c
+          starts_with_white [] = False
+          isWhite ' ' = True
+          isWhite '\t' = True
+          isWhite _ = False
+
+-- Takes the lines comprising a header (in reverse order) and constructs
+-- the corresponding Header
+mk_header :: [String] -> Header
+mk_header xs = let unfolded = concat xs'
+                   (raw_name, body) = case break ends_header unfolded of
+                                          (name, ':':val) -> (name ++ ":", val)
+                                          (name, val) -> (name, val)
+               in Header {
+                      h_raw_header = xs',
+                      h_raw_name = raw_name,
+                      h_name = map asciiToLower raw_name,
+                      h_body = body
+                  }
+    where ends_header ':' = True
+          ends_header ' ' = True
+          ends_header '\t' = True
+          ends_header _ = False
+          xs' = reverse xs
+
diff --git a/Codec/MIME/String/QuotedPrintable.hs b/Codec/MIME/String/QuotedPrintable.hs
new file mode 100644
--- /dev/null
+++ b/Codec/MIME/String/QuotedPrintable.hs
@@ -0,0 +1,60 @@
+
+-- Defined in RFC 2045.
+-- We assume we have US-ASCII characters.
+-- We return a string with native '\n' line endings.
+
+module Codec.MIME.String.QuotedPrintable (encode, decode) where
+
+import Codec.MIME.String.Internal.Utils
+import Data.Bits
+import Data.Char
+import Data.List
+
+encode :: [String] -> String
+encode = enc 0
+
+-- The Int is the number of characters on this line so far
+-- 76 is the maximum we can have no one line, and 3 is the most
+-- generated for 1 input char (but we also need space for a trailing
+-- '=' for a soft line break).
+enc :: Int -> [String] -> String
+enc _ [] = ""
+enc _ [[]] = ""
+enc _ ([]:ls) = '\n':enc 0 ls
+enc n ls | n > 72 = '=':'\n':enc 0 ls
+enc n ((c:cs):ls)
+ | (33 <= o && o <= 126 && o /= 61) ||
+   (not (null cs) && (o == 9 || o == 32))  = c:enc (n+1) (cs:ls)
+ | otherwise                               = '=':x1:x2:enc (n+3) (cs:ls)
+    where o = ord c
+          x1 = toUpper $ intToDigit (o `shiftR` 4)
+          x2 = toUpper $ intToDigit (o .&. 0xF)
+
+-- decode is very forgiving, and makes some best guesses
+decode :: [String] -> String
+decode = dec . concat . intersperse "\n"
+       . removeSoftLinebreaks
+       . map (dropFromEndWhile is_tab_space)
+    where is_tab_space ' ' = True
+          is_tab_space '\t' = True
+          is_tab_space _ = False
+          breakLast "" = ("", "")
+          breakLast [x] = ("", [x])
+          breakLast (x:xs) = case breakLast xs of
+                                 (ys, zs) -> (x:ys, zs)
+          removeSoftLinebreaks [] = []
+          removeSoftLinebreaks (x:xs)
+              = case breakLast x of
+                    (x', "=") ->
+                        case removeSoftLinebreaks xs of
+                            [] -> [x']
+                            (y:ys) -> (x' ++ y):ys
+                    _ -> x:removeSoftLinebreaks xs
+
+dec :: String -> String
+dec ('=':c1:c2:cs)
+ | isAsciiHexDigit c1 && isAsciiHexDigit c2
+    = chr ((digitToInt c1 `shiftL` 4)  + digitToInt c2):dec cs
+dec (c:cs) = c:dec cs
+dec "" = ""
+
diff --git a/Codec/MIME/String/Types.hs b/Codec/MIME/String/Types.hs
new file mode 100644
--- /dev/null
+++ b/Codec/MIME/String/Types.hs
@@ -0,0 +1,82 @@
+
+module Codec.MIME.String.Types
+      (
+       ParseM, PartNumber, Headers,
+       Header(Header, h_raw_header, h_raw_name, h_name, h_body),
+       Message(..), MessageInfo(..), Multipart(Multipart),
+       MessageContent(NoContent, Body, Data, Mixed, Alternative,
+                      Parallel, Digest, RFC822),
+       mkData, mkBody,
+       digest_content_type, ascii_text_content_type,
+      )
+      where
+
+import Codec.MIME.String.Date (FullDate)
+import Codec.MIME.String.Headers
+      (
+       ContentType(ContentType), ContentDescription, ContentTransferEncoding,
+       Parameter(Parameter), From, To, Subject,
+      )
+import Codec.MIME.String.Internal.Utils (my_lines)
+
+import Control.Monad.State (State)
+
+type Headers = [Header]
+data Header = Header {
+                  h_raw_header :: [String],
+                  h_raw_name :: String,
+                  h_name :: String,
+                  h_body :: String
+              }
+    deriving (Show, Read)
+
+data MessageInfo = MessageInfo {
+                       mi_headers :: Headers,
+                       mi_from :: Maybe From,
+                       mi_to :: Maybe To,
+                       mi_subject :: Maybe Subject,
+                       mi_date :: Maybe FullDate,
+                       mi_content_description :: Maybe ContentDescription
+                   }
+    deriving (Show, Read)
+data MessageContent = NoContent ContentType
+                    | Body ContentType FilePath String -- UTF-8 text
+                    | Data (Maybe ContentTransferEncoding)
+                           ContentType FilePath String -- 8-bit data
+                    | Mixed       Multipart
+                    | Alternative Multipart
+                    | Parallel    Multipart
+                    | Digest      Multipart
+                    | RFC822 String{- unparsed message-} FilePath Message
+    deriving (Show, Read)
+
+type ParseM a = State PartNumber a
+
+mkBody :: ContentType -> FilePath -> String -> ParseM MessageContent
+mkBody ct fp s = return $ Body ct fp $ unlines $ my_lines s
+
+mkData :: Maybe ContentTransferEncoding -> ContentType -> FilePath
+       -> String -> ParseM MessageContent
+mkData m_cte ct fp s = return $ Data m_cte ct fp s
+
+data Multipart = Multipart String    -- Preamble
+                           [Message] -- The messages themselves
+                           String    -- Epilogue
+    deriving (Show, Read)
+
+type PartNumber = Integer
+
+data Message = Message {
+                   m_part_number :: PartNumber,
+                   m_message_info :: MessageInfo,
+                   m_message_content :: MessageContent
+               }
+    deriving (Show, Read)
+
+ascii_text_content_type :: ContentType
+ascii_text_content_type
+ = ContentType "text" "plain" [Parameter "charset" "US-ASCII"]
+
+digest_content_type :: ContentType
+digest_content_type = ContentType "message" "rfc822" []
+
diff --git a/GPL-2 b/GPL-2
new file mode 100644
--- /dev/null
+++ b/GPL-2
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year  name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,8 @@
+
+module Main (main) where
+
+import Distribution.Simple
+
+main :: IO ()
+main = defaultMain
+
diff --git a/mime-string.cabal b/mime-string.cabal
new file mode 100644
--- /dev/null
+++ b/mime-string.cabal
@@ -0,0 +1,34 @@
+Name:               mime-string
+Version:            0.1
+License:            OtherLicense
+License-File:       COPYING
+Copyright:          Ian Lynagh, 2005, 2007
+Author:             Ian Lynagh
+Maintainer:         igloo@earth.li
+Stability:          experimental
+Homepage:           http://urchin.earth.li/~ian/cabal/mime-string/
+Synopsis:           MIME implementation for String's.
+Description:
+    Implementation of the MIME RFCs 2045-2049.
+    A bit rough around the edges.
+Category:           Codec
+Tested-With:        GHC==6.6
+Build-Depends:      base, mtl, network, iconv, base64-string
+Extensions:         PatternGuards
+Extra-source-files: "BSD3", "GPL-2"
+Exposed-modules:
+    Codec.Binary.EncodingQ.String
+    Codec.MIME.String
+    Codec.MIME.String.ContentDisposition
+    Codec.MIME.String.Date
+    Codec.MIME.String.EncodedWord
+    Codec.MIME.String.Flatten
+    Codec.MIME.String.Headers
+    Codec.MIME.String.Parse
+    Codec.MIME.String.Types
+    Codec.MIME.String.QuotedPrintable
+Other-Modules:
+    Codec.MIME.String.Internal.ABNF
+    Codec.MIME.String.Internal.Utils
+GHC-Options: -O -Wall -Werror
+
