/*
* 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), "@-_.*+/");
}
}