Protect unicode values in requests
This commit is contained in:
@@ -93,11 +93,11 @@ encodeUniformObjs =
|
|||||||
createReadStatement :: SqlQuery -> SqlQuery -> NonnegRange -> Bool -> Bool -> Bool ->
|
createReadStatement :: SqlQuery -> SqlQuery -> NonnegRange -> Bool -> Bool -> Bool ->
|
||||||
H.Query () ResultsWithCount
|
H.Query () ResultsWithCount
|
||||||
createReadStatement selectQuery countQuery range isSingle countTotal asCsv =
|
createReadStatement selectQuery countQuery range isSingle countTotal asCsv =
|
||||||
H.statement sql HE.unit decodeStandard True
|
H.statement (cs sql) HE.unit decodeStandard True
|
||||||
where
|
where
|
||||||
sql = [qc|
|
sql = [qc|
|
||||||
WITH {sourceCTEName} AS ({selectQuery}) SELECT {cols}
|
WITH {sourceCTEName} AS ({selectQuery}) SELECT {cols}
|
||||||
FROM ( SELECT * FROM {sourceCTEName} {limitF range}) t |]
|
FROM ( SELECT * FROM {sourceCTEName} {limitF range}) t |] :: Text
|
||||||
countResultF = if countTotal then "("<>countQuery<>")" else "null"
|
countResultF = if countTotal then "("<>countQuery<>")" else "null"
|
||||||
cols = intercalate ", " [
|
cols = intercalate ", " [
|
||||||
countResultF <> " AS total_result_set",
|
countResultF <> " AS total_result_set",
|
||||||
@@ -116,20 +116,20 @@ createWriteStatement :: QualifiedIdentifier -> SqlQuery -> SqlQuery -> Bool ->
|
|||||||
createWriteStatement _ _ _ _ _ _ _ (PayloadParseError _) = undefined
|
createWriteStatement _ _ _ _ _ _ _ (PayloadParseError _) = undefined
|
||||||
createWriteStatement _ _ mutateQuery _ None
|
createWriteStatement _ _ mutateQuery _ None
|
||||||
_ _ (PayloadJSON (UniformObjects _)) =
|
_ _ (PayloadJSON (UniformObjects _)) =
|
||||||
H.statement sql encodeUniformObjs decodeStandardMay True
|
H.statement (cs sql) encodeUniformObjs decodeStandardMay True
|
||||||
where
|
where
|
||||||
sql = [qc|
|
sql = [qc|
|
||||||
WITH {sourceCTEName} AS ({mutateQuery})
|
WITH {sourceCTEName} AS ({mutateQuery})
|
||||||
SELECT '', 0, '', '' |]
|
SELECT '', 0, '', '' |] :: Text
|
||||||
|
|
||||||
createWriteStatement qi _ mutateQuery isSingle HeadersOnly
|
createWriteStatement qi _ mutateQuery isSingle HeadersOnly
|
||||||
pKeys _ (PayloadJSON (UniformObjects _)) =
|
pKeys _ (PayloadJSON (UniformObjects _)) =
|
||||||
H.statement sql encodeUniformObjs decodeStandardMay True
|
H.statement (cs sql) encodeUniformObjs decodeStandardMay True
|
||||||
where
|
where
|
||||||
sql = [qc|
|
sql = [qc|
|
||||||
WITH {sourceCTEName} AS ({mutateQuery} RETURNING {fromQi qi}.*)
|
WITH {sourceCTEName} AS ({mutateQuery} RETURNING {fromQi qi}.*)
|
||||||
SELECT {cols}
|
SELECT {cols}
|
||||||
FROM (SELECT 1 FROM {sourceCTEName}) t |]
|
FROM (SELECT 1 FROM {sourceCTEName}) t |] :: Text
|
||||||
cols = intercalate ", " [
|
cols = intercalate ", " [
|
||||||
"'' AS total_result_set",
|
"'' AS total_result_set",
|
||||||
"pg_catalog.count(t) AS page_total",
|
"pg_catalog.count(t) AS page_total",
|
||||||
@@ -139,12 +139,12 @@ createWriteStatement qi _ mutateQuery isSingle HeadersOnly
|
|||||||
|
|
||||||
createWriteStatement qi selectQuery mutateQuery isSingle Full
|
createWriteStatement qi selectQuery mutateQuery isSingle Full
|
||||||
pKeys asCsv (PayloadJSON (UniformObjects _)) =
|
pKeys asCsv (PayloadJSON (UniformObjects _)) =
|
||||||
H.statement sql encodeUniformObjs decodeStandardMay True
|
H.statement (cs sql) encodeUniformObjs decodeStandardMay True
|
||||||
where
|
where
|
||||||
sql = [qc|
|
sql = [qc|
|
||||||
WITH {sourceCTEName} AS ({mutateQuery} RETURNING {fromQi qi}.*)
|
WITH {sourceCTEName} AS ({mutateQuery} RETURNING {fromQi qi}.*)
|
||||||
SELECT {cols}
|
SELECT {cols}
|
||||||
FROM ({selectQuery}) t |]
|
FROM ({selectQuery}) t |] :: Text
|
||||||
cols = intercalate ", " [
|
cols = intercalate ", " [
|
||||||
"'' AS total_result_set", -- when updateing it does not make sense
|
"'' AS total_result_set", -- when updateing it does not make sense
|
||||||
"pg_catalog.count(t) AS page_total",
|
"pg_catalog.count(t) AS page_total",
|
||||||
@@ -206,7 +206,7 @@ addJoinConditions schema (Node (query, (n, r)) forest) =
|
|||||||
type ProcResults = (Maybe Int64, Int64, JSON.Value)
|
type ProcResults = (Maybe Int64, Int64, JSON.Value)
|
||||||
callProc :: QualifiedIdentifier -> JSON.Object -> NonnegRange -> Bool -> H.Query () (Maybe ProcResults)
|
callProc :: QualifiedIdentifier -> JSON.Object -> NonnegRange -> Bool -> H.Query () (Maybe ProcResults)
|
||||||
callProc qi params range countTotal =
|
callProc qi params range countTotal =
|
||||||
H.statement sql HE.unit decodeProc True
|
H.statement (cs sql) HE.unit decodeProc True
|
||||||
where
|
where
|
||||||
sql = [qc|
|
sql = [qc|
|
||||||
WITH t AS (select * {_callSql})
|
WITH t AS (select * {_callSql})
|
||||||
@@ -217,13 +217,13 @@ callProc qi params range countTotal =
|
|||||||
coalesce(array_agg(row_to_json(r)), '\{}')
|
coalesce(array_agg(row_to_json(r)), '\{}')
|
||||||
)::character varying
|
)::character varying
|
||||||
FROM (select * from t {limitF range}) r;
|
FROM (select * from t {limitF range}) r;
|
||||||
|]
|
|] :: Text
|
||||||
_args = intercalate "," $ map _assignment (HM.toList params)
|
_args = intercalate "," $ map _assignment (HM.toList params)
|
||||||
_assignment (n,v) = pgFmtIdent n <> ":=" <> insertableValue v
|
_assignment (n,v) = pgFmtIdent n <> ":=" <> insertableValue v
|
||||||
_callSql = [qc| from {fromQi qi}({_args}) |] :: BS.ByteString
|
_callSql = [qc| from {fromQi qi}({_args}) |] :: Text
|
||||||
_countExpr = if countTotal
|
_countExpr = if countTotal
|
||||||
then "(select pg_catalog.count(1) from t)"
|
then "(select pg_catalog.count(1) from t)"
|
||||||
else "null::bigint" :: BS.ByteString
|
else "null::bigint" :: Text
|
||||||
decodeProc = HD.maybeRow procRow
|
decodeProc = HD.maybeRow procRow
|
||||||
procRow = (,,) <$> HD.nullableValue HD.int8 <*> HD.value HD.int8
|
procRow = (,,) <$> HD.nullableValue HD.int8 <*> HD.value HD.int8
|
||||||
<*> HD.value HD.json
|
<*> HD.value HD.json
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ import SpecHelper
|
|||||||
|
|
||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
import Data.Maybe (fromJust)
|
import Data.Maybe (fromJust)
|
||||||
|
import Data.Monoid ((<>))
|
||||||
import Text.Heredoc
|
import Text.Heredoc
|
||||||
import Network.HTTP.Types.Header
|
import Network.HTTP.Types.Header
|
||||||
import Network.HTTP.Types
|
import Network.HTTP.Types
|
||||||
import Control.Monad (replicateM_)
|
import Control.Monad (replicateM_, void)
|
||||||
|
|
||||||
import TestTypes(IncPK(..), CompoundPK(..))
|
import TestTypes(IncPK(..), CompoundPK(..))
|
||||||
import Network.Wai (Application)
|
import Network.Wai (Application)
|
||||||
@@ -233,10 +234,21 @@ spec = do
|
|||||||
it "fails for too few" $ do
|
it "fails for too few" $ do
|
||||||
p <- request methodPost "/no_pk" [("Content-Type", "text/csv")] "a,b\nfoo,bar\nbaz"
|
p <- request methodPost "/no_pk" [("Content-Type", "text/csv")] "a,b\nfoo,bar\nbaz"
|
||||||
liftIO $ simpleStatus p `shouldBe` badRequest400
|
liftIO $ simpleStatus p `shouldBe` badRequest400
|
||||||
-- it does not fail because the extra columns are ignored
|
|
||||||
-- it "fails for too many" $ do
|
context "with unicode values" $
|
||||||
-- p <- request methodPost "/no_pk" [("Content-Type", "text/csv")] "a,b\nfoo,bar\nbaz,bat,bad"
|
it "succeeds and returns usable location header" $ do
|
||||||
-- liftIO $ simpleStatus p `shouldBe` badRequest400
|
let payload = [json| { "a":"圍棋", "b":"¥" } |]
|
||||||
|
p <- request methodPost "/no_pk"
|
||||||
|
[("Prefer", "return=representation")]
|
||||||
|
payload
|
||||||
|
liftIO $ do
|
||||||
|
simpleBody p `shouldBe` payload
|
||||||
|
simpleStatus p `shouldBe` created201
|
||||||
|
|
||||||
|
let Just location = lookup hLocation $ simpleHeaders p
|
||||||
|
r <- get location
|
||||||
|
liftIO $ simpleBody r `shouldBe` "["<>payload<>"]"
|
||||||
|
|
||||||
|
|
||||||
describe "Putting record" $ do
|
describe "Putting record" $ do
|
||||||
|
|
||||||
@@ -387,6 +399,17 @@ spec = do
|
|||||||
, matchHeaders = []
|
, matchHeaders = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context "with unicode values" $
|
||||||
|
it "succeeds and returns values intact" $ do
|
||||||
|
void $ request methodPost "/no_pk" []
|
||||||
|
[json| { "a":"patchme", "b":"patchme" } |]
|
||||||
|
let payload = [json| { "a":"圍棋", "b":"¥" } |]
|
||||||
|
p <- request methodPatch "/no_pk?a=eq.patchme&b=eq.patchme"
|
||||||
|
[("Prefer", "return=representation")] payload
|
||||||
|
liftIO $ do
|
||||||
|
simpleBody p `shouldBe` "["<>payload<>"]"
|
||||||
|
simpleStatus p `shouldBe` ok200
|
||||||
|
|
||||||
describe "Row level permission" $
|
describe "Row level permission" $
|
||||||
it "set user_id when inserting rows" $ do
|
it "set user_id when inserting rows" $ do
|
||||||
let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqZG9lIn0.y4vZuu1dDdwAl0-S00MCRWRYMlJ5YAMSir6Es6WtWx0"
|
let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqZG9lIn0.y4vZuu1dDdwAl0-S00MCRWRYMlJ5YAMSir6Es6WtWx0"
|
||||||
|
|||||||
@@ -390,11 +390,15 @@ spec = do
|
|||||||
post "/rpc/test_empty_rowset" [json| {} |] `shouldRespondWith`
|
post "/rpc/test_empty_rowset" [json| {} |] `shouldRespondWith`
|
||||||
[json| [] |]
|
[json| [] |]
|
||||||
|
|
||||||
context "a proc that returns plain text" $
|
context "a proc that returns plain text" $ do
|
||||||
it "returns proper json" $
|
it "returns proper json" $
|
||||||
post "/rpc/sayhello" [json| { "name": "world" } |] `shouldRespondWith`
|
post "/rpc/sayhello" [json| { "name": "world" } |] `shouldRespondWith`
|
||||||
[json| [{"sayhello":"Hello, world"}] |]
|
[json| [{"sayhello":"Hello, world"}] |]
|
||||||
|
|
||||||
|
it "can handle unicode" $
|
||||||
|
post "/rpc/sayhello" [json| { "name": "¥" } |] `shouldRespondWith`
|
||||||
|
[json| [{"sayhello":"Hello, ¥"}] |]
|
||||||
|
|
||||||
context "improper input" $ do
|
context "improper input" $ do
|
||||||
it "rejects unknown content type even if payload is good" $
|
it "rejects unknown content type even if payload is good" $
|
||||||
request methodPost "/rpc/sayhello"
|
request methodPost "/rpc/sayhello"
|
||||||
|
|||||||
Reference in New Issue
Block a user