--------------------------------------------------------------------
-- |
-- Module    : Codec.MIME.QuotedPrintable
-- Copyright : (c) 2006-2009, Galois, Inc. 
-- License   : BSD3
--
-- Maintainer: Sigbjorn Finne <sigbjorn.finne@gmail.com>
-- Stability : provisional
-- Portability:
--
-- To and from QP content encoding.
--
--------------------------------------------------------------------
module Codec.MIME.QuotedPrintable 
       ( decode -- :: String -> String
       , encode -- :: String -> String
       ) where

import Data.Char

-- | 'decode' incoming quoted-printable content, stripping
-- out soft line breaks and translating @=XY@ sequences
-- into their decoded byte\/octet. The output encoding\/representation 
-- is still a String, not a sequence of bytes.
decode :: String -> String
decode :: String -> String
decode String
"" = String
""
decode (Char
'=':Char
'\r':Char
'\n':String
xs) = String -> String
decode String
xs -- soft line break.
decode (Char
'=':Char
x1:Char
x2:String
xs)
 | Char -> Bool
isHexDigit Char
x1 Bool -> Bool -> Bool
&& Char -> Bool
isHexDigit Char
x2 =
    Int -> Char
chr (Char -> Int
digitToInt Char
x1 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
x2) Char -> String -> String
forall a. a -> [a] -> [a]
: String -> String
decode String
xs
decode (Char
'=':String
xs) = Char
'='Char -> String -> String
forall a. a -> [a] -> [a]
:String -> String
decode String
xs
              -- make it explicit that we propagate other '=' occurrences.
decode (Char
x1:String
xs) = Char
x1Char -> String -> String
forall a. a -> [a] -> [a]
:String -> String
decode String
xs

-- | 'encode' converts a sequence of characeter _octets_ into
-- quoted-printable form; suitable for transmission in MIME
-- payloads. Note the stress on _octets_; it is assumed that
-- you have already converted Unicode into a <=8-bit encoding
-- (UTF-8, most likely.)
encode :: String -> String
encode :: String -> String
encode String
xs = Int -> String -> String
encodeLength Int
0 String
xs

-- | @encodeLength llen str@ is the worker function during encoding.
-- The extra argument @llen@ tracks the current column for the line
-- being processed. Soft line breaks are inserted if a line exceeds
-- a max length.
encodeLength :: Int -> String -> String
encodeLength :: Int -> String -> String
encodeLength Int
_ String
"" = String
""
encodeLength Int
n (Char
x:String
xs)
 | Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
72  = Char
'='Char -> String -> String
forall a. a -> [a] -> [a]
:Char
'\r'Char -> String -> String
forall a. a -> [a] -> [a]
:Char
'\n'Char -> String -> String
forall a. a -> [a] -> [a]
:Int -> String -> String
encodeLength Int
0 (Char
xChar -> String -> String
forall a. a -> [a] -> [a]
:String
xs)
encodeLength Int
_ (Char
'=':String
xs) 
 = Char
'='Char -> String -> String
forall a. a -> [a] -> [a]
:Char
'3'Char -> String -> String
forall a. a -> [a] -> [a]
:Char
'D'Char -> String -> String
forall a. a -> [a] -> [a]
:Int -> String -> String
encodeLength Int
0 String
xs
encodeLength Int
n (Char
x:String
xs)
 | Int
ox Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
0x100 = String -> String
forall a. HasCallStack => String -> a
error (String
"QuotedPrintable.encode: encountered > 8 bit character: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ (Char, Int) -> String
forall a. Show a => a -> String
show (Char
x,Int
ox))
 | Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
72     = Char
'='Char -> String -> String
forall a. a -> [a] -> [a]
:Char
'\r'Char -> String -> String
forall a. a -> [a] -> [a]
:Char
'\n'Char -> String -> String
forall a. a -> [a] -> [a]
:Int -> String -> String
encodeLength Int
0 (Char
xChar -> String -> String
forall a. a -> [a] -> [a]
:String
xs)
 | Int
ox Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
0x21 Bool -> Bool -> Bool
&& Int
ox Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
0x7e = Char
x Char -> String -> String
forall a. a -> [a] -> [a]
: Int -> String -> String
encodeLength (Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1) String
xs
 | Int
ox Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0x09 Bool -> Bool -> Bool
|| Int
ox Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0x20 = Char
x Char -> String -> String
forall a. a -> [a] -> [a]
: Int -> String -> String
encodeLength (Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
1) String
xs
 | Bool
otherwise = Char
'='Char -> String -> String
forall a. a -> [a] -> [a]
:Int -> Char
showH (Int
ox Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
0x10)Char -> String -> String
forall a. a -> [a] -> [a]
: Int -> Char
showH (Int
ox Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
0x10)Char -> String -> String
forall a. a -> [a] -> [a]
:Int -> String -> String
encodeLength (Int
nInt -> Int -> Int
forall a. Num a => a -> a -> a
+Int
3) String
xs
 where
  ox :: Int
ox = Char -> Int
ord Char
x
  showH :: Int -> Char
showH Int
v
   | Int
v Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
10    = Int -> Char
chr (Int
ord_0 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
v)
   | Bool
otherwise = Int -> Char
chr (Int
ord_A Int -> Int -> Int
forall a. Num a => a -> a -> a
+ (Int
vInt -> Int -> Int
forall a. Num a => a -> a -> a
-Int
10))
   
  ord_0 :: Int
ord_0 = Char -> Int
ord Char
'0'
  ord_A :: Int
ord_A = Char -> Int
ord Char
'A'