From a94ef980bbe03b265b0249a83fdb5c9eee4e8c32 Mon Sep 17 00:00:00 2001 From: Athou Date: Fri, 7 Nov 2014 08:46:09 +0100 Subject: [PATCH] cannot loop forever --- .../backend/feed/FeedEntryFilter.java | 40 +++++++++++++++++-- .../backend/service/FeedUpdateService.java | 3 +- .../commafeed/frontend/resource/FeedREST.java | 3 +- .../backend/feed/FeedEntryFilterTest.java | 24 ++++++----- 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java b/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java index 79ab69c8..f9ca6a5e 100644 --- a/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java +++ b/src/main/java/com/commafeed/backend/feed/FeedEntryFilter.java @@ -1,12 +1,20 @@ package com.commafeed.backend.feed; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + import lombok.RequiredArgsConstructor; -import org.apache.commons.jexl2.Expression; import org.apache.commons.jexl2.JexlContext; import org.apache.commons.jexl2.JexlEngine; +import org.apache.commons.jexl2.JexlException; import org.apache.commons.jexl2.JexlInfo; import org.apache.commons.jexl2.MapContext; +import org.apache.commons.jexl2.Script; import org.apache.commons.jexl2.introspection.JexlMethod; import org.apache.commons.jexl2.introspection.JexlPropertyGet; import org.apache.commons.jexl2.introspection.Uberspect; @@ -63,12 +71,17 @@ public class FeedEntryFilter { private final String filter; - public boolean matchesEntry(FeedEntry entry) { + public boolean matchesEntry(FeedEntry entry) throws FeedEntryFilterException { if (StringUtils.isBlank(filter)) { return true; } - Expression expression = ENGINE.createExpression(filter); + Script script = null; + try { + script = ENGINE.createScript(filter); + } catch (JexlException e) { + throw new FeedEntryFilterException("Exception while parsing expression " + filter, e); + } JexlContext context = new MapContext(); context.set("title", Jsoup.parse(entry.getContent().getTitle()).text().toLowerCase()); @@ -76,6 +89,25 @@ public class FeedEntryFilter { context.set("content", Jsoup.parse(entry.getContent().getContent()).text().toLowerCase()); context.set("url", entry.getUrl().toLowerCase()); - return (boolean) expression.evaluate(context); + Callable callable = script.callable(context); + Future future = Executors.newFixedThreadPool(1).submit(callable); + Object result = null; + try { + result = future.get(500, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + throw new FeedEntryFilterException("interrupted while evaluating expression " + filter, e); + } catch (ExecutionException e) { + throw new FeedEntryFilterException("Exception while evaluating expression " + filter, e); + } catch (TimeoutException e) { + throw new FeedEntryFilterException("Took too long evaluating expression " + filter, e); + } + return (boolean) result; + } + + @SuppressWarnings("serial") + public static class FeedEntryFilterException extends Exception { + public FeedEntryFilterException(String message, Throwable t) { + super(message, t); + } } } diff --git a/src/main/java/com/commafeed/backend/service/FeedUpdateService.java b/src/main/java/com/commafeed/backend/service/FeedUpdateService.java index 033fc734..437268af 100644 --- a/src/main/java/com/commafeed/backend/service/FeedUpdateService.java +++ b/src/main/java/com/commafeed/backend/service/FeedUpdateService.java @@ -14,6 +14,7 @@ import org.apache.commons.codec.digest.DigestUtils; import com.commafeed.backend.dao.FeedEntryDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.feed.FeedEntryFilter; +import com.commafeed.backend.feed.FeedEntryFilter.FeedEntryFilterException; import com.commafeed.backend.model.Feed; import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntryContent; @@ -52,7 +53,7 @@ public class FeedUpdateService { boolean matches = true; try { matches = filter.matchesEntry(entry); - } catch (Exception e) { + } catch (FeedEntryFilterException e) { log.error("could not evaluate filter {}", sub.getFilter(), e); } if (!matches) { diff --git a/src/main/java/com/commafeed/frontend/resource/FeedREST.java b/src/main/java/com/commafeed/frontend/resource/FeedREST.java index b16b349a..b9382d39 100644 --- a/src/main/java/com/commafeed/frontend/resource/FeedREST.java +++ b/src/main/java/com/commafeed/frontend/resource/FeedREST.java @@ -45,6 +45,7 @@ import com.commafeed.backend.dao.FeedCategoryDAO; import com.commafeed.backend.dao.FeedEntryStatusDAO; import com.commafeed.backend.dao.FeedSubscriptionDAO; import com.commafeed.backend.feed.FeedEntryFilter; +import com.commafeed.backend.feed.FeedEntryFilter.FeedEntryFilterException; import com.commafeed.backend.feed.FeedFetcher; import com.commafeed.backend.feed.FeedQueues; import com.commafeed.backend.feed.FeedUtils; @@ -437,7 +438,7 @@ public class FeedREST { try { new FeedEntryFilter(req.getFilter()).matchesEntry(TEST_ENTRY); - } catch (Exception e) { + } catch (FeedEntryFilterException e) { Throwable root = Throwables.getRootCause(e); return Response.status(Status.BAD_REQUEST).entity(root.getMessage()).build(); } diff --git a/src/test/java/com/commafeed/backend/feed/FeedEntryFilterTest.java b/src/test/java/com/commafeed/backend/feed/FeedEntryFilterTest.java index 256c1a4c..869710b6 100644 --- a/src/test/java/com/commafeed/backend/feed/FeedEntryFilterTest.java +++ b/src/test/java/com/commafeed/backend/feed/FeedEntryFilterTest.java @@ -1,10 +1,10 @@ package com.commafeed.backend.feed; -import org.apache.commons.jexl2.JexlException; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import com.commafeed.backend.feed.FeedEntryFilter.FeedEntryFilterException; import com.commafeed.backend.model.FeedEntry; import com.commafeed.backend.model.FeedEntryContent; @@ -27,39 +27,45 @@ public class FeedEntryFilterTest { } @Test - public void emptyFilterMatchesFilter() { + public void emptyFilterMatchesFilter() throws FeedEntryFilterException { FeedEntryFilter filter = new FeedEntryFilter(null); Assert.assertTrue(filter.matchesEntry(entry)); } @Test - public void blankFilterMatchesFilter() { + public void blankFilterMatchesFilter() throws FeedEntryFilterException { FeedEntryFilter filter = new FeedEntryFilter(""); Assert.assertTrue(filter.matchesEntry(entry)); } @Test - public void simpleExpression() { + public void simpleExpression() throws FeedEntryFilterException { FeedEntryFilter filter = new FeedEntryFilter("author eq 'athou'"); Assert.assertTrue(filter.matchesEntry(entry)); } - @Test(expected = JexlException.class) - public void newIsDisabled() { + @Test(expected = FeedEntryFilterException.class) + public void newIsDisabled() throws FeedEntryFilterException { FeedEntryFilter filter = new FeedEntryFilter("null eq new ('java.lang.String', 'athou')"); filter.matchesEntry(entry); } - @Test(expected = JexlException.class) - public void getClassMethodIsDisabled() { + @Test(expected = FeedEntryFilterException.class) + public void getClassMethodIsDisabled() throws FeedEntryFilterException { FeedEntryFilter filter = new FeedEntryFilter("null eq ''.getClass()"); filter.matchesEntry(entry); } @Test - public void dotClassIsDisabled() { + public void dotClassIsDisabled() throws FeedEntryFilterException { FeedEntryFilter filter = new FeedEntryFilter("null eq ''.class"); Assert.assertTrue(filter.matchesEntry(entry)); } + @Test(expected = FeedEntryFilterException.class) + public void cannotLoopForever() throws FeedEntryFilterException { + FeedEntryFilter filter = new FeedEntryFilter("while(true) {}"); + filter.matchesEntry(entry); + } + }