using System; using System.Globalization; using System.Linq; using System.Text; using Jint.Native.Object; using Jint.Native.String; using Jint.Runtime; using Jint.Runtime.Interop; namespace Jint.Native.Global { public sealed class GlobalObject : ObjectInstance { private GlobalObject(Engine engine) : base(engine) { } public static GlobalObject CreateGlobalObject(Engine engine) { var global = new GlobalObject(engine) { Prototype = null, Extensible = true }; return global; } public void Configure() { // this is implementation dependent, and only to pass some unit tests Prototype = Engine.Object.PrototypeObject; // Global object properties FastAddProperty("Object", Engine.Object, true, false, true); FastAddProperty("Function", Engine.Function, true, false, true); FastAddProperty("Array", Engine.Array, true, false, true); FastAddProperty("String", Engine.String, true, false, true); FastAddProperty("RegExp", Engine.RegExp, true, false, true); FastAddProperty("Number", Engine.Number, true, false, true); FastAddProperty("Boolean", Engine.Boolean, true, false, true); FastAddProperty("Date", Engine.Date, true, false, true); FastAddProperty("Math", Engine.Math, true, false, true); FastAddProperty("JSON", Engine.Json, true, false, true); FastAddProperty("Error", Engine.Error, true, false, true); FastAddProperty("EvalError", Engine.EvalError, true, false, true); FastAddProperty("RangeError", Engine.RangeError, true, false, true); FastAddProperty("ReferenceError", Engine.ReferenceError, true, false, true); FastAddProperty("SyntaxError", Engine.SyntaxError, true, false, true); FastAddProperty("TypeError", Engine.TypeError, true, false, true); FastAddProperty("URIError", Engine.UriError, true, false, true); FastAddProperty("NaN", double.NaN, false, false, false); FastAddProperty("Infinity", double.PositiveInfinity, false, false, false); FastAddProperty("undefined", Undefined.Instance, false, false, false); // Global object functions FastAddProperty("parseInt", new ClrFunctionInstance(Engine, ParseInt, 2), true, false, true); FastAddProperty("parseFloat", new ClrFunctionInstance(Engine, ParseFloat, 1), true, false, true); FastAddProperty("isNaN", new ClrFunctionInstance(Engine, IsNaN, 1), true, false, true); FastAddProperty("isFinite", new ClrFunctionInstance(Engine, IsFinite, 1), true, false, true); FastAddProperty("decodeURI", new ClrFunctionInstance(Engine, DecodeUri, 1), true, false, true); FastAddProperty("decodeURIComponent", new ClrFunctionInstance(Engine, DecodeUriComponent, 1), true, false, true); FastAddProperty("encodeURI", new ClrFunctionInstance(Engine, EncodeUri, 1), true, false, true); FastAddProperty("encodeURIComponent", new ClrFunctionInstance(Engine, EncodeUriComponent, 1), true, false, true); FastAddProperty("escape", new ClrFunctionInstance(Engine, Escape, 1), true, false, true); FastAddProperty("unescape", new ClrFunctionInstance(Engine, Unescape, 1), true, false, true); } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.2 /// public static JsValue ParseInt(JsValue thisObject, JsValue[] arguments) { string inputString = TypeConverter.ToString(arguments.At(0)); var s = StringPrototype.TrimEx(inputString); var sign = 1; if (!System.String.IsNullOrEmpty(s)) { if (s[0] == '-') { sign = -1; } if (s[0] == '-' || s[0] == '+') { s = s.Substring(1); } } var stripPrefix = true; int radix = arguments.Length > 1 ? TypeConverter.ToInt32(arguments[1]) : 0; if (radix == 0) { if (s.Length >= 2 && s.StartsWith("0x") || s.StartsWith("0X")) { radix = 16; } else { radix = 10; } } else if (radix < 2 || radix > 36) { return double.NaN; } else if (radix != 16) { stripPrefix = false; } if (stripPrefix && s.Length >= 2 && s.StartsWith("0x") || s.StartsWith("0X")) { s = s.Substring(2); } try { return sign * Parse(s, radix).AsNumber(); } catch { return double.NaN; } } private static JsValue Parse(string number, int radix) { if (number == "") { return double.NaN; } double result = 0; double pow = 1; for (int i = number.Length - 1; i >= 0; i--) { double index = double.NaN; char digit = number[i]; if (digit >= '0' && digit <= '9') { index = digit - '0'; } else if (digit >= 'a' && digit <= 'z') { index = digit - 'a' + 10; } else if (digit >= 'A' && digit <= 'Z') { index = digit - 'A' + 10; } if (double.IsNaN(index) || index >= radix) { return Parse(number.Substring(0, i), radix); } result += index * pow; pow = pow * radix; } return result; } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.3 /// public static JsValue ParseFloat(JsValue thisObject, JsValue[] arguments) { var inputString = TypeConverter.ToString(arguments.At(0)); var trimmedString = StringPrototype.TrimStartEx(inputString); var sign = 1; if (trimmedString.Length > 0) { if (trimmedString[0] == '-') { sign = -1; trimmedString = trimmedString.Substring(1); } else if (trimmedString[0] == '+') { trimmedString = trimmedString.Substring(1); } } if (trimmedString.StartsWith("Infinity")) { return sign * double.PositiveInfinity; } if (trimmedString.StartsWith("NaN")) { return double.NaN; } var separator = (char)0; bool isNan = true; decimal number = 0; var i = 0; for (; i < trimmedString.Length; i++) { var c = trimmedString[i]; if (c == '.') { i++; separator = '.'; break; } if (c == 'e' || c == 'E') { i++; separator = 'e'; break; } var digit = c - '0'; if (digit >= 0 && digit <= 9) { isNan = false; number = number * 10 + digit; } else { break; } } decimal pow = 0.1m; if (separator == '.') { for (; i < trimmedString.Length; i++) { var c = trimmedString[i]; var digit = c - '0'; if (digit >= 0 && digit <= 9) { isNan = false; number += digit * pow; pow *= 0.1m; } else if (c == 'e' || c == 'E') { i++; separator = 'e'; break; } else { break; } } } var exp = 0; var expSign = 1; if (separator == 'e') { if (i < trimmedString.Length) { if (trimmedString[i] == '-') { expSign = -1; i++; } else if (trimmedString[i] == '+') { i++; } } for (; i < trimmedString.Length; i++) { var c = trimmedString[i]; var digit = c - '0'; if (digit >= 0 && digit <= 9) { exp = exp * 10 + digit; } else { break; } } } if (isNan) { return double.NaN; } for (var k = 1; k <= exp; k++) { if (expSign > 0) { number *= 10; } else { number /= 10; } } return (double)(sign * number); } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.4 /// public static JsValue IsNaN(JsValue thisObject, JsValue[] arguments) { var x = TypeConverter.ToNumber(arguments.At(0)); return double.IsNaN(x); } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.5 /// public static JsValue IsFinite(JsValue thisObject, JsValue[] arguments) { if (arguments.Length != 1) { return false; } var n = TypeConverter.ToNumber(arguments.At(0)); if (double.IsNaN(n) || double.IsInfinity(n)) { return false; } return true; } private static readonly char[] UriReserved = { ';', '/', '?', ':', '@', '&', '=', '+', '$', ',' }; private static readonly char[] UriUnescaped = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', '.', '!', '~', '*', '\'', '(', ')' }; private const string HexaMap = "0123456789ABCDEF"; private static bool IsValidHexaChar(char c) { return c >= '0' && c <= '9' || c >= 'a' && c <= 'f' || c >= 'A' && c <= 'F'; } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.2 /// /// /// /// public JsValue EncodeUri(JsValue thisObject, JsValue[] arguments) { var uriString = TypeConverter.ToString(arguments.At(0)); var unescapedUriSet = UriReserved.Concat(UriUnescaped).Concat(new[] { '#' }).ToArray(); return Encode(uriString, unescapedUriSet); } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.3.4 /// /// /// /// public JsValue EncodeUriComponent(JsValue thisObject, JsValue[] arguments) { var uriString = TypeConverter.ToString(arguments.At(0)); return Encode(uriString, UriUnescaped); } private string Encode(string uriString, char[] unescapedUriSet) { var strLen = uriString.Length; var r = new StringBuilder(uriString.Length); for (var k = 0; k < strLen; k++) { var c = uriString[k]; if (System.Array.IndexOf(unescapedUriSet, c) != -1) { r.Append(c); } else { if (c >= 0xDC00 && c <= 0xDBFF) { throw new JavaScriptException(Engine.UriError); } int v; if (c < 0xD800 || c > 0xDBFF) { v = c; } else { k++; if (k == strLen) { throw new JavaScriptException(Engine.UriError); } var kChar = (int)uriString[k]; if (kChar < 0xDC00 || kChar > 0xDFFF) { throw new JavaScriptException(Engine.UriError); } v = (c - 0xD800) * 0x400 + (kChar - 0xDC00) + 0x10000; } byte[] octets; if (v >= 0 && v <= 0x007F) { // 00000000 0zzzzzzz -> 0zzzzzzz octets = new[] { (byte)v }; } else if (v <= 0x07FF) { // 00000yyy yyzzzzzz -> 110yyyyy ; 10zzzzzz octets = new[] { (byte)(0xC0 | (v >> 6)), (byte)(0x80 | (v & 0x3F)) }; } else if (v <= 0xD7FF) { // xxxxyyyy yyzzzzzz -> 1110xxxx; 10yyyyyy; 10zzzzzz octets = new[] { (byte)(0xE0 | (v >> 12)), (byte)(0x80 | ((v >> 6) & 0x3F)), (byte)(0x80 | (v & 0x3F)) }; } else if (v <= 0xDFFF) { throw new JavaScriptException(Engine.UriError); } else if (v <= 0xFFFF) { octets = new[] { (byte) (0xE0 | (v >> 12)), (byte) (0x80 | ((v >> 6) & 0x3F)), (byte) (0x80 | (v & 0x3F)) }; } else { octets = new[] { (byte) (0xF0 | (v >> 18)), (byte) (0x80 | (v >> 12 & 0x3F)), (byte) (0x80 | (v >> 6 & 0x3F)), (byte) (0x80 | (v >> 0 & 0x3F)) }; } for (var j = 0; j < octets.Length; j++) { var jOctet = octets[j]; var x1 = HexaMap[jOctet / 16]; var x2 = HexaMap[jOctet % 16]; r.Append('%').Append(x1).Append(x2); } } } return r.ToString(); } public JsValue DecodeUri(JsValue thisObject, JsValue[] arguments) { var uriString = TypeConverter.ToString(arguments.At(0)); var reservedUriSet = UriReserved.Concat(new[] { '#' }).ToArray(); return Decode(uriString, reservedUriSet); } public JsValue DecodeUriComponent(JsValue thisObject, JsValue[] arguments) { var componentString = TypeConverter.ToString(arguments.At(0)); var reservedUriComponentSet = new char[0]; return Decode(componentString, reservedUriComponentSet); } public string Decode(string uriString, char[] reservedSet) { var strLen = uriString.Length; var R = new StringBuilder(strLen); for (var k = 0; k < strLen; k++) { var C = uriString[k]; if (C != '%') { R.Append(C); } else { var start = k; if (k + 2 >= strLen) { throw new JavaScriptException(Engine.UriError); } if (!IsValidHexaChar(uriString[k + 1]) || !IsValidHexaChar(uriString[k + 2])) { throw new JavaScriptException(Engine.UriError); } var B = Convert.ToByte(uriString[k + 1].ToString() + uriString[k + 2], 16); k += 2; if ((B & 0x80) == 0) { C = (char)B; if (System.Array.IndexOf(reservedSet, C) == -1) { R.Append(C); } else { R.Append(uriString.Substring(start, k - start + 1)); } } else { var n = 0; for (; ((B << n) & 0x80) != 0; n++) ; if (n == 1 || n > 4) { throw new JavaScriptException(Engine.UriError); } var Octets = new byte[n]; Octets[0] = B; if (k + (3 * (n - 1)) >= strLen) { throw new JavaScriptException(Engine.UriError); } for (var j = 1; j < n; j++) { k++; if (uriString[k] != '%') { throw new JavaScriptException(Engine.UriError); } if (!IsValidHexaChar(uriString[k + 1]) || !IsValidHexaChar(uriString[k + 2])) { throw new JavaScriptException(Engine.UriError); } B = Convert.ToByte(uriString[k + 1].ToString() + uriString[k + 2], 16); // B & 11000000 != 10000000 if ((B & 0xC0) != 0x80) { throw new JavaScriptException(Engine.UriError); } k += 2; Octets[j] = B; } R.Append(Encoding.UTF8.GetString(Octets, 0, Octets.Length)); } } } return R.ToString(); } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-B.2.1 /// public JsValue Escape(JsValue thisObject, JsValue[] arguments) { const string whiteList = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789@*_ + -./"; var uriString = TypeConverter.ToString(arguments.At(0)); var strLen = uriString.Length; var r = new StringBuilder(strLen); for (var k = 0; k < strLen; k++) { var c = uriString[k]; if (whiteList.IndexOf(c) != -1) { r.Append(c); } else if (c < 256) { r.Append(string.Format("%{0}", ((int)c).ToString("X2"))); } else { r.Append(string.Format("%u{0}", ((int)c).ToString("X4"))); } } return r.ToString(); } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-B.2.2 /// public JsValue Unescape(JsValue thisObject, JsValue[] arguments) { var uriString = TypeConverter.ToString(arguments.At(0)); var strLen = uriString.Length; var r = new StringBuilder(strLen); for (var k = 0; k < strLen; k++) { var c = uriString[k]; if (c == '%') { if (k < strLen - 6 && uriString[k + 1] == 'u' && uriString.Skip(k + 2).Take(4).All(IsValidHexaChar)) { c = (char)int.Parse( string.Join(string.Empty, uriString.Skip(k + 2).Take(4)), NumberStyles.AllowHexSpecifier); k += 5; } else if (k < strLen - 3 && uriString.Skip(k + 1).Take(2).All(IsValidHexaChar)) { c = (char)int.Parse( string.Join(string.Empty, uriString.Skip(k + 1).Take(2)), NumberStyles.AllowHexSpecifier); k += 2; } } r.Append(c); } return r.ToString(); } } }