/* * Copyright (C) 2010-2016 JPEXS, All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3.0 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. */ package com.jpexs.decompiler.flash.ecma; import com.jpexs.decompiler.flash.action.swf4.ConstantIndex; import com.jpexs.helpers.utf8.Utf8Helper; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; /** * * @author JPEXS */ public class EcmaScript { public static Double toNumber(Object o) { if (o == null) { return 0.0; } if (o == Undefined.INSTANCE) { return Double.NaN; } if (o == Null.INSTANCE) { return 0.0; } if (o instanceof Boolean) { return (Boolean) o ? 1.0 : 0.0; } if (o instanceof Float) { o = (double) (float) (Float) o; } if (o instanceof Double) { return (Double) o; } if (o instanceof Long) { return (double) (long) (Long) o; } if (o instanceof Integer) { return (double) (int) (Integer) o; } if (o instanceof String) { String str = (String) o; if (str.isEmpty()) { return 0.0; } try { return Double.parseDouble(str); } catch (NumberFormatException nfe) { return Double.NaN; } } return toNumber(toPrimitive(o, "Number")); } public static Object toPrimitive(Object o, String prefferedType) { if (o == Undefined.INSTANCE) { return o; } if (o == Null.INSTANCE) { return o; } if (o == Boolean.TRUE || o == Boolean.FALSE) { return o; } if (o instanceof Number) { return o; } if (o instanceof String) { return o; } if (o instanceof ObjectType) { return object_defaultValue((ObjectType) o, prefferedType); } return Undefined.INSTANCE; //?? } public static Object object_defaultValue(ObjectType o) { return object_defaultValue(o, "Number"); } public static Object object_get(ObjectType o, String p) { //TODO: isDataDesciptor, etc. ECMA 8.12.3 return object_getProperty(o, p); } public static Object object_getProperty(ObjectType o, String p) { //TODO: getownproperty, etc... ECMA 8.12.2 return o.getAttribute(p); } public static Object object_defaultValue(ObjectType o, String hint) { switch (hint) { case "String": //TODO: logic similar to 8.12.8 return o.call("toString", new ArrayList<>()); case "Number": //TODO: logic similar to 8.12.8 return o.call("valueOf", new ArrayList<>()); default: return o.toPrimitive(); } } public static Double toNumberAs2(Object o) { if (o == Null.INSTANCE) { return Double.NaN; } if (o instanceof String && ((String) o).isEmpty()) { return Double.NaN; } return toNumber(o); } public static EcmaType type(Object o) { if (o == null) { return EcmaType.NULL; } if (o.getClass() == String.class) { return EcmaType.STRING; } if (o.getClass() == Integer.class) { return EcmaType.NUMBER; } if (o.getClass() == Double.class) { return EcmaType.NUMBER; } if (o.getClass() == Long.class) { return EcmaType.NUMBER; } if (o.getClass() == Boolean.class) { return EcmaType.BOOLEAN; } if (o.getClass() == Null.class) { return EcmaType.NULL; } if (o.getClass() == Undefined.class) { return EcmaType.UNDEFINED; } return EcmaType.OBJECT; } public static String typeString(Object o) { EcmaType type = EcmaScript.type(o); String typeStr; switch (type) { case STRING: typeStr = "string"; break; case BOOLEAN: typeStr = "boolean"; break; case NUMBER: typeStr = "number"; break; case OBJECT: typeStr = "object"; break; case UNDEFINED: typeStr = "undefined"; break; case NULL: // note: null is object in AS3 typeStr = "object"; break; default: // todo: function,movieclip typeStr = "object"; break; } return typeStr; } public static Object compare(Object x, Object y) { return compare(x, y, false); } public static Object compare(Object x, Object y, boolean as2) { Object px = x; Object py = y; /*if (leftFirst) { px = x; //toPrimitive py = y; //toPrimitive } else { py = y; //toPrimitive px = x; //toPrimitive }*/ if (type(px) != EcmaType.STRING || type(py) != EcmaType.STRING) { Double nx = as2 ? toNumberAs2(px) : toNumber(px); Double ny = as2 ? toNumberAs2(py) : toNumber(py); if (nx.isNaN() || ny.isNaN()) { return Undefined.INSTANCE; } if (nx.compareTo(ny) == 0) { return 0; } if (Double.compare(nx, -0.0) == 0 && Double.compare(ny, 0.0) == 0) { return 0; } if (Double.compare(nx, 0.0) == 0 && Double.compare(ny, -0.0) == 0) { return 0; } if (nx.isInfinite() && nx > 0) { return 1; } if (ny.isInfinite() && ny > 0) { return -1; } if (nx.isInfinite() && nx < 0) { return 1; } if (ny.isInfinite() && ny < 0) { return -1; } if (nx.compareTo(ny) < 0) { return -1; } return 1; } else {//Both are STRING String sx = (String) px; String sy = (String) py; if (sx.equals(sy)) { return 0; } if (as2) { // in AS2 an empty string is greater than a non-empty string... if (sx.isEmpty()) { return 1; } if (sy.isEmpty()) { return -1; } } if (sx.startsWith(sy)) { return 1; } if (sy.startsWith(sx)) { return -1; } int len = sx.length() > sy.length() ? sx.length() : sy.length(); for (int k = 0; k < len; k++) { int m = 0; int n = 0; if (sx.length() > k) { m = sx.charAt(k); } if (sy.length() > k) { n = sy.charAt(k); } if (m != n) { if (m < n) { return -1; } else { return 1; } } } return 0; } } public static boolean strictEquals(Object x, Object y) { return strictEquals(false, x, y); } public static boolean equals(Object x, Object y) { return equals(false, x, y); } public static boolean strictEquals(boolean as2, Object x, Object y) { if (type(x) != type(y)) { return false; } return equals(as2, x, y); } public static boolean equals(boolean as2, Object x, Object y) { EcmaType typeX = type(x); EcmaType typeY = type(y); if (typeX == typeY) { if (typeX == null) { return true; } if (typeX == EcmaType.NULL) { return true; } if (typeX == EcmaType.UNDEFINED) { return true; } if (typeX == EcmaType.NUMBER) { if (x instanceof Integer) { x = Double.valueOf((Integer) x); } if (x instanceof Long) { x = Double.valueOf((Long) x); } if (y instanceof Integer) { y = Double.valueOf((Integer) y); } if (y instanceof Long) { y = Double.valueOf((Long) y); } if (((Double) x).isNaN()) { return false; } if (((Double) y).isNaN()) { return false; } if (((Double) x).compareTo((Double) y) == 0) { return true; } if ((Double.compare((Double) x, -0.0) == 0) && (Double.compare((Double) y, 0.0) == 0)) { return true; } if ((Double.compare((Double) x, 0.0) == 0) && (Double.compare((Double) y, -0.0) == 0)) { return true; } return false; } if (typeX == EcmaType.STRING) { return ((String) x).equals((String) y); } if (typeX == EcmaType.BOOLEAN) { return x == y; } return x == y; } if ((typeX == EcmaType.NULL) && (typeY == EcmaType.UNDEFINED)) { return true; } if ((typeX == EcmaType.UNDEFINED) && (typeY == EcmaType.NULL)) { return true; } if ((typeX == EcmaType.NUMBER) && (typeY == EcmaType.STRING)) { return equals(as2, x, as2 ? toNumberAs2(y) : toNumber(y)); } if ((typeX == EcmaType.STRING) && (typeY == EcmaType.NUMBER)) { return equals(as2, as2 ? toNumberAs2(x) : toNumber(x), y); } if (typeX == EcmaType.BOOLEAN) { return equals(as2, as2 ? toNumberAs2(x) : toNumber(x), y); } if (typeY == EcmaType.BOOLEAN) { return equals(as2, x, as2 ? toNumberAs2(y) : toNumber(y)); } if (typeX == EcmaType.STRING || typeX == EcmaType.NUMBER) { //y is object //return ecmaEquals(ecmaToPrimitive(x), y); } if (typeY == EcmaType.STRING || typeY == EcmaType.NUMBER) { //x is object //return ecmaEquals(x, ecmaToPrimitive(y)); } return false; } public static boolean toBoolean(Object o) { if (o == null) { return false; } if (o == Undefined.INSTANCE) { return false; } if (o == Null.INSTANCE) { return false; } if (o instanceof Boolean) { return (Boolean) o; } if (o instanceof Long) { return ((Long) o) != 0; } if (o instanceof Integer) { return ((Integer) o) != 0; } if (o instanceof Float) { o = (double) (float) (Float) o; } if (o instanceof Double) { Double d = (Double) o; if (d.isNaN()) { return false; } if (Double.compare(d, 0) == 0) { return false; } return true; } if (o instanceof String) { String s = (String) o; return !s.isEmpty(); } return true; //other Object } public static int toInt32(Object o) { return (int) toUint32(o); } public static long toUint32(Object o) { Double n = toNumber(o); if (n.isNaN()) { return 0L; } if (Double.compare(n, 0.0) == 0) { return 0L; } if (Double.compare(n, -0.0) == 0) { return 0L; } if (Double.isInfinite(n)) { return 0L; } long posInt = (long) (double) (Math.signum(n) * Math.floor(Math.abs(n))); posInt &= 0xffffffffL; return posInt; } public static String toString(Object o) { return toString(o, false); } public static String toString(Object o, List<String> constantPool) { if (o instanceof ConstantIndex) { int index = ((ConstantIndex) o).index; if (constantPool != null && index < constantPool.size()) { return constantPool.get(index); } } return toString(o, false); } public static String toString(Object o, boolean maxPrecision) { if (o == null) { return "null"; } if (o instanceof Number) { // http://www.ecma-international.org/ecma-262/5.1/#sec-9.8.1 Number n = (Number) o; return new EcmaFloatingDecimal(n.doubleValue(), maxPrecision).toJavaFormatString(); } return o.toString(); } public static Double parseFloat(Object string) { String inputString = toString(string); int startPos = 0; String trimmedString = ""; for (; startPos < inputString.length(); startPos++) { char c = inputString.charAt(startPos); if (!Character.isWhitespace(c)) { trimmedString = inputString.substring(startPos); break; } } try { return Double.parseDouble(trimmedString); //Is this the same? } catch (NumberFormatException nfe) { return Double.NaN; } } public static Boolean isNaN(Object number) { return Double.isNaN(toNumber(number)); } public static Boolean isFinite(Object number) { return Double.isFinite(toNumber(number)); } public static Object parseInt(Object string, Object radix) { String inputString = toString(string); int startPos = 0; String s = ""; for (; startPos < inputString.length(); startPos++) { char c = inputString.charAt(startPos); if (!Character.isWhitespace(c)) { s = inputString.substring(startPos); break; } } int sign = 1; if (!s.isEmpty() && s.charAt(0) == '-') { sign = -1; } if (!s.isEmpty() && (s.charAt(0) == '+' || s.charAt(0) == '-')) { s = s.substring(1); } int r = toInt32(radix); boolean stripPrefix = true; if (r != 0) { if (r < 2 || r > 36) { return Double.NaN; } if (r != 16) { stripPrefix = false; } } else { r = 10; } if (stripPrefix) { if (s.length() >= 2) { if (s.substring(0, 2).toLowerCase().equals("0x")) { s = s.substring(2); r = 16; } } } String allDigits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; String allowedDigits = allDigits.substring(0, r); String z = s; for (int i = 0; i < s.length(); i++) { if (("" + s.charAt(i)).matches("[" + allowedDigits + "]")) { if (i == 0) { z = ""; break; } z = s.substring(0, i); break; } } if (z.isEmpty()) { return Double.NaN; } Long number = Long.parseLong(z, r); return sign * number; } private static char toHex(int ch) { return (char) (ch < 10 ? '0' + ch : 'A' + ch - 10); } private static String simpleCustomEncode(String input, String additionalValidChars) { String alphas = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; String num = "0123456789"; String alphaCases = alphas + alphas.toLowerCase(); String alphanum = alphaCases + num; return customEncode(input, alphanum + additionalValidChars); } private static String simpleCustomDecode(String input, String additionalValidChars) { String alphas = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; String num = "0123456789"; String alphaCases = alphas + alphas.toLowerCase(); String alphanum = alphaCases + num; return customDecode(input, alphanum + additionalValidChars); } private static String customEncode(String input, String validChars) { StringBuilder resultStr = new StringBuilder(); for (char ch : input.toCharArray()) { if (!validChars.contains("" + ch)) { resultStr.append('%'); resultStr.append(toHex(ch / 16)); resultStr.append(toHex(ch % 16)); } else { resultStr.append(ch); } } return resultStr.toString(); } private static String customDecode(String input, String reservedSet) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); for (int i = 0; i < input.length(); i++) { char ch = input.charAt(i); String s; if (ch == '%' && i + 2 < input.length()) { try { int k = i; int b = Integer.parseInt(input.substring(k + 1, k + 2 + 1), 16); int msb = (b >> 15) & 1; if (msb == 0) { char c = (char) b; if (!reservedSet.contains("" + c)) { baos.write(c); } else { baos.write(Utf8Helper.getBytes(input.substring(k, k + 3))); } } else { //here continues some multibyte character //FIXME: is this working? for (; msb == 1 && k < input.length() && input.charAt(k) == '%'; k += 3) { b = Integer.parseInt(input.substring(k + 1, k + 2 + 1), 16); msb = (b >> 15) & 1; baos.write(b); } //throw error is msb=1 } } catch (NumberFormatException nfe) { //throw URIEx } catch (IOException ex) { } } } try { return baos.toString("UTF-8"); } catch (UnsupportedEncodingException ex) { return null; } } public static String encodeUriComponent(Object s) { return simpleCustomEncode(toString(s), "-_.!~*'()"); } public static String encodeUri(Object s) { return simpleCustomEncode(toString(s), ";/?:@&=+$,#-_.!~*'()"); } public static String escape(Object s) { return simpleCustomEncode(toString(s), "@-_.*+/"); } public static String decodeUriComponent(Object s) { return simpleCustomDecode(toString(s), "-_.!~*'()"); } public static String decodeUri(Object s) { return simpleCustomDecode(toString(s), ";/?:@&=+$,#-_.!~*'()"); } public static String unescape(Object s) { return simpleCustomDecode(toString(s), "@-_.*+/"); } }