packages feed

mariadb-haskell-0.1.0.0: src/Database/MySQL/Protocol/Escape.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}

{-|
Module      : Database.MySQL.Protocol.Escape
Description : Low-level SQL escaping; prefer session-aware public APIs
Copyright   : (c) Winterland, 2016
License     : BSD
Maintainer  : Anton Antich <anton.antich@gmail.com>
Stability   : experimental
Portability : PORTABLE

Low-level backslash escaping for byte/text literals. These helpers do not
observe the connection's SQL mode or charset, and are insufficient when
@NO_BACKSLASH_ESCAPES@ is enabled. Application code should use managed prepared
bindings or session-aware text parameters from "Database.MySQL.Base".

Pattern wildcards @%@ and @_@ are intentionally left unchanged: SQL literal
quoting and LIKE-pattern escaping are separate application decisions.

-}

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)