Browse Source

fix(discover): Handle large filter values (#25977)

- This adds a query error when a filter value is too large
- Fixes SENTRY-Q9J
William Mak 3 years ago
parent
commit
5546a89659

+ 21 - 15
src/sentry/search/utils.py

@@ -39,25 +39,31 @@ def parse_status_value(value):
 
 def parse_duration(value, interval):
     try:
-        value = float(value)
+        duration = float(value)
     except ValueError:
         raise InvalidQuery(f"{value} is not a valid duration value")
 
-    if interval == "ms":
-        delta = timedelta(milliseconds=value)
-    elif interval == "s":
-        delta = timedelta(seconds=value)
-    elif interval in ["min", "m"]:
-        delta = timedelta(minutes=value)
-    elif interval in ["hr", "h"]:
-        delta = timedelta(hours=value)
-    elif interval in ["day", "d"]:
-        delta = timedelta(days=value)
-    elif interval in ["wk", "w"]:
-        delta = timedelta(days=value * 7)
-    else:
+    try:
+        if interval == "ms":
+            delta = timedelta(milliseconds=duration)
+        elif interval == "s":
+            delta = timedelta(seconds=duration)
+        elif interval in ["min", "m"]:
+            delta = timedelta(minutes=duration)
+        elif interval in ["hr", "h"]:
+            delta = timedelta(hours=duration)
+        elif interval in ["day", "d"]:
+            delta = timedelta(days=duration)
+        elif interval in ["wk", "w"]:
+            delta = timedelta(days=duration * 7)
+        else:
+            raise InvalidQuery(
+                f"{interval} is not a valid duration type, must be ms, s, min, m, hr, h, day, d, wk or w"
+            )
+    except OverflowError:
+        # don't use duration so we show the value the user entered, ie. 9 instead of 9.0
         raise InvalidQuery(
-            f"{interval} is not a valid duration type, must be ms, s, min, m, hr, h, day, d, wk or w"
+            f"{value}{interval} is too large of a value, the maximum value is 999999999 days"
         )
 
     return delta.total_seconds() * 1000.0

+ 10 - 0
tests/sentry/search/events/test_filter.py

@@ -1245,6 +1245,16 @@ class GetSnubaQueryArgsTest(TestCase):
             ]
         ]
 
+    def test_shorthand_overflow(self):
+        with self.assertRaises(InvalidSearchQuery):
+            get_filter(f"transaction.duration:<{'9'*13}m")
+
+        with self.assertRaises(InvalidSearchQuery):
+            get_filter(f"transaction.duration:<{'9'*11}h")
+
+        with self.assertRaises(InvalidSearchQuery):
+            get_filter(f"transaction.duration:<{'9'*10}d")
+
 
 def with_type(type, argument):
     argument.get_type = lambda *_: type

+ 56 - 0
tests/sentry/search/test_utils.py

@@ -18,6 +18,7 @@ from sentry.search.utils import (
     convert_user_tag_to_query,
     get_latest_release,
     get_numeric_field_value,
+    parse_duration,
     parse_query,
     tokenize_query,
 )
@@ -47,6 +48,61 @@ def test_get_numeric_field_value():
     }
 
 
+class TestParseDuration(TestCase):
+    def test_ms(self):
+        assert parse_duration(123, "ms") == 123
+
+    def test_sec(self):
+        assert parse_duration(456, "s") == 456000
+
+    def test_minutes(self):
+        assert parse_duration(789, "min") == 789 * 60 * 1000
+        assert parse_duration(789, "m") == 789 * 60 * 1000
+
+    def test_hours(self):
+        assert parse_duration(234, "hr") == 234 * 60 * 60 * 1000
+        assert parse_duration(234, "h") == 234 * 60 * 60 * 1000
+
+    def test_days(self):
+        assert parse_duration(567, "day") == 567 * 24 * 60 * 60 * 1000
+        assert parse_duration(567, "d") == 567 * 24 * 60 * 60 * 1000
+
+    def test_weeks(self):
+        assert parse_duration(890, "wk") == 890 * 7 * 24 * 60 * 60 * 1000
+        assert parse_duration(890, "w") == 890 * 7 * 24 * 60 * 60 * 1000
+
+    def test_errors(self):
+        with self.assertRaises(InvalidQuery):
+            parse_duration("test", "ms")
+
+        with self.assertRaises(InvalidQuery):
+            parse_duration(123, "test")
+
+    def test_large_durations(self):
+        max_duration = 999999999 * 24 * 60 * 60 * 1000
+        assert parse_duration(999999999, "d") == max_duration
+        assert parse_duration(999999999 * 24, "h") == max_duration
+        assert parse_duration(999999999 * 24 * 60, "m") == max_duration
+        assert parse_duration(999999999 * 24 * 60 * 60, "s") == max_duration
+        assert parse_duration(999999999 * 24 * 60 * 60 * 1000, "ms") == max_duration
+
+    def test_overflow_durations(self):
+        with self.assertRaises(InvalidQuery):
+            assert parse_duration(999999999 + 1, "d")
+
+        with self.assertRaises(InvalidQuery):
+            assert parse_duration((999999999 + 1) * 24, "h")
+
+        with self.assertRaises(InvalidQuery):
+            assert parse_duration((999999999 + 1) * 24 * 60 + 1, "m")
+
+        with self.assertRaises(InvalidQuery):
+            assert parse_duration((999999999 + 1) * 24 * 60 * 60 + 1, "s")
+
+        with self.assertRaises(InvalidQuery):
+            assert parse_duration((999999999 + 1) * 24 * 60 * 60 * 1000 + 1, "ms")
+
+
 def test_tokenize_query_only_keyed_fields():
     tests = [
         ("a:a", {"a": ["a"]}),