mysql-haskell-1.1.2: src/Database/MySQL/Protocol/Escape.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-|
Module : Database.MySQL.Protocol.Escape
Description : Pure haskell mysql escape
Copyright : (c) Winterland, 2016
License : BSD
Maintainer : drkoster@qq.com
Stability : experimental
Portability : PORTABLE
This module provide escape machinery for bytes and text types.
reference: <http://dev.mysql.com/doc/refman/5.7/en/string-literals.html>
* Escape Sequence Character Represented by Sequence
* \0 An ASCII NUL (X'00') character
* \' A single quote (“'”) character
* \" A double quote (“"”) character
* \b A backspace character
* \n A newline (linefeed) character
* \r A carriage return character
* \t A tab character
* \Z ASCII 26 (Control+Z); see note following the table
* \\ A backslash (“\”) character
* \% A “%” character; see note following the table
* \_ A “_” character; see note following the table
The @\%@ and @\_@ sequences are used to search for literal instances of @%@ and @_@ in pattern-matching contexts where they would otherwise be interpreted as wildcard characters, so we won't auto escape @%@ or @_@ here.
-}
module Database.MySQL.Protocol.Escape where
import Control.Monad (forM_)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Internal as B
import Data.Text (Text)
import qualified Data.Text.Array as TA
import qualified Data.Text.Internal as T
import Data.Word
import Foreign.ForeignPtr (withForeignPtr)
import Foreign.Ptr (Ptr, minusPtr, plusPtr)
import Foreign.Storable (peek, poke, pokeByteOff)
import GHC.IO (unsafeDupablePerformIO)
escapeText :: Text -> Text
#if MIN_VERSION_text(2,0,0)
escapeText (T.Text arr off len)
| len <= 0 = T.empty
| otherwise =
let (arr', len') = TA.run2 $ do
marr <- TA.new (len * 2)
loop arr (off + len) marr off 0
in T.Text arr' 0 len'
where
escape c marr ix = do
TA.unsafeWrite marr ix 92
TA.unsafeWrite marr (ix+1) c
loop oarr oend marr !ix !ix'
| ix == oend = return (marr, ix')
| otherwise = do
let c = TA.unsafeIndex oarr ix
cs = c : [ TA.unsafeIndex oarr (ix+1) | c >= 0xC0 ]
++ [ TA.unsafeIndex oarr (ix+2) | c >= 0xE0 ]
++ [ TA.unsafeIndex oarr (ix+3) | c >= 0xF0 ]
go2 = loop oarr oend marr (ix+1) (ix'+2)
goN = do
forM_ (zip [0..4] cs) $ \(di,c') -> TA.unsafeWrite marr (ix' + di) c'
loop oarr oend marr (ix + length cs) (ix' + length cs)
if | c == 0
|| c == 39
|| c == 34 -> escape c marr ix' >> go2 -- \0 \' \"
| c == 8 -> escape 98 marr ix' >> go2 -- \b
| c == 10 -> escape 110 marr ix' >> go2 -- \n
| c == 13 -> escape 114 marr ix' >> go2 -- \r
| c == 9 -> escape 116 marr ix' >> go2 -- \t
| c == 26 -> escape 90 marr ix' >> go2 -- \Z
| c == 92 -> escape 92 marr ix' >> go2 -- \\
| otherwise -> goN
#else
escapeText (T.Text arr off len)
| len <= 0 = T.empty
| otherwise =
let (arr', len') = TA.run2 $ do
marr <- TA.new (len * 2)
loop arr (off + len) marr off 0
in T.Text arr' 0 len'
where
escape c marr ix = do
TA.unsafeWrite marr ix 92
TA.unsafeWrite marr (ix+1) c
loop oarr oend marr !ix !ix'
| ix == oend = return (marr, ix')
| otherwise = do
let c = TA.unsafeIndex oarr ix
go1 = loop oarr oend marr (ix+1) (ix'+1)
go2 = loop oarr oend marr (ix+1) (ix'+2)
if | c >= 0xD800 && c <= 0xDBFF -> do let c2 = TA.unsafeIndex oarr (ix+1)
TA.unsafeWrite marr ix' c
TA.unsafeWrite marr (ix'+1) c2
loop oarr oend marr (ix+2) (ix'+2)
| c == 0
|| c == 39
|| c == 34 -> escape c marr ix' >> go2 -- \0 \' \"
| c == 8 -> escape 98 marr ix' >> go2 -- \b
| c == 10 -> escape 110 marr ix' >> go2 -- \n
| c == 13 -> escape 114 marr ix' >> go2 -- \r
| c == 9 -> escape 116 marr ix' >> go2 -- \t
| c == 26 -> escape 90 marr ix' >> go2 -- \Z
| c == 92 -> escape 92 marr ix' >> go2 -- \\
| otherwise -> TA.unsafeWrite marr ix' c >> go1
#endif
escapeBytes :: ByteString -> ByteString
escapeBytes (B.PS fp s len) = unsafeDupablePerformIO $ withForeignPtr fp $ \ a ->
B.createUptoN (len * 2) $ \ b -> do
b' <- loop (a `plusPtr` s) (a `plusPtr` s `plusPtr` len) b
return (b' `minusPtr` b)
where
escape :: Word8 -> Ptr Word8 -> IO (Ptr Word8)
escape c p = do
poke p 92
pokeByteOff p 1 c
return (p `plusPtr` 2)
loop !a aend !b
| a == aend = return b
| otherwise = do
c <- peek a
if | c == 0
|| c == 39
|| c == 34 -> escape c b >>= loop (a `plusPtr` 1) aend -- \0 \' \"
| c == 8 -> escape 98 b >>= loop (a `plusPtr` 1) aend -- \b
| c == 10 -> escape 110 b >>= loop (a `plusPtr` 1) aend -- \n
| c == 13 -> escape 114 b >>= loop (a `plusPtr` 1) aend -- \r
| c == 9 -> escape 116 b >>= loop (a `plusPtr` 1) aend -- \t
| c == 26 -> escape 90 b >>= loop (a `plusPtr` 1) aend -- \Z
| c == 92 -> escape 92 b >>= loop (a `plusPtr` 1) aend -- \\
| otherwise -> poke b c >> loop (a `plusPtr` 1) aend (b `plusPtr` 1)