wai-app-file-cgi-3.2.2: Network/Wai/Application/Classic/Lang.hs
{-# LANGUAGE OverloadedStrings #-}
module Network.Wai.Application.Classic.Lang (parseLang) where
import Control.Applicative hiding (optional)
import Data.Attoparsec.ByteString (Parser, parseOnly, takeWhile)
import Data.Attoparsec.ByteString.Char8 (
char,
count,
digit,
option,
sepBy1,
space,
string,
)
import Data.ByteString.Char8 hiding (
count,
filter,
map,
notElem,
take,
takeWhile,
)
import qualified Data.ByteString.Char8 as B8 (all, null)
import Data.Char (isAsciiLower, isAsciiUpper, isDigit)
import Data.List (sortBy)
import Data.Ord
import Prelude hiding (takeWhile)
-- | Languages are used as file suffixes, so only safe ones are kept.
--
-- >>> parseLang "en-gb;q=0.8, en;q=0.7, da"
-- ["da","en-gb","en"]
-- >>> parseLang "ja;q=0, ../../x, *, en"
-- ["en"]
parseLang :: ByteString -> [ByteString]
parseLang bs = case parseOnly acceptLanguage bs of
Right ls -> map fst $ sortBy detrimental $ filter acceptable ls
_ -> []
where
detrimental = flip (comparing snd)
acceptable (l, q) = q > 0 && not (B8.null l) && B8.all isLangChar l
isLangChar c = isAsciiUpper c || isAsciiLower c || isDigit c || c == '-'
----------------------------------------------------------------
acceptLanguage :: Parser [(ByteString, Int)]
acceptLanguage = rangeQvalue `sepBy1` (spaces *> char ',' *> spaces)
rangeQvalue :: Parser (ByteString, Int)
rangeQvalue = (,) <$> languageRange <*> quality
languageRange :: Parser ByteString
languageRange = takeWhile (`notElem` [32, 44, 59])
quality :: Parser Int
quality = option 1000 (string ";q=" *> qvalue)
qvalue :: Parser Int
qvalue =
1000 <$ (char '1' *> optional (char '.' *> range 0 3 digit))
<|> read3 <$> (char '0' *> option "0" (char '.' *> range 0 3 digit))
where
read3 n = read . take 3 $ n ++ repeat '0'
optional p = () <$ p <|> return ()
----------------------------------------------------------------
range :: Int -> Int -> Parser a -> Parser [a]
range n m p = (++) <$> count n p <*> upto (m - n) p
upto :: Int -> Parser a -> Parser [a]
upto 0 _ = return []
upto n p = (:) <$> p <*> upto (n - 1) p <|> return []
spaces :: Parser ()
spaces = () <$ many space