/* * Created by Angel Leon (@gubatron), Alden Torres (aldenml) * Copyright (c) 2011-2014, FrostWire(R). All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.frostwire.search.extractors.js; import static com.frostwire.search.extractors.js.JavaFunctions.isalpha; import static com.frostwire.search.extractors.js.JavaFunctions.isdigit; import static com.frostwire.search.extractors.js.JavaFunctions.join; import static com.frostwire.search.extractors.js.JavaFunctions.len; import static com.frostwire.search.extractors.js.JavaFunctions.list; import static com.frostwire.search.extractors.js.JavaFunctions.reverse; import static com.frostwire.search.extractors.js.JavaFunctions.splice; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import com.google.code.regexp.Matcher; import com.google.code.regexp.Pattern; /** * @author gubatron * @author aldenml * */ public final class JsFunction<T> { private final JsContext ctx; private final LambdaN initial_function; public JsFunction(String jscode, String funcname) { this.ctx = new JsContext(jscode); this.initial_function = extract_function(ctx, funcname); } @SuppressWarnings("unchecked") public T eval(Object[] args) { try { return (T) initial_function.eval(args); } finally { // at this point we know that jscode is no longer necessary ctx.free(); } } public T eval(Object s) { return eval(new Object[] { s }); } private static Object interpret_statement(final JsContext ctx, String stmt, final Map<String, Object> local_vars, final int allow_recursion) { if (allow_recursion < 0) { throw new JsError("Recursion limit reached"); } if (stmt.startsWith("var ")) { stmt = stmt.substring("var ".length()); } final Matcher ass_m = Pattern.compile("^(?<out>[a-z]+)(\\[(?<index>.+?)\\])?=(?<expr>.*)$").matcher(stmt); Lambda1 assign; String expr; if (ass_m.find()) { if (ass_m.group("index") != null) { final Object lvar = local_vars.get(ass_m.group("out")); final Object idx = interpret_expression(ctx, ass_m.group("index"), local_vars, allow_recursion); assert idx instanceof Integer; assign = new Lambda1() { @Override public Object eval(Object val) { ((Object[]) lvar)[(Integer) idx] = val; return val; } }; expr = ass_m.group("expr"); } else { final String var = ass_m.group("out"); assign = new Lambda1() { @Override public Object eval(Object val) { local_vars.put(var, val); return val; } }; expr = ass_m.group("expr"); } } else if (stmt.startsWith("return ")) { assign = new Lambda1() { @Override public Object eval(Object v) { return v; } }; expr = stmt.substring("return ".length()); } else { throw new JsError(String.format("Cannot determine left side of statement in %s", stmt)); } Object v = interpret_expression(ctx, expr, local_vars, allow_recursion); return assign.eval(v); } private static Object interpret_expression(final JsContext ctx, String expr, Map<String, Object> local_vars, int allow_recursion) { if (isdigit(expr)) { return Integer.valueOf(expr); } if (isalpha(expr)) { return local_vars.get(expr); } Matcher m = Pattern.compile("^(?<in>[a-z]+)\\.(?<member>.*)$").matcher(expr); if (m.find()) { String member = m.group("member"); Object val = local_vars.get(m.group("in")); if (member.equals("split(\"\")")) { return list((String) val); } if (member.equals("join(\"\")")) { return join((Object[]) val); } if (member.equals("length")) { return len(val); } if (member.equals("reverse()")) { return reverse(val); } Matcher slice_m = Pattern.compile("slice\\((?<idx>.*)\\)").matcher(member); if (slice_m.find()) { Object idx = interpret_expression(ctx, slice_m.group("idx"), local_vars, allow_recursion - 1); return splice(val, (Integer) idx); } } m = Pattern.compile("^(?<in>[a-z]+)\\[(?<idx>.+)\\]$").matcher(expr); if (m.find()) { Object val = local_vars.get(m.group("in")); Object idx = interpret_expression(ctx, m.group("idx"), local_vars, allow_recursion - 1); return ((Object[]) val)[(Integer) idx]; } m = Pattern.compile("^(?<a>.+?)(?<op>[%])(?<b>.+?)$").matcher(expr); if (m.find()) { Object a = interpret_expression(ctx, m.group("a"), local_vars, allow_recursion); Object b = interpret_expression(ctx, m.group("b"), local_vars, allow_recursion); return (Integer) a % (Integer) b; } m = Pattern.compile("^(?<func>[a-zA-Z]+)\\((?<args>[a-z0-9,]+)\\)$").matcher(expr); if (m.find()) { String fname = m.group("func"); if (!ctx.functions.containsKey(fname) && ctx.jscode.length() > 0) { ctx.functions.put(fname, extract_function(ctx, fname)); } List<Object> argvals = new ArrayList<Object>(); for (String v : m.group("args").split(",")) { if (isdigit(v)) { argvals.add(Integer.valueOf(v)); } else { argvals.add(local_vars.get(v)); } } return ctx.functions.get(fname).eval(argvals.toArray()); } throw new JsError(String.format("Unsupported JS expression %s", expr)); } private static LambdaN extract_function(final JsContext ctx, String funcname) { final Matcher func_m = Pattern.compile("function " + java.util.regex.Pattern.quote(funcname) + "\\((?<args>[a-z,]+)\\)\\{(?<code>[^\\}]+)\\}").matcher(ctx.jscode); func_m.find(); final String[] argnames = mscpy(func_m.group("args").split(",")); final String[] stmts = mscpy(func_m.group("code").split(";")); return new LambdaN() { @Override public Object eval(Object[] args) { Map<String, Object> local_vars = new HashMap<String, Object>(); for (int i = 0; i < argnames.length; i++) { local_vars.put(argnames[i], args[i]); } Object res = null; for (String stmt : stmts) { res = interpret_statement(ctx, stmt, local_vars, 20); } return res; } }; } private static String[] mscpy(String[] arr) { String[] r = new String[arr.length]; for (int i = 0; i < arr.length; i++) { r[i] = new String(arr[i].toCharArray()); } return r; } }