fix: parsing of the for parameter of plan media type (#4005)
This commit is contained in:
committed by
Wolfgang Walther
parent
026d84a093
commit
513704dc2b
@@ -5,6 +5,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- #3498, Fix incorrect parsing of the `for` parameter of the `application/vnd.pgrst.plan` media type - @taimoorzaeem
|
||||||
|
|
||||||
## [12.2.8] - 2025-02-10
|
## [12.2.8] - 2025-02-10
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
+51
-28
@@ -1,7 +1,7 @@
|
|||||||
{-# LANGUAGE DeriveAnyClass #-}
|
{-# LANGUAGE DeriveAnyClass #-}
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
{-# LANGUAGE DuplicateRecordFields #-}
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||||||
|
{-# OPTIONS_GHC -Wno-unused-do-bind #-}
|
||||||
module PostgREST.MediaType
|
module PostgREST.MediaType
|
||||||
( MediaType(..)
|
( MediaType(..)
|
||||||
, MTVndPlanOption (..)
|
, MTVndPlanOption (..)
|
||||||
@@ -11,17 +11,16 @@ module PostgREST.MediaType
|
|||||||
, decodeMediaType
|
, decodeMediaType
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
import qualified Data.ByteString as BS
|
import qualified Data.ByteString as BS
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import qualified Text.ParserCombinators.Parsec as P
|
||||||
|
|
||||||
|
import Data.Map (fromList, (!?))
|
||||||
|
import Data.Text.Encoding (decodeLatin1)
|
||||||
import Network.HTTP.Types.Header (Header, hContentType)
|
import Network.HTTP.Types.Header (Header, hContentType)
|
||||||
|
|
||||||
import Data.Map (fromList, (!?))
|
import Protolude
|
||||||
import qualified Data.Text as T (break, drop, dropWhile,
|
|
||||||
dropWhileEnd, null, splitOn,
|
|
||||||
toLower)
|
|
||||||
import Data.Text.Encoding (decodeLatin1)
|
|
||||||
import Protolude
|
|
||||||
|
|
||||||
-- | Enumeration of currently supported media types
|
-- | Enumeration of currently supported media types
|
||||||
data MediaType
|
data MediaType
|
||||||
@@ -104,6 +103,9 @@ toMimePlanFormat PlanText = "text"
|
|||||||
-- >>> decodeMediaType "application/vnd.pgrst.plan;for=\"application/json\""
|
-- >>> decodeMediaType "application/vnd.pgrst.plan;for=\"application/json\""
|
||||||
-- MTVndPlan MTApplicationJSON PlanText []
|
-- MTVndPlan MTApplicationJSON PlanText []
|
||||||
--
|
--
|
||||||
|
-- >>> decodeMediaType "application/vnd.pgrst.plan ; for=\"text/xml\" ; options=analyze"
|
||||||
|
-- MTVndPlan MTTextXML PlanText [PlanAnalyze]
|
||||||
|
--
|
||||||
-- >>> decodeMediaType "application/vnd.pgrst.plan+json;for=\"text/csv\""
|
-- >>> decodeMediaType "application/vnd.pgrst.plan+json;for=\"text/csv\""
|
||||||
-- MTVndPlan MTTextCSV PlanJSON []
|
-- MTVndPlan MTTextCSV PlanJSON []
|
||||||
--
|
--
|
||||||
@@ -150,7 +152,10 @@ decodeMediaType mt = decodeMediaType' $ decodeLatin1 mt
|
|||||||
("*","*",_) -> MTAny
|
("*","*",_) -> MTAny
|
||||||
_ -> MTOther mt'
|
_ -> MTOther mt'
|
||||||
where
|
where
|
||||||
(mainType, subType, params') = tokenizeMediaType mt'
|
mediaTypeOrError = P.parse tokenizeMediaType "parsec: tokenizeMediaType failed" $ T.unpack mt'
|
||||||
|
(mainType, subType, params') = case mediaTypeOrError of
|
||||||
|
Right mt'' -> mt''
|
||||||
|
Left _ -> ("*", "*", []) -- TODO: Throw mediatype error, would need refactoring because currently Error module depend on MediaType module
|
||||||
params = fromList $ map (first T.toLower) params' -- normalize parameter names to lowercase, per RFC 7321
|
params = fromList $ map (first T.toLower) params' -- normalize parameter names to lowercase, per RFC 7321
|
||||||
getPlan fmt = MTVndPlan mtFor fmt $
|
getPlan fmt = MTVndPlan mtFor fmt $
|
||||||
[PlanAnalyze | inOpts "analyze" ] ++
|
[PlanAnalyze | inOpts "analyze" ] ++
|
||||||
@@ -166,21 +171,39 @@ decodeMediaType mt = decodeMediaType' $ decodeLatin1 mt
|
|||||||
checkArrayNullStrip = if strippedNulls then MTVndArrayJSONStrip else MTApplicationJSON
|
checkArrayNullStrip = if strippedNulls then MTVndArrayJSONStrip else MTApplicationJSON
|
||||||
|
|
||||||
-- | Split a Media Type string into components
|
-- | Split a Media Type string into components
|
||||||
-- >>> tokenizeMediaType "application/vnd.pgrst.plan+json;for=\"text/csv\""
|
-- >>> P.parse tokenizeMediaType "" "application/vnd.pgrst.plan+json;for=\"text/csv\""
|
||||||
-- ("application","vnd.pgrst.plan+json",[("for","text/csv")])
|
-- Right ("application","vnd.pgrst.plan+json",[("for","text/csv")])
|
||||||
-- >>> tokenizeMediaType "*/*"
|
--
|
||||||
-- ("*","*",[])
|
-- >>> P.parse tokenizeMediaType "" "*/*"
|
||||||
-- >>> tokenizeMediaType "application/vnd.pgrst.plan;wat=\"application/json;text/csv\""
|
-- Right ("*","*",[])
|
||||||
-- ("application","vnd.pgrst.plan",[("wat","application/json"),("text/csv\"","")])
|
--
|
||||||
tokenizeMediaType :: Text -> (Text, Text, [(Text, Text)])
|
-- >>> P.parse tokenizeMediaType "" "application/vnd.pgrst.plan;wat=\"application/json;text/csv\""
|
||||||
tokenizeMediaType t = (mainType, subType, params)
|
-- Right ("application","vnd.pgrst.plan",[("wat","application/json;text/csv")])
|
||||||
where
|
--
|
||||||
(mainType, rest) = T.break (== '/') t
|
-- >>> P.parse tokenizeMediaType "" "application/vnd.pgrst.plan+text; for=\"text/xml\"; options=analyze|verbose|settings|buffers|wal"
|
||||||
(subType, restParams) = T.break (== ';') $ T.drop 1 rest
|
-- Right ("application","vnd.pgrst.plan+text",[("for","text/xml"),("options","analyze|verbose|settings|buffers|wal")])
|
||||||
params =
|
|
||||||
let rp = T.drop 1 restParams
|
tokenizeMediaType :: P.Parser (Text, Text, [(Text, Text)])
|
||||||
in if T.null rp then [] else map param $ T.splitOn ";" rp -- FIXME: breaks if there's a ';' in a quoted value
|
tokenizeMediaType = do
|
||||||
param p =
|
mainType <- P.many1 (P.alphaNum <|> P.oneOf ".*")
|
||||||
let (k, v) = T.break (== '=') p
|
P.char '/'
|
||||||
in (k, dropAround (== '"') $ T.drop 1 v) -- FIXME: doesn't unescape quotes in values
|
subType <- P.many1 (P.alphaNum <|> P.oneOf ".*+-")
|
||||||
dropAround p = T.dropWhile p . T.dropWhileEnd p
|
params <- P.many pSemicolonSeparatedKeyVals
|
||||||
|
P.optional $ P.try $ P.spaces *> P.char ';' -- ending semicolon
|
||||||
|
P.eof
|
||||||
|
return (T.pack mainType, T.pack subType, params)
|
||||||
|
where
|
||||||
|
pSemicolonSeparatedKeyVals :: P.Parser (Text, Text)
|
||||||
|
pSemicolonSeparatedKeyVals = P.try $ P.spaces *> P.char ';' *> P.spaces *> pKeyVal
|
||||||
|
where
|
||||||
|
pKeyVal :: P.Parser (Text, Text)
|
||||||
|
pKeyVal = do
|
||||||
|
key <- P.many1 P.alphaNum
|
||||||
|
P.spaces
|
||||||
|
P.char '='
|
||||||
|
P.spaces
|
||||||
|
val <- P.try pQuoted <|> P.try pUnQuoted
|
||||||
|
return (T.pack key, T.pack val)
|
||||||
|
where
|
||||||
|
pUnQuoted = P.many1 (P.alphaNum <|> P.oneOf "|")
|
||||||
|
pQuoted = P.char '\"' *> P.manyTill P.anyChar (P.char '\"')
|
||||||
|
|||||||
@@ -103,8 +103,8 @@ postJsonArrayTest(){
|
|||||||
echo "Running memory usage tests.."
|
echo "Running memory usage tests.."
|
||||||
|
|
||||||
jsonKeyTest "1M" "POST" "/rpc/leak?columns=blob" "27M"
|
jsonKeyTest "1M" "POST" "/rpc/leak?columns=blob" "27M"
|
||||||
jsonKeyTest "1M" "POST" "/leak?columns=blob" "20M"
|
jsonKeyTest "1M" "POST" "/leak?columns=blob" "21M"
|
||||||
jsonKeyTest "1M" "PATCH" "/leak?id=eq.1&columns=blob" "20M"
|
jsonKeyTest "1M" "PATCH" "/leak?id=eq.1&columns=blob" "21M"
|
||||||
|
|
||||||
jsonKeyTest "10M" "POST" "/rpc/leak?columns=blob" "32M"
|
jsonKeyTest "10M" "POST" "/rpc/leak?columns=blob" "32M"
|
||||||
jsonKeyTest "10M" "POST" "/leak?columns=blob" "32M"
|
jsonKeyTest "10M" "POST" "/leak?columns=blob" "32M"
|
||||||
|
|||||||
Reference in New Issue
Block a user