module Darcs.UI.Email
( makeEmail
, readEmail
, formatHeader
, prop_qp_roundtrip
) where
import Darcs.Prelude
import Data.Char ( digitToInt, isHexDigit, ord, intToDigit, isPrint, toUpper )
import Data.List ( isInfixOf )
import Darcs.Util.Printer
( Doc, ($$), (<+>), text, empty, packedString, renderPS )
import Darcs.Util.ByteString ( packStringToUTF8, dropSpace, linesPS, betweenLinesPS )
import qualified Data.ByteString as B (ByteString, length, null, tail
,drop, head, concat, singleton
,pack, append, empty, unpack, snoc
)
import qualified Data.ByteString.Char8 as BC (index, head, pack)
import Data.ByteString.Internal as B (c2w, createAndTrim)
import System.IO.Unsafe ( unsafePerformIO )
import Foreign.Ptr ( Ptr, plusPtr )
import Foreign.Storable ( poke )
import Data.Word ( Word8 )
import Data.Maybe ( fromMaybe )
lineMax, qlineMax :: Int
lineMax :: Int
lineMax = Int
78
qlineMax :: Int
qlineMax = Int
75
formatHeader :: String -> String -> B.ByteString
String
headerName String
headerValue =
ByteString -> ByteString -> ByteString
B.append ByteString
nameColon ByteString
encodedValue
where nameColon :: ByteString
nameColon = [Word8] -> ByteString
B.pack ((Char -> Word8) -> String -> [Word8]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Word8
B.c2w (String
headerName String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
":"))
encodedValue :: ByteString
encodedValue = String -> Int -> Bool -> Bool -> ByteString
foldAndEncode (Char
' 'Char -> String -> String
forall a. a -> [a] -> [a]
:String
headerValue)
(ByteString -> Int
B.length ByteString
nameColon) Bool
False Bool
False
foldAndEncode :: String -> Int -> Bool -> Bool -> B.ByteString
foldAndEncode :: String -> Int -> Bool -> Bool -> ByteString
foldAndEncode [] Int
_ Bool
_ Bool
_ = ByteString
B.empty
foldAndEncode String
s Int
p Bool
lastWordEncoded Bool
inMidWord =
let newline :: ByteString
newline = Word8 -> ByteString
B.singleton Word8
10
space :: ByteString
space = Word8 -> ByteString
B.singleton Word8
32
s2bs :: String -> ByteString
s2bs = [Word8] -> ByteString
B.pack ([Word8] -> ByteString)
-> (String -> [Word8]) -> String -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Word8) -> String -> [Word8]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Word8
B.c2w
safeEncChunkLength :: Int
safeEncChunkLength = (Int
qlineMax Int -> Int -> Int
forall a. Num a => a -> a -> a
- ByteString -> Int
B.length ByteString
encodedWordStart
Int -> Int -> Int
forall a. Num a => a -> a -> a
- ByteString -> Int
B.length ByteString
encodedWordEnd) Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
12
(String
curSpace, String
afterCurSpace) = (Char -> Bool) -> String -> (String, String)
forall a. (a -> Bool) -> [a] -> ([a], [a])
span (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
' ') String
s
(String
curWord, String
afterCurWord) = (Char -> Bool) -> String -> (String, String)
forall a. (a -> Bool) -> [a] -> ([a], [a])
break (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
' ') String
afterCurSpace
qEncWord :: ByteString
qEncWord | Bool
lastWordEncoded = String -> ByteString
qEncode (String
curSpace String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
curWord)
| Bool
otherwise = String -> ByteString
qEncode String
curWord
mustEncode :: Bool
mustEncode = Bool
inMidWord
Bool -> Bool -> Bool
|| (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\Char
c -> Bool -> Bool
not (Char -> Bool
isPrint Char
c) Bool -> Bool -> Bool
|| Char -> Int
ord Char
c Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
127) String
curWord
Bool -> Bool -> Bool
|| String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
curWord Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
lineMax Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1
Bool -> Bool -> Bool
|| String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
isInfixOf String
"=?" String
curWord
mustFold :: Bool
mustFold
| Bool
mustEncode Bool -> Bool -> Bool
&& Bool
lastWordEncoded
= Int
p Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ ByteString -> Int
B.length ByteString
qEncWord Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
lineMax
| Bool
mustEncode
= Int
p Int -> Int -> Int
forall a. Num a => a -> a -> a
+ String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
curSpace Int -> Int -> Int
forall a. Num a => a -> a -> a
+ ByteString -> Int
B.length ByteString
qEncWord Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
lineMax
| Bool
otherwise
= Int
p Int -> Int -> Int
forall a. Num a => a -> a -> a
+ String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
curSpace Int -> Int -> Int
forall a. Num a => a -> a -> a
+ String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
curWord Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
lineMax
mustSplit :: Bool
mustSplit = (ByteString -> Int
B.length ByteString
qEncWord Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
qlineMax Bool -> Bool -> Bool
&& Bool
mustEncode)
Bool -> Bool -> Bool
|| String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
curWord Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
lineMax Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1
spaceToInsert :: ByteString
spaceToInsert | Bool
mustEncode Bool -> Bool -> Bool
&& Bool
lastWordEncoded = ByteString
space
| Bool
otherwise = String -> ByteString
s2bs String
curSpace
wordToInsert :: ByteString
wordToInsert
| Bool
mustEncode Bool -> Bool -> Bool
&& Bool
mustSplit = String -> ByteString
qEncode (Int -> String -> String
forall a. Int -> [a] -> [a]
take Int
safeEncChunkLength String
curWord)
| Bool
mustEncode = ByteString
qEncWord
| Bool
otherwise = String -> ByteString
s2bs String
curWord
doneChunk :: ByteString
doneChunk | Bool
mustFold = [ByteString] -> ByteString
B.concat [ByteString
newline, ByteString
spaceToInsert, ByteString
wordToInsert]
| Bool
otherwise = [ByteString] -> ByteString
B.concat [ByteString
spaceToInsert, ByteString
wordToInsert]
(String
rest, Int
nextP)
| Bool
mustSplit
= (Int -> String -> String
forall a. Int -> [a] -> [a]
drop Int
safeEncChunkLength String
curWord String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
afterCurWord, Int
qlineMax Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)
| Bool
mustEncode Bool -> Bool -> Bool
&& Bool
mustFold
= (String
afterCurWord, ByteString -> Int
B.length ByteString
spaceToInsert Int -> Int -> Int
forall a. Num a => a -> a -> a
+ ByteString -> Int
B.length ByteString
wordToInsert)
| Bool
otherwise
= (String
afterCurWord, Int
p Int -> Int -> Int
forall a. Num a => a -> a -> a
+ ByteString -> Int
B.length ByteString
doneChunk)
in ByteString -> ByteString -> ByteString
B.append ByteString
doneChunk (String -> Int -> Bool -> Bool -> ByteString
foldAndEncode String
rest Int
nextP Bool
mustEncode Bool
mustSplit)
qEncode :: String -> B.ByteString
qEncode :: String -> ByteString
qEncode String
s = [ByteString] -> ByteString
B.concat [ByteString
encodedWordStart,
ByteString
encodedString,
ByteString
encodedWordEnd]
where encodedString :: ByteString
encodedString = [ByteString] -> ByteString
B.concat ((Char -> ByteString) -> String -> [ByteString]
forall a b. (a -> b) -> [a] -> [b]
map Char -> ByteString
qEncodeChar String
s)
encodedWordStart, encodedWordEnd :: B.ByteString
encodedWordStart :: ByteString
encodedWordStart = [Word8] -> ByteString
B.pack ((Char -> Word8) -> String -> [Word8]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Word8
B.c2w String
"=?UTF-8?Q?")
encodedWordEnd :: ByteString
encodedWordEnd = [Word8] -> ByteString
B.pack ((Char -> Word8) -> String -> [Word8]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Word8
B.c2w String
"?=")
qEncodeChar :: Char -> B.ByteString
qEncodeChar :: Char -> ByteString
qEncodeChar Char
c
| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
' ' = Char -> ByteString
c2bs Char
'_'
| Char -> Bool
isPrint Char
c
Bool -> Bool -> Bool
&& Char
c Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` String
"?=_"
Bool -> Bool -> Bool
&& Char -> Int
ord Char
c Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
128 = Char -> ByteString
c2bs Char
c
| Bool
otherwise = [ByteString] -> ByteString
B.concat
((Word8 -> ByteString) -> [Word8] -> [ByteString]
forall a b. (a -> b) -> [a] -> [b]
map Word8 -> ByteString
qbyte
(ByteString -> [Word8]
B.unpack
(String -> ByteString
packStringToUTF8 [Char
c])))
where c2bs :: Char -> ByteString
c2bs = Word8 -> ByteString
B.singleton (Word8 -> ByteString) -> (Char -> Word8) -> Char -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Word8
B.c2w
qbyte :: Word8 -> ByteString
qbyte Word8
b = [Word8] -> ByteString
B.pack ((Char -> Word8) -> String -> [Word8]
forall a b. (a -> b) -> [a] -> [b]
map Char -> Word8
B.c2w [Char
'='
,Word8 -> Char
word8ToUDigit (Word8
b Word8 -> Word8 -> Word8
forall a. Integral a => a -> a -> a
`div` Word8
16)
,Word8 -> Char
word8ToUDigit (Word8
b Word8 -> Word8 -> Word8
forall a. Integral a => a -> a -> a
`mod` Word8
16)
])
word8ToUDigit :: Word8 -> Char
word8ToUDigit :: Word8 -> Char
word8ToUDigit = Char -> Char
toUpper (Char -> Char) -> (Word8 -> Char) -> Word8 -> Char
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Char
intToDigit (Int -> Char) -> (Word8 -> Int) -> Word8 -> Char
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word8 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral
qpencode :: B.ByteString -> B.ByteString
qpencode :: ByteString -> ByteString
qpencode ByteString
s = IO ByteString -> ByteString
forall a. IO a -> a
unsafePerformIO
(IO ByteString -> ByteString) -> IO ByteString -> ByteString
forall a b. (a -> b) -> a -> b
$ Int -> (Ptr Word8 -> IO Int) -> IO ByteString
B.createAndTrim (Int
4 Int -> Int -> Int
forall a. Num a => a -> a -> a
* ByteString -> Int
B.length ByteString
s) (\Ptr Word8
buf -> ByteString -> Int -> Ptr Word8 -> Int -> IO Int
encode ByteString
s Int
qlineMax Ptr Word8
buf Int
0)
encode :: B.ByteString -> Int -> Ptr Word8 -> Int -> IO Int
encode :: ByteString -> Int -> Ptr Word8 -> Int -> IO Int
encode ByteString
ps Int
_ Ptr Word8
_ Int
bufi | ByteString -> Bool
B.null ByteString
ps = Int -> IO Int
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Int
bufi
encode ByteString
ps Int
n Ptr Word8
buf Int
bufi = case HasCallStack => ByteString -> Word8
ByteString -> Word8
B.head ByteString
ps of
Word8
c | Word8
c Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
newline ->
do Ptr Word8 -> Word8 -> IO ()
forall a. Storable a => Ptr a -> a -> IO ()
poke (Ptr Word8
buf Ptr Word8 -> Int -> Ptr Word8
forall a b. Ptr a -> Int -> Ptr b
`plusPtr` Int
bufi) Word8
newline
ByteString -> Int -> Ptr Word8 -> Int -> IO Int
encode ByteString
ps' Int
qlineMax Ptr Word8
buf (Int
bufiInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1)
| Int
n Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 Bool -> Bool -> Bool
&& ByteString -> Int
B.length ByteString
ps Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
1 ->
do Ptr Word8 -> Word8 -> IO ()
forall a. Storable a => Ptr a -> a -> IO ()
poke (Ptr Word8
buf Ptr Word8 -> Int -> Ptr Word8
forall a b. Ptr a -> Int -> Ptr b
`plusPtr` Int
bufi) Word8
equals
Ptr Word8 -> Word8 -> IO ()
forall a. Storable a => Ptr a -> a -> IO ()
poke (Ptr Word8
buf Ptr Word8 -> Int -> Ptr Word8
forall a b. Ptr a -> Int -> Ptr b
`plusPtr` (Int
bufiInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1)) Word8
newline
ByteString -> Int -> Ptr Word8 -> Int -> IO Int
encode ByteString
ps Int
qlineMax Ptr Word8
buf (Int
bufi Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
2)
| Word8
c Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
tab Bool -> Bool -> Bool
|| Word8
c Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
space ->
if ByteString -> Bool
B.null ByteString
ps' Bool -> Bool -> Bool
|| HasCallStack => ByteString -> Word8
ByteString -> Word8
B.head ByteString
ps' Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
newline
then do Ptr Word8 -> Word8 -> IO ()
forall a. Storable a => Ptr a -> a -> IO ()
poke (Ptr Word8
buf Ptr Word8 -> Int -> Ptr Word8
forall a b. Ptr a -> Int -> Ptr b
`plusPtr` Int
bufi) Word8
c
Ptr Word8 -> Word8 -> IO ()
forall a. Storable a => Ptr a -> a -> IO ()
poke (Ptr Word8
buf Ptr Word8 -> Int -> Ptr Word8
forall a b. Ptr a -> Int -> Ptr b
`plusPtr` (Int
bufiInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1)) Word8
equals
Ptr Word8 -> Word8 -> IO ()
forall a. Storable a => Ptr a -> a -> IO ()
poke (Ptr Word8
buf Ptr Word8 -> Int -> Ptr Word8
forall a b. Ptr a -> Int -> Ptr b
`plusPtr` (Int
bufiInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
2)) Word8
newline
ByteString -> Int -> Ptr Word8 -> Int -> IO Int
encode ByteString
ps' Int
qlineMax Ptr Word8
buf (Int
bufi Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
3)
else do Ptr Word8 -> Word8 -> IO ()
forall a. Storable a => Ptr a -> a -> IO ()
poke (Ptr Word8
buf Ptr Word8 -> Int -> Ptr Word8
forall a b. Ptr a -> Int -> Ptr b
`plusPtr` Int
bufi) Word8
c
ByteString -> Int -> Ptr Word8 -> Int -> IO Int
encode ByteString
ps' (Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) Ptr Word8
buf (Int
bufi Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)
| Word8
c Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
>= Word8
bang Bool -> Bool -> Bool
&& Word8
c Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
/= Word8
equals Bool -> Bool -> Bool
&& Word8
c Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
/= Word8
period Bool -> Bool -> Bool
&& Word8
c Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word8
tilde ->
do Ptr Word8 -> Word8 -> IO ()
forall a. Storable a => Ptr a -> a -> IO ()
poke (Ptr Word8
buf Ptr Word8 -> Int -> Ptr Word8
forall a b. Ptr a -> Int -> Ptr b
`plusPtr` Int
bufi) Word8
c
ByteString -> Int -> Ptr Word8 -> Int -> IO Int
encode ByteString
ps' (Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) Ptr Word8
buf (Int
bufi Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1)
| Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
3 ->
ByteString -> Int -> Ptr Word8 -> Int -> IO Int
encode ByteString
ps Int
0 Ptr Word8
buf Int
bufi
| Bool
otherwise ->
do let (Word8
x, Word8
y) = Word8
c Word8 -> Word8 -> (Word8, Word8)
forall a. Integral a => a -> a -> (a, a)
`divMod` Word8
16
h1 :: Word8
h1 = Word8 -> Word8
intToUDigit Word8
x
h2 :: Word8
h2 = Word8 -> Word8
intToUDigit Word8
y
Ptr Word8 -> Word8 -> IO ()
forall a. Storable a => Ptr a -> a -> IO ()
poke (Ptr Word8
buf Ptr Word8 -> Int -> Ptr Word8
forall a b. Ptr a -> Int -> Ptr b
`plusPtr` Int
bufi) Word8
equals
Ptr Word8 -> Word8 -> IO ()
forall a. Storable a => Ptr a -> a -> IO ()
poke (Ptr Word8
buf Ptr Word8 -> Int -> Ptr Word8
forall a b. Ptr a -> Int -> Ptr b
`plusPtr` (Int
bufiInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1)) Word8
h1
Ptr Word8 -> Word8 -> IO ()
forall a. Storable a => Ptr a -> a -> IO ()
poke (Ptr Word8
buf Ptr Word8 -> Int -> Ptr Word8
forall a b. Ptr a -> Int -> Ptr b
`plusPtr` (Int
bufiInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
2)) Word8
h2
ByteString -> Int -> Ptr Word8 -> Int -> IO Int
encode ByteString
ps' (Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
3) Ptr Word8
buf (Int
bufi Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
3)
where ps' :: ByteString
ps' = HasCallStack => ByteString -> ByteString
ByteString -> ByteString
B.tail ByteString
ps
newline :: Word8
newline = Char -> Word8
B.c2w Char
'\n'
tab :: Word8
tab = Char -> Word8
B.c2w Char
'\t'
space :: Word8
space = Char -> Word8
B.c2w Char
' '
bang :: Word8
bang = Char -> Word8
B.c2w Char
'!'
tilde :: Word8
tilde = Char -> Word8
B.c2w Char
'~'
equals :: Word8
equals = Char -> Word8
B.c2w Char
'='
period :: Word8
period = Char -> Word8
B.c2w Char
'.'
intToUDigit :: Word8 -> Word8
intToUDigit Word8
i
| Word8
i Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
>= Word8
0 Bool -> Bool -> Bool
&& Word8
i Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word8
9 = Char -> Word8
B.c2w Char
'0' Word8 -> Word8 -> Word8
forall a. Num a => a -> a -> a
+ Word8
i
| Word8
i Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
>= Word8
10 Bool -> Bool -> Bool
&& Word8
i Word8 -> Word8 -> Bool
forall a. Ord a => a -> a -> Bool
<= Word8
15 = Char -> Word8
B.c2w Char
'A' Word8 -> Word8 -> Word8
forall a. Num a => a -> a -> a
+ Word8
i Word8 -> Word8 -> Word8
forall a. Num a => a -> a -> a
- Word8
10
| Bool
otherwise = String -> Word8
forall a. HasCallStack => String -> a
error (String -> Word8) -> String -> Word8
forall a b. (a -> b) -> a -> b
$ String
"intToUDigit: '"String -> String -> String
forall a. [a] -> [a] -> [a]
++Word8 -> String
forall a. Show a => a -> String
show Word8
iString -> String -> String
forall a. [a] -> [a] -> [a]
++String
"'not a digit"
qpdecode :: B.ByteString -> B.ByteString
qpdecode :: ByteString -> ByteString
qpdecode ByteString
s = IO ByteString -> ByteString
forall a. IO a -> a
unsafePerformIO
(IO ByteString -> ByteString) -> IO ByteString -> ByteString
forall a b. (a -> b) -> a -> b
$ Int -> (Ptr Word8 -> IO Int) -> IO ByteString
B.createAndTrim (ByteString -> Int
B.length ByteString
s Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) (\Ptr Word8
buf -> [ByteString] -> Ptr Word8 -> Int -> IO Int
decode (ByteString -> [ByteString]
linesPS ByteString
s) Ptr Word8
buf Int
0)
decode :: [B.ByteString] -> Ptr Word8 -> Int -> IO Int
decode :: [ByteString] -> Ptr Word8 -> Int -> IO Int
decode [] Ptr Word8
_ Int
bufi = Int -> IO Int
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Int
bufi
decode (ByteString
ps:[ByteString]
pss) Ptr Word8
buf Int
bufi
| ByteString -> Bool
B.null (ByteString -> ByteString
dropSpace ByteString
ps)
= do Ptr Word8 -> Word8 -> IO ()
forall a. Storable a => Ptr a -> a -> IO ()
poke (Ptr Word8
buf Ptr Word8 -> Int -> Ptr Word8
forall a b. Ptr a -> Int -> Ptr b
`plusPtr` Int
bufi) Word8
newline
[ByteString] -> Ptr Word8 -> Int -> IO Int
decode [ByteString]
pss Ptr Word8
buf (Int
bufiInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1)
| Bool
is_equals Bool -> Bool -> Bool
&& ByteString -> Int
B.length ByteString
ps Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
3 Bool -> Bool -> Bool
&& Char -> Bool
isHexDigit Char
c1 Bool -> Bool -> Bool
&& Char -> Bool
isHexDigit Char
c2
= do Ptr Word8 -> Word8 -> IO ()
forall a. Storable a => Ptr a -> a -> IO ()
poke (Ptr Word8
buf Ptr Word8 -> Int -> Ptr Word8
forall a b. Ptr a -> Int -> Ptr b
`plusPtr` Int
bufi)
(Int -> Word8
toWord8 (Int -> Word8) -> Int -> Word8
forall a b. (a -> b) -> a -> b
$ Char -> Int
digitToInt Char
c1 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
16 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Char -> Int
digitToInt Char
c2)
[ByteString] -> Ptr Word8 -> Int -> IO Int
decode (Int -> ByteString -> ByteString
B.drop Int
3 ByteString
psByteString -> [ByteString] -> [ByteString]
forall a. a -> [a] -> [a]
:[ByteString]
pss) Ptr Word8
buf (Int
bufiInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1)
| Bool
is_equals Bool -> Bool -> Bool
&& ByteString -> Bool
B.null (ByteString -> ByteString
dropSpace (HasCallStack => ByteString -> ByteString
ByteString -> ByteString
B.tail ByteString
ps)) = [ByteString] -> Ptr Word8 -> Int -> IO Int
decode [ByteString]
pss Ptr Word8
buf Int
bufi
| Bool
otherwise = do Ptr Word8 -> Word8 -> IO ()
forall a. Storable a => Ptr a -> a -> IO ()
poke (Ptr Word8
buf Ptr Word8 -> Int -> Ptr Word8
forall a b. Ptr a -> Int -> Ptr b
`plusPtr` Int
bufi) (HasCallStack => ByteString -> Word8
ByteString -> Word8
B.head ByteString
ps)
[ByteString] -> Ptr Word8 -> Int -> IO Int
decode (HasCallStack => ByteString -> ByteString
ByteString -> ByteString
B.tail ByteString
psByteString -> [ByteString] -> [ByteString]
forall a. a -> [a] -> [a]
:[ByteString]
pss) Ptr Word8
buf (Int
bufiInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1)
where is_equals :: Bool
is_equals = ByteString -> Char
BC.head ByteString
ps Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'='
c1 :: Char
c1 = ByteString -> Int -> Char
BC.index ByteString
ps Int
1
c2 :: Char
c2 = ByteString -> Int -> Char
BC.index ByteString
ps Int
2
newline :: Word8
newline = Char -> Word8
B.c2w Char
'\n'
toWord8 :: Int -> Word8
toWord8 :: Int -> Word8
toWord8 = Int -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral
makeEmail :: String -> [(String, String)] -> Maybe Doc -> Maybe String -> Doc -> Maybe String -> Doc
makeEmail :: String
-> [(String, String)]
-> Maybe Doc
-> Maybe String
-> Doc
-> Maybe String
-> Doc
makeEmail String
repodir [(String, String)]
headers Maybe Doc
mcontents Maybe String
mcharset Doc
bundle Maybe String
mfilename =
String -> Doc
text String
"DarcsURL:" Doc -> Doc -> Doc
<+> String -> Doc
text String
repodir
Doc -> Doc -> Doc
$$ (Doc -> (String, String) -> Doc)
-> Doc -> [(String, String)] -> Doc
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl (\Doc
m (String
h,String
v) -> Doc
m Doc -> Doc -> Doc
$$ (String -> Doc
text (String
h String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
":") Doc -> Doc -> Doc
<+> String -> Doc
text String
v)) Doc
empty [(String, String)]
headers
Doc -> Doc -> Doc
$$ String -> Doc
text String
"MIME-Version: 1.0"
Doc -> Doc -> Doc
$$ String -> Doc
text String
"Content-Type: multipart/mixed; boundary=\"=_\""
Doc -> Doc -> Doc
$$ String -> Doc
text String
""
Doc -> Doc -> Doc
$$ String -> Doc
text String
"--=_"
Doc -> Doc -> Doc
$$ (case Maybe Doc
mcontents of
Just Doc
contents ->
String -> Doc
text (String
"Content-Type: text/plain; charset=\"" String -> String -> String
forall a. [a] -> [a] -> [a]
++
String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
"x-unknown" Maybe String
mcharset String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"\"")
Doc -> Doc -> Doc
$$ String -> Doc
text String
"Content-Transfer-Encoding: quoted-printable"
Doc -> Doc -> Doc
$$ String -> Doc
text String
""
Doc -> Doc -> Doc
$$ ByteString -> Doc
packedString (ByteString -> ByteString
qpencode (Doc -> ByteString
renderPS Doc
contents))
Doc -> Doc -> Doc
$$ String -> Doc
text String
""
Doc -> Doc -> Doc
$$ String -> Doc
text String
"--=_"
Maybe Doc
Nothing -> Doc
empty)
Doc -> Doc -> Doc
$$ String -> Doc
text String
"Content-Type: text/x-darcs-patch; name=\"patch-preview.txt\""
Doc -> Doc -> Doc
$$ String -> Doc
text String
"Content-Disposition: inline"
Doc -> Doc -> Doc
$$ String -> Doc
text String
"Content-Transfer-Encoding: quoted-printable"
Doc -> Doc -> Doc
$$ String -> Doc
text String
"Content-Description: Patch preview"
Doc -> Doc -> Doc
$$ String -> Doc
text String
""
Doc -> Doc -> Doc
$$ (case ByteString -> ByteString -> ByteString -> Maybe ByteString
betweenLinesPS (String -> ByteString
BC.pack String
"New patches:") (String -> ByteString
BC.pack String
"Context:") (Doc -> ByteString
renderPS Doc
bundle) of
Just ByteString
s -> ByteString -> Doc
packedString (ByteString -> Doc) -> ByteString -> Doc
forall a b. (a -> b) -> a -> b
$ ByteString -> ByteString
qpencode ByteString
s
Maybe ByteString
Nothing -> ByteString -> Doc
packedString (ByteString -> Doc) -> ByteString -> Doc
forall a b. (a -> b) -> a -> b
$ ByteString -> ByteString
qpencode (ByteString -> ByteString) -> ByteString -> ByteString
forall a b. (a -> b) -> a -> b
$ Doc -> ByteString
renderPS Doc
bundle)
Doc -> Doc -> Doc
$$ String -> Doc
text String
"--=_"
Doc -> Doc -> Doc
$$ String -> Doc
text String
"Content-Type: application/x-darcs-patch" Doc -> Doc -> Doc
forall a. Semigroup a => a -> a -> a
<>
(case Maybe String
mfilename of
Just String
filename -> String -> Doc
text String
"; name=\"" Doc -> Doc -> Doc
forall a. Semigroup a => a -> a -> a
<> String -> Doc
text String
filename Doc -> Doc -> Doc
forall a. Semigroup a => a -> a -> a
<> String -> Doc
text String
"\""
Maybe String
Nothing -> Doc
empty)
Doc -> Doc -> Doc
$$ String -> Doc
text String
"Content-Transfer-Encoding: quoted-printable"
Doc -> Doc -> Doc
$$ String -> Doc
text String
"Content-Disposition: attachment"
Doc -> Doc -> Doc
$$ String -> Doc
text String
"Content-Description: A darcs patch for your repository!"
Doc -> Doc -> Doc
$$ String -> Doc
text String
""
Doc -> Doc -> Doc
$$ ByteString -> Doc
packedString (ByteString -> ByteString
qpencode (Doc -> ByteString
renderPS Doc
bundle))
Doc -> Doc -> Doc
$$ String -> Doc
text String
"--=_--"
Doc -> Doc -> Doc
$$ String -> Doc
text String
""
Doc -> Doc -> Doc
$$ String -> Doc
text String
"."
Doc -> Doc -> Doc
$$ String -> Doc
text String
""
Doc -> Doc -> Doc
$$ String -> Doc
text String
""
readEmail :: B.ByteString -> B.ByteString
readEmail :: ByteString -> ByteString
readEmail ByteString
s =
case ByteString -> ByteString -> ByteString -> Maybe ByteString
betweenLinesPS
(String -> ByteString
BC.pack String
"Content-Description: A darcs patch for your repository!")
(String -> ByteString
BC.pack String
"--=_--") ByteString
s of
Maybe ByteString
Nothing -> ByteString
s
Just ByteString
s' -> ByteString -> ByteString
qpdecode ByteString
s'
prop_qp_roundtrip :: B.ByteString -> Bool
prop_qp_roundtrip :: ByteString -> Bool
prop_qp_roundtrip ByteString
s = ByteString -> Word8 -> ByteString
B.snoc ByteString
s Word8
10 ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== (ByteString -> ByteString
qpdecode (ByteString -> ByteString)
-> (ByteString -> ByteString) -> ByteString -> ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString
qpencode) ByteString
s