on master help sign in
Fix #317: Do not truncate or under/overflow non-integral numbers
commit4d25381ce27bc19b2d6d84eebb8c561c2489e61b authorOleg Grenrus <oleg.grenrus@iki.fi> dateMon 4 Jul 2016, 16:15 (10 years ago) parentfb484e4
3 files changed+72−38
Data/Aeson/Types/FromJSON.hs+53−25 changelog.md+3 tests/UnitTests.hs+16−13
Data/Aeson/Types/FromJSON.hs+53−25
@@ -178,18 +178,46 @@ parseRealFloat _ Null = pure (0/0)
178178parseRealFloat expected v = typeMismatch expected v
179179{-# INLINE parseRealFloat #-}
180180
181+truncateIntegral :: Integral a => String -> Scientific -> Parser a
182+truncateIntegral expected s
183+ | Scientific.isInteger s = pure (truncate s)
184+ | otherwise =
185+ fail $ "Floating number specified for " ++ expected ++ ": " ++ show s
186+{-# INLINE truncateIntegral #-}
187+
181188parseIntegral :: Integral a => String -> Value -> Parser a
182−parseIntegral expected = withScientific expected $ pure . truncate
189+parseIntegral expected = withScientific expected $ truncateIntegral expected
183190{-# INLINE parseIntegral #-}
184191
192+truncateBoundedIntegral :: Integral a => String -> Scientific -> Parser a
193+truncateBoundedIntegral expected s
194+ | Scientific.isInteger s =
195+ let x = truncate s
196+ in if fromIntegral x == s
197+ then pure x
198+ else fail $ "Truncating to " ++ expected ++ " cause over or underflow: " ++ show s
199+ | otherwise =
200+ fail $ "Floating number specified for " ++ expected ++ ": " ++ show s
201+{-# INLINE truncateBoundedIntegral #-}
202+
203+parseBoundedIntegral :: Integral a => String -> Value -> Parser a
204+parseBoundedIntegral expected = withScientific expected $ truncateBoundedIntegral expected
205+{-# INLINE parseBoundedIntegral #-}
206+
185207parseScientificText :: Text -> Parser Scientific
186208parseScientificText
187209 = either fail pure
188210 . A.parseOnly (A.scientific <* A.endOfInput)
189211 . T.encodeUtf8
190212
191−parseIntegralText :: Integral a => Text -> Parser a
192−parseIntegralText t = truncate <$> parseScientificText t
213+parseIntegralText :: Integral a => String -> Text -> Parser a
214+parseIntegralText expected t =
215+ parseScientificText t >>= truncateIntegral expected
216+{-# INLINE parseIntegralText #-}
217+
218+parseBoundedIntegralText :: Integral a => String -> Text -> Parser a
219+parseBoundedIntegralText expected t =
220+ parseScientificText t >>= truncateBoundedIntegral expected
193221
194222parseOptionalFieldWith :: (Value -> Parser (Maybe a))
195223 -> Object -> Text -> Parser (Maybe a)
@@ -1115,22 +1143,22 @@ instance HasResolution a => FromJSON (Fixed a) where
11151143 {-# INLINE parseJSON #-}
11161144
11171145instance FromJSON Int where
1118− parseJSON = parseIntegral "Int"
1146+ parseJSON = parseBoundedIntegral "Int"
11191147 {-# INLINE parseJSON #-}
11201148
11211149instance FromJSONKey Int where
1122− fromJSONKey = FromJSONKeyTextParser parseIntegralText
1150+ fromJSONKey = FromJSONKeyTextParser $ parseBoundedIntegralText "Int"
11231151
11241152-- | /WARNING:/ Only parse Integers from trusted input since an
11251153-- attacker could easily fill up the memory of the target system by
11261154-- specifying a scientific number with a big exponent like
11271155-- @1e1000000000@.
11281156instance FromJSON Integer where
1129− parseJSON = withScientific "Integral" $ pure . truncate
1157+ parseJSON = parseIntegral "Integer"
11301158 {-# INLINE parseJSON #-}
11311159
11321160instance FromJSONKey Integer where
1133− fromJSONKey = FromJSONKeyTextParser parseIntegralText
1161+ fromJSONKey = FromJSONKeyTextParser $ parseIntegralText "Integer"
11341162
11351163instance FromJSON Natural where
11361164 parseJSON = withScientific "Natural" $ \s ->
@@ -1145,67 +1173,67 @@ instance FromJSONKey Natural where
11451173 else pure $ truncate s
11461174
11471175instance FromJSON Int8 where
1148− parseJSON = parseIntegral "Int8"
1176+ parseJSON = parseBoundedIntegral "Int8"
11491177 {-# INLINE parseJSON #-}
11501178
11511179instance FromJSONKey Int8 where
1152− fromJSONKey = FromJSONKeyTextParser parseIntegralText
1180+ fromJSONKey = FromJSONKeyTextParser $ parseBoundedIntegralText "Int8"
11531181
11541182instance FromJSON Int16 where
1155− parseJSON = parseIntegral "Int16"
1183+ parseJSON = parseBoundedIntegral "Int16"
11561184 {-# INLINE parseJSON #-}
11571185
11581186instance FromJSONKey Int16 where
1159− fromJSONKey = FromJSONKeyTextParser parseIntegralText
1187+ fromJSONKey = FromJSONKeyTextParser $ parseBoundedIntegralText "Int16"
11601188
11611189instance FromJSON Int32 where
1162− parseJSON = parseIntegral "Int32"
1190+ parseJSON = parseBoundedIntegral "Int32"
11631191 {-# INLINE parseJSON #-}
11641192
11651193instance FromJSONKey Int32 where
1166− fromJSONKey = FromJSONKeyTextParser parseIntegralText
1194+ fromJSONKey = FromJSONKeyTextParser $ parseBoundedIntegralText "Int32"
11671195
11681196instance FromJSON Int64 where
1169− parseJSON = parseIntegral "Int64"
1197+ parseJSON = parseBoundedIntegral "Int64"
11701198 {-# INLINE parseJSON #-}
11711199
11721200instance FromJSONKey Int64 where
1173− fromJSONKey = FromJSONKeyTextParser parseIntegralText
1201+ fromJSONKey = FromJSONKeyTextParser $ parseBoundedIntegralText "Int64"
11741202
11751203instance FromJSON Word where
1176− parseJSON = parseIntegral "Word"
1204+ parseJSON = parseBoundedIntegral "Word"
11771205 {-# INLINE parseJSON #-}
11781206
11791207instance FromJSONKey Word where
1180− fromJSONKey = FromJSONKeyTextParser parseIntegralText
1208+ fromJSONKey = FromJSONKeyTextParser $ parseBoundedIntegralText "Word"
11811209
11821210instance FromJSON Word8 where
1183− parseJSON = parseIntegral "Word8"
1211+ parseJSON = parseBoundedIntegral "Word8"
11841212 {-# INLINE parseJSON #-}
11851213
11861214instance FromJSONKey Word8 where
1187− fromJSONKey = FromJSONKeyTextParser parseIntegralText
1215+ fromJSONKey = FromJSONKeyTextParser $ parseBoundedIntegralText "Word8"
11881216
11891217instance FromJSON Word16 where
1190− parseJSON = parseIntegral "Word16"
1218+ parseJSON = parseBoundedIntegral "Word16"
11911219 {-# INLINE parseJSON #-}
11921220
11931221instance FromJSONKey Word16 where
1194− fromJSONKey = FromJSONKeyTextParser parseIntegralText
1222+ fromJSONKey = FromJSONKeyTextParser $ parseBoundedIntegralText "Word16"
11951223
11961224instance FromJSON Word32 where
1197− parseJSON = parseIntegral "Word32"
1225+ parseJSON = parseBoundedIntegral "Word32"
11981226 {-# INLINE parseJSON #-}
11991227
12001228instance FromJSONKey Word32 where
1201− fromJSONKey = FromJSONKeyTextParser parseIntegralText
1229+ fromJSONKey = FromJSONKeyTextParser $ parseBoundedIntegralText "Word32"
12021230
12031231instance FromJSON Word64 where
1204− parseJSON = parseIntegral "Word64"
1232+ parseJSON = parseBoundedIntegral "Word64"
12051233 {-# INLINE parseJSON #-}
12061234
12071235instance FromJSONKey Word64 where
1208− fromJSONKey = FromJSONKeyTextParser parseIntegralText
1236+ fromJSONKey = FromJSONKeyTextParser $ parseBoundedIntegralText "Word64"
12091237
12101238
12111239instance FromJSON Text where
changelog.md+3
@@ -11,6 +11,9 @@ For the latest version of this document, please see [https://github.com/bos/aeso
1111* Modified instances for `Map` and `HashMap`. Introduced new `FromJSONKey` and
1212 `ToJSONKey` type classes.
1313
14+* Integral `FromJSON` instances do not accept non integral JSON numbers (e.g. `3.14`).
15+ Also over/underflows are catched for bounded types.
16+
1417* Added `Data.Aeson.Encoding` with functions to work with `Encoding` in an abstract way.
1518
1619* Export `decodeWith`, `decodeStrictWith`, `eitherDecodeWith`, and
tests/UnitTests.hs+16−13
@@ -86,7 +86,7 @@ tests = testGroup "unit" [
8686 ]
8787 , testGroup ".:, .:?, .:!" $ fmap (testCase "-") dotColonMark
8888 , testGroup "To JSON representation" $ fmap assertJsonEncodingExample jsonEncodingExamples
89− , testGroup "From JSON representation" $ fmap assertJsonDecodingExample jsonDecodingExamples
89+ , testGroup "From JSON representation" $ fmap assertJsonExample jsonDecodingExamples
9090 , testGroup "To/From JSON representation" $ fmap assertJsonExample jsonExamples
9191 , testGroup "JSONPath" $ fmap (testCase "-") jsonPath
9292 , testGroup "Hashable laws" $ fmap (testCase "-") hashableLaws
@@ -255,21 +255,24 @@ data Example where
255255 Example
256256 :: (Eq a, Show a, ToJSON a, FromJSON a)
257257 => String -> L.ByteString -> a -> Example
258+ MaybeExample
259+ :: (Eq a, Show a, FromJSON a)
260+ => String -> L.ByteString -> Maybe a -> Example
258261
259262assertJsonExample :: Example -> Test
260263assertJsonExample (Example name bs val) = testCase name $ do
261264 assertEqual "encode" bs (encode val)
262265 assertEqual "encode/via value" bs (encode $ toJSON val)
263266 assertEqual "decode" (Just val) (decode bs)
267+assertJsonExample (MaybeExample name bs mval) = testCase name $
268+ assertEqual "decode" mval (decode bs)
264269
265270assertJsonEncodingExample :: Example -> Test
266271assertJsonEncodingExample (Example name bs val) = testCase name $ do
267272 assertEqual "encode" bs (encode val)
268273 assertEqual "encode/via value" bs (encode $ toJSON val)
269−
270−assertJsonDecodingExample :: Example -> Test
271−assertJsonDecodingExample (Example name bs val) = testCase name $
272− assertEqual "decode" (Just val) (decode bs)
274+assertJsonEncodingExample (MaybeExample name _ _) = testCase name $
275+ assertFailure "cannot encode MaybeExample"
273276
274277jsonEncodingExamples :: [Example]
275278jsonEncodingExamples =
@@ -285,16 +288,16 @@ jsonDecodingExamples :: [Example]
285288jsonDecodingExamples = [
286289 -- Maybe serialising is lossy
287290 -- https://github.com/bos/aeson/issues/376
288− Example "Nothing" "null" (Nothing :: Maybe Int)
289− , Example "Just" "1" (Just 1 :: Maybe Int)
290− , Example "Just Nothing" "null" (Nothing :: Maybe (Maybe Int))
291+ MaybeExample "Nothing" "null" (Just $ Nothing :: Maybe (Maybe Int))
292+ , MaybeExample "Just" "1" (Just $ Just 1 :: Maybe (Maybe Int))
293+ , MaybeExample "Just Nothing" "null" (Just $ Nothing :: Maybe (Maybe (Maybe Int)))
291294 -- Integral values are truncated, and overflowed
292295 -- https://github.com/bos/aeson/issues/317
293− , Example "Word8 3" "3" (Just 3 :: Maybe Word8)
294− , Example "Word8 3.00" "3.00" (Just 3 :: Maybe Word8)
295− , Example "Word8 3.14" "3.14" (Just 3 :: Maybe Word8) -- TODO: should be Nothing
296− , Example "Word8 -1" "-1" (Just 255 :: Maybe Word8) -- TODO: should be Nothing
297− , Example "Word8 300" "300" (Just 44 :: Maybe Word8) -- TODO: should be Nothing
296+ , MaybeExample "Word8 3" "3" (Just 3 :: Maybe Word8)
297+ , MaybeExample "Word8 3.00" "3.00" (Just 3 :: Maybe Word8)
298+ , MaybeExample "Word8 3.14" "3.14" (Nothing :: Maybe Word8)
299+ , MaybeExample "Word8 -1" "-1" (Nothing :: Maybe Word8)
300+ , MaybeExample "Word8 300" "300" (Nothing :: Maybe Word8)
298301 ]
299302
300303jsonExamples :: [Example]