mirror of
https://github.com/M66B/FairEmail.git
synced 2026-04-02 07:06:18 +02:00
PoC rule expressions
This commit is contained in:
@@ -21,6 +21,8 @@ package eu.faircode.email;
|
||||
|
||||
import static androidx.room.ForeignKey.CASCADE;
|
||||
|
||||
import static com.ezylang.evalex.operators.OperatorIfc.OPERATOR_PRECEDENCE_COMPARISON;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
@@ -41,6 +43,15 @@ import androidx.room.ForeignKey;
|
||||
import androidx.room.Index;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
import com.ezylang.evalex.EvaluationException;
|
||||
import com.ezylang.evalex.Expression;
|
||||
import com.ezylang.evalex.config.ExpressionConfiguration;
|
||||
import com.ezylang.evalex.data.EvaluationValue;
|
||||
import com.ezylang.evalex.operators.AbstractOperator;
|
||||
import com.ezylang.evalex.operators.InfixOperator;
|
||||
import com.ezylang.evalex.parser.ParseException;
|
||||
import com.ezylang.evalex.parser.Token;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.jsoup.nodes.Document;
|
||||
@@ -62,6 +73,7 @@ import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Matcher;
|
||||
@@ -151,6 +163,10 @@ public class EntityRule {
|
||||
private static final int MAX_NOTES_LENGTH = 512; // characters
|
||||
private static final int URL_TIMEOUT = 15 * 1000; // milliseconds
|
||||
|
||||
private static final List<String> EXPR_VARIABLES = Collections.unmodifiableList(Arrays.asList(
|
||||
"sender", "subject"
|
||||
));
|
||||
|
||||
static boolean needsHeaders(EntityMessage message, List<EntityRule> rules) {
|
||||
return needsHeaders(rules);
|
||||
}
|
||||
@@ -183,6 +199,16 @@ public class EntityRule {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (jcondition.has("expression")) {
|
||||
Expression expression = getExpression(rule, null);
|
||||
if (expression != null)
|
||||
for (String variable : expression.getUsedVariables())
|
||||
if ("body".equals(what) && "body".equalsIgnoreCase(variable))
|
||||
return true;
|
||||
else if ("header".equals(what) && "header".equalsIgnoreCase(variable))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Log.e(ex);
|
||||
}
|
||||
@@ -463,6 +489,19 @@ public class EntityRule {
|
||||
return false;
|
||||
}
|
||||
|
||||
Expression expression = getExpression(this, message);
|
||||
if (expression != null) {
|
||||
for (String variable : expression.getUsedVariables())
|
||||
if ("header".equalsIgnoreCase(variable) && message.headers == null)
|
||||
throw new IllegalArgumentException(context.getString(R.string.title_rule_no_headers));
|
||||
|
||||
Log.i("EXPR evaluating " + jcondition.getString("expression"));
|
||||
Boolean result = expression.evaluate().getBooleanValue();
|
||||
Log.i("EXPR evaluated=" + result);
|
||||
if (!Boolean.TRUE.equals(result))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Safeguard
|
||||
if (jsender == null &&
|
||||
jrecipient == null &&
|
||||
@@ -472,9 +511,10 @@ public class EntityRule {
|
||||
jbody == null &&
|
||||
jdate == null &&
|
||||
jschedule == null &&
|
||||
!jcondition.has("younger"))
|
||||
!jcondition.has("younger") &&
|
||||
!jcondition.has("expression"))
|
||||
return false;
|
||||
} catch (JSONException ex) {
|
||||
} catch (JSONException | ParseException | EvaluationException ex) {
|
||||
Log.e(ex);
|
||||
return false;
|
||||
}
|
||||
@@ -593,6 +633,58 @@ public class EntityRule {
|
||||
return matched;
|
||||
}
|
||||
|
||||
@InfixOperator(precedence = OPERATOR_PRECEDENCE_COMPARISON)
|
||||
public static class ContainsOperator extends AbstractOperator {
|
||||
@Override
|
||||
public EvaluationValue evaluate(
|
||||
Expression expression, Token operatorToken, EvaluationValue... operands) {
|
||||
String op1 = operands[0].getStringValue();
|
||||
String op2 = operands[1].getStringValue();
|
||||
Log.i("EXPR " + op1 + " CONTAINS " + op2);
|
||||
return expression.convertValue(
|
||||
!TextUtils.isEmpty(op1) && !TextUtils.isEmpty(op2) &&
|
||||
op1.toLowerCase().contains(op2.toLowerCase()));
|
||||
}
|
||||
}
|
||||
|
||||
@InfixOperator(precedence = OPERATOR_PRECEDENCE_COMPARISON)
|
||||
public static class MatchesOperator extends AbstractOperator {
|
||||
@Override
|
||||
public EvaluationValue evaluate(
|
||||
Expression expression, Token operatorToken, EvaluationValue... operands) {
|
||||
String op1 = operands[0].getStringValue();
|
||||
String op2 = operands[1].getStringValue();
|
||||
Log.i("EXPR " + op1 + " MATCHES " + op2);
|
||||
return expression.convertValue(
|
||||
!TextUtils.isEmpty(op1) && !TextUtils.isEmpty(op2) &&
|
||||
Pattern.compile(op2, Pattern.DOTALL).matcher(op1).matches());
|
||||
}
|
||||
}
|
||||
|
||||
static Expression getExpression(EntityRule rule, EntityMessage message) throws JSONException {
|
||||
// https://ezylang.github.io/EvalEx/
|
||||
|
||||
JSONObject jcondition = new JSONObject(rule.condition);
|
||||
if (!jcondition.has("expression"))
|
||||
return null;
|
||||
|
||||
String sender = null;
|
||||
String subject = null;
|
||||
if (message != null) {
|
||||
if (message.from != null && message.from.length == 1)
|
||||
sender = MessageHelper.formatAddresses(message.from);
|
||||
subject = message.subject;
|
||||
}
|
||||
|
||||
ExpressionConfiguration configuration = ExpressionConfiguration.defaultConfiguration();
|
||||
configuration.getOperatorDictionary().addOperator("CONTAINS", new ContainsOperator());
|
||||
configuration.getOperatorDictionary().addOperator("MATCHES", new MatchesOperator());
|
||||
|
||||
return new Expression(jcondition.getString("expression"), configuration)
|
||||
.with("sender", sender)
|
||||
.with("subject", subject);
|
||||
}
|
||||
|
||||
boolean execute(Context context, EntityMessage message, String html) throws JSONException, IOException {
|
||||
boolean executed = _execute(context, message, html);
|
||||
if (this.id != null && executed) {
|
||||
@@ -655,6 +747,25 @@ public class EntityRule {
|
||||
}
|
||||
|
||||
void validate(Context context) throws JSONException, IllegalArgumentException {
|
||||
Expression expression = getExpression(this, null);
|
||||
if (expression != null)
|
||||
try {
|
||||
for (String variable : expression.getUsedVariables()) {
|
||||
Log.i("EXPR variable=" + variable);
|
||||
if (!EXPR_VARIABLES.contains(variable))
|
||||
throw new IllegalArgumentException("Unknown variable '" + variable + "'");
|
||||
}
|
||||
Log.i("EXPR validating");
|
||||
expression.validate();
|
||||
Log.i("EXPR validated");
|
||||
} catch (ParseException ex) {
|
||||
Log.w("EXPR", ex);
|
||||
String message = ex.getMessage();
|
||||
if (TextUtils.isEmpty(message))
|
||||
message = "Invalid expression";
|
||||
throw new IllegalArgumentException(message, ex);
|
||||
}
|
||||
|
||||
JSONObject jargs = new JSONObject(action);
|
||||
int type = jargs.getInt("type");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user