Prechádzať zdrojové kódy

Adaptations for warus spools

Marcos Dumay de Medeiros 7 rokov pred
rodič
commit
45ebe79b90

+ 1 - 1
fcmtp-data.cabal

@@ -58,4 +58,4 @@ library
         Data.SMTP.Types.Address Data.SMTP.Parser.Address
         Data.SMTP.Types.Address Data.SMTP.Parser.Address
         Data.SMTP.Types.URI Data.SMTP.Parser.URI,
         Data.SMTP.Types.URI Data.SMTP.Parser.URI,
         Data.SMTP.Crypto.Types.CP, Data.SMTP.Crypto.Algos.CP
         Data.SMTP.Crypto.Types.CP, Data.SMTP.Crypto.Algos.CP
-    ghc-options: -Wall -fno-warn-unused-do-bind -fwarn-incomplete-patterns -threaded
+    ghc-options: -Wall -fno-warn-unused-do-bind -fwarn-incomplete-patterns

+ 14 - 35
src/Data/SMTP/Mime.hs

@@ -15,14 +15,15 @@ import Data.SMTP.Parser.Mime
 import Data.SMTP.Types.Resource
 import Data.SMTP.Types.Resource
 import Data.SMTP.Parser.Resource
 import Data.SMTP.Parser.Resource
 
 
-import Text.StringConvert
+--import Text.StringConvert
+import qualified Text.StringConvert as SC
 
 
 import qualified Data.Attoparsec.ByteString.Char8.Extras as P
 import qualified Data.Attoparsec.ByteString.Char8.Extras as P
 import qualified Data.Attoparsec.ByteString.Char8 as A
 import qualified Data.Attoparsec.ByteString.Char8 as A
 import qualified Data.Char as C
 import qualified Data.Char as C
 import Data.ByteString (ByteString)
 import Data.ByteString (ByteString)
 import Data.Word8 (Word8)
 import Data.Word8 (Word8)
-import qualified Data.Word8 as W
+--import qualified Data.Word8 as W
 import qualified Data.ByteString as BS
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Char8 as C8
 import qualified Data.ByteString.Char8 as C8
 import qualified Data.ByteString.Lazy as LBS
 import qualified Data.ByteString.Lazy as LBS
@@ -30,21 +31,7 @@ import qualified Data.ByteString.Base64.Lazy as B64
 import qualified Data.ByteString.Lazy.Search as LSearch
 import qualified Data.ByteString.Lazy.Search as LSearch
 --import qualified Data.ByteString.Search as SSearch
 --import qualified Data.ByteString.Search as SSearch
 
 
-import Data.Default.Class
-
-getMimeData :: [Header] -> (ContentTypeHeader, TransferEncoding)
-getMimeData hh = (ct, te)
-  where
-    ct = case getHeader hh contentTypeHeaderName of
-      Nothing -> def
-      Just (Header (_, cth)) -> case A.parseOnly parseContentType (s cth) of
-        Left _ -> def
-        Right (ctv, ctp) -> ContentTypeHeader ctv ctp
-    te = case getHeader hh transferEncodingHeaderName of
-      Nothing -> def
-      Just (Header (_, teh)) -> case A.parseOnly parseTransferEncoding (C8.pack teh) of
-        Left _ -> def
-        Right t -> t
+--import Data.Default.Class
 
 
 {- |
 {- |
 Required encoding for the given data.
 Required encoding for the given data.
@@ -73,13 +60,13 @@ requiredEncoding dt = case requiredFeatures 0 $ LBS.unpack dt of
 
 
 reencodeMessage :: BodyEncoding -> ResourceData -> ResourceData
 reencodeMessage :: BodyEncoding -> ResourceData -> ResourceData
 reencodeMessage toEncoding dt = LBS.concat [
 reencodeMessage toEncoding dt = LBS.concat [
-  if newEncoding == fromEncoding then LBS.fromStrict $ BS.concat dthh else replaceEncodingHeader newEncoding dthh,
+  if newEncoding == fromEncoding then LBS.fromStrict $ originalData hh else replaceEncodingHeader newEncoding hh,
   "\r\n",
   "\r\n",
   reencodeBody'
   reencodeBody'
   ]
   ]
   where
   where
-    (dthh, hh, dtb) = takeHeaders dt
-    (ct, fromEncoding) = getMimeData hh
+    (hh, dtb) = takeHeaders dt
+    (ct, fromEncoding) = getMimeData . public $ hh
     fromEncoding' = transferToBody fromEncoding
     fromEncoding' = transferToBody fromEncoding
     (message, multiPart, separator) = case ct of
     (message, multiPart, separator) = case ct of
       (ContentTypeHeader (MultiPartMime _) (ContentTypeParameters (Just sep) _ _)) -> (False, True, C8.pack sep)
       (ContentTypeHeader (MultiPartMime _) (ContentTypeParameters (Just sep) _ _)) -> (False, True, C8.pack sep)
@@ -128,19 +115,11 @@ concatParts sep = joinChunks (LBS.fromStrict sep)
     joinChunks sep' [c1, c2] = LBS.concat [c1, "\r\n--", sep', "--\r\n", c2]
     joinChunks sep' [c1, c2] = LBS.concat [c1, "\r\n--", sep', "--\r\n", c2]
     joinChunks sep' (c:cc2) = LBS.concat [c, "\r\n--", sep', "\r\n", joinChunks sep' cc2]
     joinChunks sep' (c:cc2) = LBS.concat [c, "\r\n--", sep', "\r\n", joinChunks sep' cc2]
 
 
-replaceEncodingHeader :: TransferEncoding -> [ByteString] -> LBS.ByteString
-replaceEncodingHeader enc = LBS.concat . map LBS.fromStrict . echoAndReplace
+replaceEncodingHeader :: TransferEncoding -> PlainHeaders -> LBS.ByteString
+replaceEncodingHeader enc hh = LBS.fromStrict . originalData $ hh{public = map replaceHeader . public $ hh}
   where
   where
-    h = transferEncodingHeaderName
-    echoAndReplace [] = []
-    echoAndReplace (l:ll) = if isPrefixCI h l
-                            then BS.concat [C8.pack h, ": ", C8.pack . show $ enc, "\r\n"] :
-                                 dropWhile (\x -> BS.null x || (W.isSpace . BS.head $ x)) ll
-                            else l : echoAndReplace ll
-                                 
-
-isPrefixCI :: String -> ByteString -> Bool
-isPrefixCI [] _ = True
-isPrefixCI (c:cc) bs = case BS.uncons bs of
-  Nothing -> False
-  Just (h, t) -> (C.toUpper . C.chr . fromIntegral $ h) == C.toUpper c && isPrefixCI cc t
+    k = transferEncodingHeaderName
+    replaceHeader h = if hasKey k h
+                      then Header{key=k, value=show enc,
+                                  bare = BS.concat [SC.s k, ": ", SC.s . show $ enc, "\r\n"]}
+                      else h

+ 33 - 21
src/Data/SMTP/Parser/Resource.hs

@@ -4,10 +4,12 @@
 module Data.SMTP.Parser.Resource (takeHeaders, parseHeaders) where
 module Data.SMTP.Parser.Resource (takeHeaders, parseHeaders) where
 
 
 import Data.SMTP.Types.Resource
 import Data.SMTP.Types.Resource
+import qualified Data.SMTP.Types.Mime as Mime
 import Text.StringConvert
 import Text.StringConvert
 
 
 import Control.Applicative ((<|>))
 import Control.Applicative ((<|>))
 import Control.Monad
 import Control.Monad
+import Data.Default.Class
 import Data.Attoparsec.ByteString.Char8
 import Data.Attoparsec.ByteString.Char8
 import qualified Data.Attoparsec.ByteString.Char8 as A
 import qualified Data.Attoparsec.ByteString.Char8 as A
 import qualified Data.Attoparsec.ByteString.Lazy as Alz
 import qualified Data.Attoparsec.ByteString.Lazy as Alz
@@ -26,23 +28,32 @@ Returns: (headers data, headers, body)
 Notice that this function chomps the lone CRLF
 Notice that this function chomps the lone CRLF
 that separates the body from the headers.
 that separates the body from the headers.
 -}
 -}
-takeHeaders :: ResourceData -> ([ByteString], [Header], ResourceData)
-takeHeaders dt = case Alz.parse parseHeadersAndReturn dt of
-  Alz.Fail{} -> ([], [], dt)
-  Alz.Done dtb (dth, hh) -> (dth, hh, dtb)
+takeHeaders :: ResourceData -> (PlainHeaders, ResourceData)
+takeHeaders dt = case Alz.parse parseHeaders dt of
+  Alz.Fail{} -> (def, dt)
+  Alz.Done dtb hh -> (hh, dtb)
 
 
 
 
-parseHeaders :: Parser [Header]
-parseHeaders = snd <$> parseHeadersAndReturn
+parseHeaders :: Parser PlainHeaders
+parseHeaders = do
+  (pp, psep) <- parseHeadersGroup
+  let (Mime.ContentTypeHeader ct _) = (fst $ getMimeData pp)
+  if ct == Mime.MessageMime Mime.FcmtpResource
+    then do
+    -- Has sealed headers
+    (ss, ssep) <- parseHeadersGroup
+    return $ PlainHeaders pp ss (psep, ssep)
+    else
+    return $ PlainHeaders pp [] (psep, "")
 
 
-parseHeadersAndReturn :: Parser ([ByteString], [Header])
-parseHeadersAndReturn = (
-  blankLine >> return ([], [])
+parseHeadersGroup :: Parser ([Header], ByteString)
+parseHeadersGroup = (
+  blankLine >>= (\l -> return ([], l))
   ) <|> (
   ) <|> (
   do
   do
-    (dth, h) <- headerAndReturn
-    (dthh, hh) <- parseHeadersAndReturn
-    return (dth:dthh, h:hh)
+    h <- header
+    (hh, l) <- parseHeadersGroup
+    return (h:hh, l)
   )
   )
 
 
 -- header :: Parser Header
 -- header :: Parser Header
@@ -56,16 +67,17 @@ parseHeadersAndReturn = (
 --   let value' = s . BS.reverse . (BS.dropWhile A.isEndOfLine) . BS.reverse $ value
 --   let value' = s . BS.reverse . (BS.dropWhile A.isEndOfLine) . BS.reverse $ value
 --   return $ Header (key', value')
 --   return $ Header (key', value')
 
 
-headerAndReturn :: Parser (ByteString, Header)
-headerAndReturn = do
-  key <- A.takeTill (== ':')
+header :: Parser Header
+header = do
+  k <- A.takeTill (\x -> elem x [':', '\r', '\n']) -- OverloadedStrings!
   char ':'
   char ':'
-  when (BS.null key) $ fail "email header must have a name"
+  when (BS.null k) $ fail "email header must have a name"
   sp <- A.takeWhile isSpace
   sp <- A.takeWhile isSpace
-  value <- scan Value headerValueScanner
-  let key' = s . BS.reverse . BS.dropWhile W.isSpace . BS.reverse . BS.dropWhile W.isSpace $ key
-  let value' = s . BS.reverse . BS.dropWhile A.isEndOfLine . BS.reverse $ value
-  return (BS.concat [key, sp, value], Header (key', value'))
+  v <- scan Value headerValueScanner
+  let k' = s . BS.reverse . BS.dropWhile W.isSpace . BS.reverse . BS.dropWhile W.isSpace $ k
+  let v' = s . BS.reverse . BS.dropWhile A.isEndOfLine . BS.reverse $ v
+  let b = BS.concat [k, ":", sp, v]
+  return $ Header k' v' b
 
 
 data HeaderScanStatus = Value | CR | LF
 data HeaderScanStatus = Value | CR | LF
 
 
@@ -79,7 +91,7 @@ headerValueScanner CR c
   | c == '\n' = Just LF
   | c == '\n' = Just LF
   | otherwise = Nothing
   | otherwise = Nothing
 headerValueScanner LF c
 headerValueScanner LF c
-  | isHorizontalSpace . fromIntegral .C.ord $ c = Just Value
+  | isHorizontalSpace . fromIntegral . C.ord $ c = Just Value
   | otherwise = Nothing
   | otherwise = Nothing
 
 
 blankLine :: Parser ByteString
 blankLine :: Parser ByteString

+ 62 - 17
src/Data/SMTP/Types/Resource.hs

@@ -1,3 +1,5 @@
+{-# LANGUAGE OverloadedStrings #-}
+
 module Data.SMTP.Types.Resource where
 module Data.SMTP.Types.Resource where
 
 
 import qualified Data.SMTP.URI as URI
 import qualified Data.SMTP.URI as URI
@@ -5,15 +7,19 @@ import qualified Data.SMTP.Types.Mime as Mime
 import qualified Data.SMTP.Parser.Mime as PMime
 import qualified Data.SMTP.Parser.Mime as PMime
 import qualified Data.Attoparsec.ByteString as BA
 import qualified Data.Attoparsec.ByteString as BA
 
 
+import qualified Data.ByteString as BS
 import qualified Data.ByteString.Lazy as LBS
 import qualified Data.ByteString.Lazy as LBS
 import qualified Text.StringConvert as SC
 import qualified Text.StringConvert as SC
 
 
 import Data.Maybe
 import Data.Maybe
 import Data.Default.Class
 import Data.Default.Class
 
 
-import Data.Char
+import qualified Data.Char as C
 
 
-data Header = Header (String, String) deriving (Read, Show, Eq, Ord)
+data Header = Header {key :: String, value :: String, bare :: BS.ByteString} deriving (Read, Show, Eq, Ord)
+data PlainHeaders = PlainHeaders {public :: [Header], sealed :: [Header], separators :: (BS.ByteString, BS.ByteString)} deriving (Read, Show, Eq, Ord)
+instance Default PlainHeaders where
+  def = PlainHeaders [] [] ("\r\n", "")
 
 
 data ResourceInfo = ResourceInfo {
 data ResourceInfo = ResourceInfo {
   mimeType :: Mime.ContentType,
   mimeType :: Mime.ContentType,
@@ -27,41 +33,80 @@ instance Default ResourceInfo where
 
 
 type ResourceData = LBS.ByteString
 type ResourceData = LBS.ByteString
 
 
+-- | Creates a header from a key and a value
+makeHeader :: String -> String -> Header
+makeHeader k v = Header k v . SC.s $ k ++ ": " ++ v ++ "\r\n"
+
+-- | True if the header key matches
+hasKey :: String -> Header -> Bool
+hasKey k h = map C.toLower k == key h
+
+-- | Retrieves a single header with the given key
 getHeader :: [Header] -> String -> Maybe Header
 getHeader :: [Header] -> String -> Maybe Header
-getHeader [] _ = Nothing
-getHeader (h@(Header (hk, _)) : hh) k
-  | map toLower hk == map toLower k = Just h
-  | otherwise = getHeader hh k
+getHeader hh k = case getMultiple hh k of
+  [] -> Nothing
+  (h:_) -> Just h
 
 
+-- | Retrieves the value of the header that matches key
 getHeaderValue :: [Header] -> String -> Maybe String
 getHeaderValue :: [Header] -> String -> Maybe String
-getHeaderValue hh h = case getHeader hh h of
-  Just (Header (_, v)) -> Just v
-  Nothing -> Nothing
+getHeaderValue hh k = value <$> getHeader hh k
 
 
+-- | Retrieves all headers that match the key
 getMultiple :: [Header] -> String -> [Header]
 getMultiple :: [Header] -> String -> [Header]
-getMultiple hh h = filter (\(Header (k, _)) -> k == h) hh
+getMultiple hh k = filter (hasKey k) hh
 
 
+-- | Retrieves all the values of the headers that match the key
 getMultipleValue :: [Header] -> String -> [String]
 getMultipleValue :: [Header] -> String -> [String]
-getMultipleValue hh = map (\(Header (_, v)) -> v) . getMultiple hh
+getMultipleValue hh k = map value $ getMultiple hh k
 
 
+-- | fCMTP revision header
 revisionHeader :: String
 revisionHeader :: String
 revisionHeader = "fCMTP-Revision"
 revisionHeader = "fCMTP-Revision"
 
 
+-- | fCMTP base revision header
 baseHeader :: String
 baseHeader :: String
 baseHeader = "fCMTP-Revision-Base"
 baseHeader = "fCMTP-Revision-Base"
 
 
+-- | fCMTP resource URI header
 addressHeader :: String
 addressHeader :: String
 addressHeader = "fCMTP-Address"
 addressHeader = "fCMTP-Address"
 
 
-resourceInfo :: [Header] -> ResourceInfo
-resourceInfo hh = let
-  (mimet, mimep) = fromMaybe (def, def) $ getHeaderValue hh Mime.contentTypeHeaderName >>= bparse PMime.parseContentType
-  curr = fromMaybe URI.NoRevision $ URI.Revision <$> getHeaderValue hh revisionHeader
-  bb = map URI.Revision $ getMultipleValue hh baseHeader
-  uu = catMaybes . map (bparse URI.parseURI) $ getMultipleValue hh addressHeader
+-- | Retrieves the meta information from resource headers
+resourceInfo :: PlainHeaders -> ResourceInfo
+resourceInfo (PlainHeaders _ ss _) = let
+  (mimet, mimep) = fromMaybe (def, def) $ getHeaderValue ss Mime.contentTypeHeaderName >>= bparse PMime.parseContentType
+  curr = fromMaybe URI.NoRevision $ URI.Revision <$> getHeaderValue ss revisionHeader
+  bb = map URI.Revision $ getMultipleValue ss baseHeader
+  uu = catMaybes . map (bparse URI.parseURI) $ getMultipleValue ss addressHeader
   in ResourceInfo mimet mimep curr bb uu
   in ResourceInfo mimet mimep curr bb uu
   where
   where
     bparse :: BA.Parser a -> String -> Maybe a
     bparse :: BA.Parser a -> String -> Maybe a
     bparse p v = eitherToMaybe . BA.parseOnly p $ SC.s v 
     bparse p v = eitherToMaybe . BA.parseOnly p $ SC.s v 
     eitherToMaybe (Left _) = Nothing
     eitherToMaybe (Left _) = Nothing
     eitherToMaybe (Right v) = Just v
     eitherToMaybe (Right v) = Just v
+
+-- | Retrieve MIME information from the resource headers
+getMimeData :: [Header] -> (Mime.ContentTypeHeader, Mime.TransferEncoding)
+getMimeData hh = (ct, te)
+  where
+    ct = case getHeader hh Mime.contentTypeHeaderName of
+      Nothing -> def
+      Just h -> case BA.parseOnly PMime.parseContentType (SC.s . value $ h) of
+        Left _ -> def
+        Right (ctv, ctp) -> Mime.ContentTypeHeader ctv ctp
+    te = case getHeader hh Mime.transferEncodingHeaderName of
+      Nothing -> def
+      Just h -> case BA.parseOnly PMime.parseTransferEncoding (SC.s . value $ h) of
+        Left _ -> def
+        Right t -> t
+
+-- | Recreates the original resource text for the headers
+originalData :: PlainHeaders -> BS.ByteString
+originalData (PlainHeaders pp ss (psep, ssep)) =
+  BS.concat [
+  catData pp, psep,
+  catData ss, ssep
+  ]
+  where
+    catData :: [Header] -> BS.ByteString
+    catData hh = BS.concat . map bare $ hh

+ 10 - 0
src/Data/SMTP/Types/URI.hs

@@ -25,6 +25,7 @@ Returns the entire path of the URI, except for the leading slash (/).
 relativePath :: URI -> String
 relativePath :: URI -> String
 relativePath URI{path=Path p} = intercalate "/" . map uriEncode $ drop 1 p
 relativePath URI{path=Path p} = intercalate "/" . map uriEncode $ drop 1 p
 
 
+-- | Entire textual representation of the URI
 fullURI :: URI -> String
 fullURI :: URI -> String
 fullURI u@(URI{account=a, revision=r, parameters=pp}) =
 fullURI u@(URI{account=a, revision=r, parameters=pp}) =
   concat $ ["FCMTP://", s . fullAccount $ a, fullPath u] ++ (
   concat $ ["FCMTP://", s . fullAccount $ a, fullPath u] ++ (
@@ -45,6 +46,7 @@ uriEncode = N.escapeURIString N.isUnescapedInURIComponent
 instance Show URI where
 instance Show URI where
   show = fullURI
   show = fullURI
 
 
+-- | Retrieves a parameter from the URI by key name
 getParameter :: String -> URI -> Maybe String
 getParameter :: String -> URI -> Maybe String
 getParameter p u = case map (\(Parameter _ vl) -> vl) .
 getParameter p u = case map (\(Parameter _ vl) -> vl) .
                         filter (\(Parameter nm _) -> nm == p) $
                         filter (\(Parameter nm _) -> nm == p) $
@@ -52,3 +54,11 @@ getParameter p u = case map (\(Parameter _ vl) -> vl) .
                      [] -> Nothing
                      [] -> Nothing
                      (v:_) -> Just v
                      (v:_) -> Just v
 
 
+-- | Return revision string or a default value
+fromRevision :: String -> Revision -> String
+fromRevision d NoRevision = d
+fromRevision _ (Revision r) = r
+
+-- | Appends a new segment at the end of the path
+appendPath :: Path -> String -> Path
+appendPath (Path pp) e = Path $ pp ++ [e]