Browse Source

Timeout for regex. (#621)

Sebastian Stehle 6 years ago
parent
commit
6e06825f1c

+ 34 - 0
Jint.Tests/Runtime/RegExpTests.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Text.RegularExpressions;
+using Xunit;
+
+namespace Jint.Tests.Runtime
+{
+    public class RegExpTests
+    {
+        private readonly string testRegex = "^(https?:\\/\\/)?([\\da-z\\.-]+)\\.([a-z\\.]{2,6})([\\/\\w\\.-]*)*\\/?$";
+        private readonly string testedValue = "https://archiverbx.blob.core.windows.net/static/C:/Users/USR/Documents/Projects/PROJ/static/images/full/1234567890.jpg";
+
+        [Fact]
+        public void CanNotBreakEngineWithLongRunningMatch()
+        {
+            var engine = new Engine(e => e.RegexTimeoutInterval(TimeSpan.FromSeconds(1)));
+
+            Assert.Throws<RegexMatchTimeoutException>(() =>
+            {
+                engine.Execute($"'{testedValue}'.match(/{testRegex}/)");
+            });
+        }
+
+        [Fact]
+        public void CanNotBreakEngineWithLongRunningRegExp()
+        {
+            var engine = new Engine(e => e.RegexTimeoutInterval(TimeSpan.FromSeconds(1)));
+
+            Assert.Throws<RegexMatchTimeoutException>(() =>
+            {
+               engine.Execute($"'{testedValue}'.match(new RegExp(/{testRegex}/))");
+            });
+        }
+    }
+}

+ 1 - 1
Jint/Engine.cs

@@ -103,7 +103,7 @@ namespace Jint
             { typeof(UInt16), (engine, v) => JsNumber.Create((UInt16)v) },
             { typeof(UInt16), (engine, v) => JsNumber.Create((UInt16)v) },
             { typeof(UInt32), (engine, v) => JsNumber.Create((UInt32)v) },
             { typeof(UInt32), (engine, v) => JsNumber.Create((UInt32)v) },
             { typeof(UInt64), (engine, v) => JsNumber.Create((UInt64)v) },
             { typeof(UInt64), (engine, v) => JsNumber.Create((UInt64)v) },
-            { typeof(System.Text.RegularExpressions.Regex), (engine, v) => engine.RegExp.Construct((System.Text.RegularExpressions.Regex)v, "") }
+            { typeof(System.Text.RegularExpressions.Regex), (engine, v) => engine.RegExp.Construct((System.Text.RegularExpressions.Regex)v, "", engine) }
         };
         };
 
 
         // shared frozen version
         // shared frozen version

+ 4 - 4
Jint/JsValueExtensions.cs

@@ -11,7 +11,7 @@ namespace Jint
         {
         {
             if (value._type != Types.Boolean)
             if (value._type != Types.Boolean)
             {
             {
-                ExceptionHelper.ThrowArgumentException("The value is not a boolean");
+                ExceptionHelper.ThrowArgumentException($"Expected boolean but got {value._type}");
             }
             }
 
 
             return ((JsBoolean) value)._value;
             return ((JsBoolean) value)._value;
@@ -22,7 +22,7 @@ namespace Jint
         {
         {
             if (value._type != Types.Number)
             if (value._type != Types.Number)
             {
             {
-                ExceptionHelper.ThrowArgumentException("The value is not a number");
+                ExceptionHelper.ThrowArgumentException($"Expected number but got {value._type}");
             }
             }
 
 
             return ((JsNumber) value)._value;
             return ((JsNumber) value)._value;
@@ -33,7 +33,7 @@ namespace Jint
         {
         {
             if (value._type != Types.String)
             if (value._type != Types.String)
             {
             {
-                ExceptionHelper.ThrowArgumentException("The value is not a string");
+                ExceptionHelper.ThrowArgumentException($"Expected string but got {value._type}");
             }
             }
 
 
             return AsStringWithoutTypeCheck(value);
             return AsStringWithoutTypeCheck(value);
@@ -50,7 +50,7 @@ namespace Jint
         {
         {
             if (value._type != Types.Symbol)
             if (value._type != Types.Symbol)
             {
             {
-                ExceptionHelper.ThrowArgumentException("The value is not a symbol");
+                ExceptionHelper.ThrowArgumentException($"Expected symbol but got {value._type}");
             }
             }
 
 
             return ((JsSymbol) value)._value;
             return ((JsSymbol) value)._value;

+ 26 - 4
Jint/Native/RegExp/RegExpConstructor.cs

@@ -94,7 +94,14 @@ namespace Jint.Native.RegExp
             try
             try
             {
             {
                 var options = new Scanner("").ParseRegexOptions(f);
                 var options = new Scanner("").ParseRegexOptions(f);
-                r.Value = new Regex(p, options);
+
+                var timeout = _engine.Options._RegexTimeoutInterval;
+                if (timeout.Ticks <= 0)
+                {
+                    timeout = Regex.InfiniteMatchTimeout;
+                }
+
+                r.Value = new Regex(p, options, timeout);
             }
             }
             catch (Exception e)
             catch (Exception e)
             {
             {
@@ -117,7 +124,7 @@ namespace Jint.Native.RegExp
             return r;
             return r;
         }
         }
 
 
-        public RegExpInstance Construct(string regExp)
+        public RegExpInstance Construct(string regExp, Engine engine)
         {
         {
             var r = new RegExpInstance(Engine);
             var r = new RegExpInstance(Engine);
             r.Prototype = PrototypeObject;
             r.Prototype = PrototypeObject;
@@ -128,6 +135,12 @@ namespace Jint.Native.RegExp
             var flags = (string)scanner.ScanRegExpFlags().Value;
             var flags = (string)scanner.ScanRegExpFlags().Value;
             r.Value = scanner.TestRegExp(body, flags);
             r.Value = scanner.TestRegExp(body, flags);
 
 
+            var timeout = engine.Options._RegexTimeoutInterval;
+            if (timeout.Ticks > 0)
+            {
+                r.Value = new Regex(r.Value.ToString(), r.Value.Options);
+            }
+
             r.Flags = flags;
             r.Flags = flags;
             AssignFlags(r, flags);
             AssignFlags(r, flags);
             r.Source = System.String.IsNullOrEmpty(body) ? "(?:)" : body;
             r.Source = System.String.IsNullOrEmpty(body) ? "(?:)" : body;
@@ -137,7 +150,7 @@ namespace Jint.Native.RegExp
             return r;
             return r;
         }
         }
 
 
-        public RegExpInstance Construct(Regex regExp, string flags)
+        public RegExpInstance Construct(Regex regExp, string flags, Engine engine)
         {
         {
             var r = new RegExpInstance(Engine);
             var r = new RegExpInstance(Engine);
             r.Prototype = PrototypeObject;
             r.Prototype = PrototypeObject;
@@ -147,7 +160,16 @@ namespace Jint.Native.RegExp
             AssignFlags(r, flags);
             AssignFlags(r, flags);
 
 
             r.Source = regExp.ToString();
             r.Source = regExp.ToString();
-            r.Value = regExp;
+
+            var timeout = _engine.Options._RegexTimeoutInterval;
+            if (timeout.Ticks > 0)
+            {
+                r.Value = new Regex(regExp.ToString(), regExp.Options, timeout);
+            }
+            else
+            {
+                r.Value = regExp;
+            }
 
 
             SetRegexProperties(r);
             SetRegexProperties(r);
 
 

+ 9 - 0
Jint/Options.cs

@@ -22,6 +22,7 @@ namespace Jint
         private long _memoryLimit;
         private long _memoryLimit;
         private int _maxRecursionDepth = -1;
         private int _maxRecursionDepth = -1;
         private TimeSpan _timeoutInterval;
         private TimeSpan _timeoutInterval;
+        private TimeSpan? _regexTimeoutInterval;
         private CultureInfo _culture = CultureInfo.CurrentCulture;
         private CultureInfo _culture = CultureInfo.CurrentCulture;
         private TimeZoneInfo _localTimeZone = TimeZoneInfo.Local;
         private TimeZoneInfo _localTimeZone = TimeZoneInfo.Local;
         private List<Assembly> _lookupAssemblies = new List<Assembly>();
         private List<Assembly> _lookupAssemblies = new List<Assembly>();
@@ -145,6 +146,12 @@ namespace Jint
             return this;
             return this;
         }
         }
 
 
+        public Options RegexTimeoutInterval(TimeSpan regexTimeoutInterval)
+        {
+            _regexTimeoutInterval = regexTimeoutInterval;
+            return this;
+        }
+
         /// <summary>
         /// <summary>
         /// Sets maximum allowed depth of recursion.
         /// Sets maximum allowed depth of recursion.
         /// </summary>
         /// </summary>
@@ -206,6 +213,8 @@ namespace Jint
 
 
         internal TimeSpan _TimeoutInterval => _timeoutInterval;
         internal TimeSpan _TimeoutInterval => _timeoutInterval;
 
 
+        internal TimeSpan _RegexTimeoutInterval => _regexTimeoutInterval ?? _timeoutInterval;
+
         internal CultureInfo _Culture => _culture;
         internal CultureInfo _Culture => _culture;
 
 
         internal TimeZoneInfo _LocalTimeZone => _localTimeZone;
         internal TimeZoneInfo _LocalTimeZone => _localTimeZone;

+ 1 - 1
Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs

@@ -55,7 +55,7 @@ namespace Jint.Runtime.Interpreter.Expressions
             var expression = (Literal) _expression;
             var expression = (Literal) _expression;
             if (expression.TokenType == TokenType.RegularExpression)
             if (expression.TokenType == TokenType.RegularExpression)
             {
             {
-                return _engine.RegExp.Construct((System.Text.RegularExpressions.Regex) expression.Value, expression.Regex.Flags);
+                return _engine.RegExp.Construct((System.Text.RegularExpressions.Regex) expression.Value, expression.Regex.Flags, _engine);
             }
             }
 
 
             return JsValue.FromObject(_engine, expression.Value);
             return JsValue.FromObject(_engine, expression.Value);