--
-- Minio Haskell SDK, (C) 2017 Minio, Inc.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--

{-# LANGUAGE FlexibleInstances #-}
module Network.Minio.Data.ByteString
  (
    stripBS
  , UriEncodable(..)
  ) where

import qualified Data.ByteString as B
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Char8 as BC8
import qualified Data.ByteString.Lazy as LB
import           Data.Char (isSpace, toUpper, isAsciiUpper, isAsciiLower, isDigit)
import qualified Data.Text as T
import           Numeric (showHex)

import           Lib.Prelude

stripBS :: ByteString -> ByteString
stripBS = BC8.dropWhile isSpace . fst . BC8.spanEnd isSpace

class UriEncodable s where
  uriEncode :: Bool -> s -> ByteString

instance UriEncodable [Char] where
  uriEncode encodeSlash payload =
    LB.toStrict $ BB.toLazyByteString $ mconcat $
    map (`uriEncodeChar` encodeSlash) payload

instance UriEncodable ByteString where
  -- assumes that uriEncode is passed ASCII encoded strings.
  uriEncode encodeSlash bs =
    uriEncode encodeSlash $ BC8.unpack bs

instance UriEncodable Text where
  uriEncode encodeSlash txt =
    uriEncode encodeSlash $ T.unpack txt

-- | URI encode a char according to AWS S3 signing rules - see
-- UriEncode() at
-- https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
uriEncodeChar :: Char -> Bool -> BB.Builder
uriEncodeChar '/' True = BB.byteString "%2F"
uriEncodeChar '/' False = BB.char7 '/'
uriEncodeChar ch _
  | isAsciiUpper ch
    || isAsciiLower ch
    || isDigit ch
    || (ch == '_')
    || (ch == '-')
    || (ch == '.')
    || (ch == '~') = BB.char7 ch
  | otherwise = mconcat $ map f $ B.unpack $ encodeUtf8 $ T.singleton ch
  where
    f :: Word8 -> BB.Builder
    f n = BB.char7 '%' <> BB.string7 hexStr
      where
        hexStr = map toUpper $ showHex q $ showHex r ""
        (q, r) = divMod (fromIntegral n) (16::Word8)