Initial work on EMAIL-198
diff --git a/src/main/java/org/apache/commons/mail/re/AbstractMatcher.java b/src/main/java/org/apache/commons/mail/re/AbstractMatcher.java
new file mode 100644
index 0000000..985623e
--- /dev/null
+++ b/src/main/java/org/apache/commons/mail/re/AbstractMatcher.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.mail.re;
+
+
+/** Abstract base class for deriving implementations of {@link IMatcher}.
+ */
+public abstract class AbstractMatcher implements IMatcher {
+
+ /** Checks, whether a given word is in the given text at the given offset.
+ * @param text The text, in which the word should be present.
+ * @param offset The offset, at which to look for the word.
+ * @param word The word to look for.
+ * @param caseInsensitive Whether the match might be case insensitive.
+ * @return True, if the word is founf. Otherwise false.
+ */
+ protected boolean isWordAt(String text, int offset, String word, boolean caseInsensitive) {
+ if (offset+word.length() <= text.length()) {
+ for (int i = 0; i < word.length(); i++) {
+ final char c1 = text.charAt(offset+i);
+ final char c2 = word.charAt(i);
+ if (caseInsensitive) {
+ if (Character.toLowerCase(c1) != Character.toLowerCase(c2)) {
+ return false;
+ }
+ } else {
+ if (c1 != c2) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/mail/re/AttributeSkipper.java b/src/main/java/org/apache/commons/mail/re/AttributeSkipper.java
new file mode 100644
index 0000000..34895f5
--- /dev/null
+++ b/src/main/java/org/apache/commons/mail/re/AttributeSkipper.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.mail.re;
+
+/** An implementation of {@link IMatcher}, that implements the regular expression
+ * <pre>
+ * \\s*[^>]*?\\s+
+ * </pre>
+ */
+public class AttributeSkipper extends AbstractMatcher {
+ @Override
+ public void find(String text, int startOffset, int endOffset, IListener listener) throws TerminationRequest {
+ // Implement the initial \s*
+ int offset2 = startOffset;
+ for (int i = startOffset; i < endOffset; i++) {
+ final char c = text.charAt(i);
+ if (!Character.isWhitespace(c)) {
+ break;
+ } else {
+ ++offset2;
+ }
+ }
+ if (offset2 > startOffset) {
+ listener.match(this, text, startOffset, offset2);
+ }
+ // Implement the [^>]*
+ int offset3 = offset2;
+ for (int i = startOffset; i < endOffset; i++) {
+ final char c = text.charAt(i);
+ if (Character.isWhitespace(c)) {
+ for (int j = offset3; j < endOffset; j++) {
+ if (Character.isWhitespace(text.charAt(j))) {
+ if (j+1 > offset2) {
+ listener.match(this, text, startOffset, j+1);
+ } else {
+ // Already notified, do nothing.
+ }
+ }
+ }
+ } else if (c == '>') {
+ return;
+ } else {
+ ++offset3;
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/commons/mail/re/AttributeValueMatcher.java b/src/main/java/org/apache/commons/mail/re/AttributeValueMatcher.java
new file mode 100644
index 0000000..6777938
--- /dev/null
+++ b/src/main/java/org/apache/commons/mail/re/AttributeValueMatcher.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.mail.re;
+
+public class AttributeValueMatcher extends AbstractMatcher {
+ private final String expectedAttributeName;
+ private int attributeValueStart = -1;
+ private int attributeValueEnd = -1;
+
+ public AttributeValueMatcher(String expectedAttributeName) {
+ this.expectedAttributeName = expectedAttributeName;
+ }
+
+ @Override
+ public void find(String text, int startOffset, int endOffset, IListener listener) throws TerminationRequest {
+ }
+
+ public int getAttributeValueStart() {
+ if (attributeValueStart == -1) {
+ throw new IllegalStateException("The attribute value start is only available inside IListener.match()");
+ }
+ return attributeValueStart;
+ }
+
+ public int getAttributeValueEnd() {
+ if (attributeValueEnd == -1) {
+ throw new IllegalStateException("The attribute value end is only available inside IListener.match()");
+ }
+ return attributeValueEnd;
+ }
+}
diff --git a/src/main/java/org/apache/commons/mail/re/HtmlTagMatcher.java b/src/main/java/org/apache/commons/mail/re/HtmlTagMatcher.java
new file mode 100644
index 0000000..251496b
--- /dev/null
+++ b/src/main/java/org/apache/commons/mail/re/HtmlTagMatcher.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.mail.re;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** A {@link IMatcher} for opening HTML tags, like <pre><img</pre>,
+ * or <pre><src</pre>. Ignores attribute definitions, and the closing
+ * > character.
+ */
+public class HtmlTagMatcher extends AbstractMatcher {
+ private final List<String> elementNames;
+ private String currentElementName;
+
+ public HtmlTagMatcher(String[] elementNames) {
+ this.elementNames = Arrays.asList(elementNames);
+ }
+
+ @Override
+ public void find(String text, int startOffset, int endOffset, IMatcher.IListener listener) {
+ for (int i = startOffset; i < endOffset; i++) {
+ final char c = text.charAt(i);
+ if (c == '<') {
+ for (int j = 0; j < elementNames.size(); j++) {
+ final String elementName = elementNames.get(j);
+ if (isWordAt(text, i+1, elementName, true)) {
+ currentElementName = elementName;
+ listener.match(this, text, i, i+1+elementName.length());
+ currentElementName = null;
+ }
+ }
+ }
+ }
+ }
+
+ public String getCurrentElementName() {
+ if (currentElementName == null) {
+ throw new IllegalStateException("The current element name is only available inside IListener.match()");
+ }
+ return currentElementName;
+ }
+}
diff --git a/src/main/java/org/apache/commons/mail/re/IMatcher.java b/src/main/java/org/apache/commons/mail/re/IMatcher.java
new file mode 100644
index 0000000..709371b
--- /dev/null
+++ b/src/main/java/org/apache/commons/mail/re/IMatcher.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.mail.re;
+
+
+/** An {@link IMatcher} is a helper classes, that is designed to replace
+ * regular expressions, like
+ * <pre>
+ * (<[Ii][Mm][Gg]\\s*[^>]*?\\s+[Ss][Rr][Cc]\\s*=\\s*[\"'])([^\"']+?)([\"'])
+ * </pre>
+ * The reason for using the helper classes, and not a simple regular
+ * expression is a performance problem of the latter, that we wish to overcome.
+ * The main idea of the {@link IMatcher} is to divide the regular expression into a
+ * cascade of so-called matchers. A matcher replaces a comparatively simple
+ * subexpression, like <pre>\\s*</pre>. The simplicity of the replaced expression
+ * allows a manual, performace optimized implementation of the corresponding
+ * matcher.
+ */
+public interface IMatcher {
+ /** This exception is being thrown, if the search for matches should
+ * be terminated.
+ */
+ public static class TerminationRequest extends RuntimeException {
+ private static final long serialVersionUID = 5706488409254083692L;
+
+ public TerminationRequest() {
+ }
+ }
+ /** Interface of a match listener, which is being invoked, if a match
+ * has been found.
+ */
+ public interface IListener {
+ /** Called, if a match has been found.
+ * @param matcher The matcher, which is sending the notification.
+ * @param text The text, in which a match has been found.
+ * @param startOffset Offset of the matches first character in the text.
+ * @param endOffset Offset of the first character in the text, that
+ * follows after the match.
+ * @throws TerminationRequest The caller is supposed to stop
+ * searching for further matches.
+ */
+ void match(IMatcher matcher, String text, int startOffset, int endOffset) throws TerminationRequest;
+ }
+
+ /** Called to find matches in the given text.
+ * @param text The text, in which a match has been found.
+ * @param startOffset Offset of the matches first character in the text.
+ * @param endOffset Offset of the first character in the text, that
+ * follows after the match.
+ * @param listener The listener, which is being notified in case of matches.
+ * @throws TerminationRequest The caller is supposed to stop
+ * searching for further matches.
+ */
+ void find(String text, int startOffset, int endOffset, IListener listener) throws TerminationRequest;
+}
diff --git a/src/main/java/org/apache/commons/mail/re/ImageTagHandler.java b/src/main/java/org/apache/commons/mail/re/ImageTagHandler.java
new file mode 100644
index 0000000..d1ce128
--- /dev/null
+++ b/src/main/java/org/apache/commons/mail/re/ImageTagHandler.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.mail.re;
+
+import java.util.function.Function;
+
+import org.apache.commons.mail.ImageHtmlEmail;
+import org.apache.commons.mail.re.IMatcher.TerminationRequest;
+
+/** The {@link ImageTagHandler} implements the regular expressions
+ * <pre>
+ * (<[Ii][Mm][Gg]\\s*[^>]*?\\s+[Ss][Rr][Cc]\\s*=\\s*[\"'])([^\"']+?)([\"'])
+ * </pre>
+ * , and,
+ * <pre>
+ * (<[Ss][Cc][Rr][Ii][Pp][Tt]\\s*.*?\\s+[Ss][Rr][Cc]\\s*=\\s*[\"'])([^\"']+?)([\"'])
+ * </pre>
+ * ({@link ImageHtmlEmail#REGEX_IMG_SRC}, and {@link ImageHtmlEmail#REGEX_SCRIPT_SRC})
+ * with instances of {@link IMatcher}.
+ */
+public class ImageTagHandler {
+ private static class MatchFoundException extends TerminationRequest {
+ private static final long serialVersionUID = 1206347581416053598L;
+ private final int matchStartOffset, matchEndOffset;
+
+ public MatchFoundException(int matchStartOffset, int matchEndOffset) {
+ this.matchStartOffset = matchStartOffset;
+ this.matchEndOffset = matchEndOffset;
+ }
+
+ public int getMatchStartOffset() {
+ return matchStartOffset;
+ }
+
+ public int getMatchEndOffset() {
+ return matchEndOffset;
+ }
+ }
+
+ public String findReferences(String text, Function<String,String> editor) {
+ final HtmlTagMatcher htm = new HtmlTagMatcher(new String[] {"img", "script"});
+ final AttributeSkipper as = new AttributeSkipper();
+ final AttributeValueMatcher avm = new AttributeValueMatcher("src");
+
+ String txt = text;
+ for (;;) {
+ MatchFoundException mfe;
+ try {
+ htm.find(text, 0, text.length(), (m,t,s,e) -> {
+ as.find(t, e, text.length(), (m2,t2,s2,e2) -> {
+ avm.find(t2, e2, t2.length(), (m3,t3,s3,e3) -> {
+ throw new MatchFoundException(avm.getAttributeValueStart(), avm.getAttributeValueEnd());
+ });
+ });
+ });
+ mfe = null;
+ } catch (MatchFoundException e) {
+ mfe = e;
+ }
+ if (mfe != null) {
+ txt = txt.substring(0, mfe.matchStartOffset)
+ + editor.apply(txt.substring(mfe.matchStartOffset+1, mfe.matchEndOffset))
+ + txt.substring(mfe.matchEndOffset);
+ } else {
+ break;
+ }
+ }
+ return txt;
+ }
+}
diff --git a/src/main/java/org/apache/commons/mail/re/package-info.java b/src/main/java/org/apache/commons/mail/re/package-info.java
new file mode 100644
index 0000000..32fe6b8
--- /dev/null
+++ b/src/main/java/org/apache/commons/mail/re/package-info.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.mail.re;
+
+/** This package provides helper classes, that are designed to replace
+ * regular expressions, like
+ * <pre>
+ * (<[Ii][Mm][Gg]\\s*[^>]*?\\s+[Ss][Rr][Cc]\\s*=\\s*[\"'])([^\"']+?)([\"'])
+ * </pre>
+ * The reason for using the helper classes, and not a simple regular
+ * expression is a performance problem of the latter, that we wish to overcome.
+ * The main idea of the helper class is to divide the regular expression into a
+ * cascade of so-called matchers. A matcher replaces a comparatively simple
+ * subexpression, like <pre>\\s*</pre>. The simplicity of the replaced expression
+ * allows a manual, performace optimized implementation of the corresponding
+ * matcher.
+ */
diff --git a/src/test/java/org/apache/commons/mail/AbstractEmailTest.java b/src/test/java/org/apache/commons/mail/AbstractEmailTest.java
index 8c1324f..a9dfb06 100644
--- a/src/test/java/org/apache/commons/mail/AbstractEmailTest.java
+++ b/src/test/java/org/apache/commons/mail/AbstractEmailTest.java
@@ -26,6 +26,7 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
+import java.net.BindException;
import java.net.URL;
import java.util.Date;
import java.util.Enumeration;
@@ -199,7 +200,14 @@
this.fakeMailServer = new Wiser();
this.fakeMailServer.setPort(getMailServerPort());
- this.fakeMailServer.start();
+ try {
+ this.fakeMailServer.start();
+ } catch (RuntimeException e) {
+ if (e.getCause() != null && e.getCause() instanceof BindException) {
+ throw new IllegalStateException("Port " + getMailServerPort()
+ + " is already in use.");
+ }
+ }
assertFalse("fake mail server didn't start", isMailServerStopped(fakeMailServer));
diff --git a/src/test/java/org/apache/commons/mail/ImageHtmlEmailTest.java b/src/test/java/org/apache/commons/mail/ImageHtmlEmailTest.java
index 5f52098..026264c 100644
--- a/src/test/java/org/apache/commons/mail/ImageHtmlEmailTest.java
+++ b/src/test/java/org/apache/commons/mail/ImageHtmlEmailTest.java
@@ -477,6 +477,26 @@
email.getCcAddresses(), email.getBccAddresses(), true);
}
+ @Test
+ public void testSlowRegularExpressions() throws Exception {
+ ImageHtmlEmail mail = new ImageHtmlEmail();
+ mail.setHostName("example.com");
+ mail.setFrom("from@example.com");
+ mail.addTo("to@example.com");
+ StringBuilder text = new StringBuilder("<img");
+ for (int i = 0; i < 3000; i++) {
+ text.append(" ");
+ }
+ mail.setMsg("<html><body><pre>" + text + "</pre></body></html>");
+
+ long startTime = System.currentTimeMillis();
+ mail.buildMimeMessage();
+ long duration = System.currentTimeMillis() - startTime;
+ final int permittedDurationInSeconds = 200;
+ assertTrue("Run time exceeds permitted duration of " + permittedDurationInSeconds
+ + " seconds: " + duration, duration < permittedDurationInSeconds*1000);
+ }
+
private String loadUrlContent(final URL url) throws IOException {
final InputStream stream = url.openStream();
final StringBuilder html = new StringBuilder();
@@ -492,7 +512,6 @@
}
private static final class MockDataSourceClassPathResolver extends DataSourceClassPathResolver {
-
public MockDataSourceClassPathResolver(final String classPathBase, final boolean lenient) {
super(classPathBase, lenient);
}
@@ -504,6 +523,5 @@
ds.setName(null);
return ds;
}
-
}
}
diff --git a/src/test/java/org/apache/commons/mail/re/AttributeSkipperTest.java b/src/test/java/org/apache/commons/mail/re/AttributeSkipperTest.java
new file mode 100644
index 0000000..badfe43
--- /dev/null
+++ b/src/test/java/org/apache/commons/mail/re/AttributeSkipperTest.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.mail.re;
+
+import static org.junit.Assert.assertSame;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+
+public class AttributeSkipperTest {
+ /** Test the {@link AttributeSkipper} alone.
+ */
+ @Test
+ public void testStandalone() {
+ final AttributeSkipper as = new AttributeSkipper();
+ MatcherTests.assertMatch(as, "<img src='foo'>", 0, 0, 5);
+ MatcherTests.assertMatch(as, "<img src='foo'>", 4, 4, 5);
+ MatcherTests.assertNoMatch(as, "", 0);
+ }
+
+ /** Test the {@link AttributeSkipper} in combination with the {@link HtmlTagMatcher}.
+ */
+ @Test
+ public void testChain() {
+ assertChainMatch("<img src='foo'>", 0, 4, 5);
+ }
+
+ private void assertChainMatch(String text, int startOffset, int... expectedOffsets) {
+ final HtmlTagMatcher htm = new HtmlTagMatcher(new String[] {"script", "img"});
+ final AttributeSkipper as = new AttributeSkipper();
+ final List<Integer> actualOffsets = new ArrayList<>();
+ htm.find(text, startOffset, text.length(), (m,t,s,e) -> {
+ assertSame(htm,m);
+ assertSame(text,t);
+ as.find(text, e, text.length(), (m2,t2,s2,e2) -> {
+ assertSame(as,m2);
+ assertSame(text,t2);
+ actualOffsets.add(Integer.valueOf(s));
+ actualOffsets.add(Integer.valueOf(e));
+ });
+ });
+ }
+}
diff --git a/src/test/java/org/apache/commons/mail/re/HtmlTagMatcherTest.java b/src/test/java/org/apache/commons/mail/re/HtmlTagMatcherTest.java
new file mode 100644
index 0000000..fe36e6d
--- /dev/null
+++ b/src/test/java/org/apache/commons/mail/re/HtmlTagMatcherTest.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.mail.re;
+
+import org.junit.Test;
+
+public class HtmlTagMatcherTest {
+ @Test
+ public void testMatch() {
+ final HtmlTagMatcher htm = new HtmlTagMatcher(new String[] {"img", "src"});
+ MatcherTests.assertMatch(htm, "<img", 0, 0, 4);
+ MatcherTests.assertMatch(htm, "<img>", 0, 0, 4);
+ MatcherTests.assertMatch(htm, "<iMg", 0, 0, 4);
+ MatcherTests.assertMatch(htm, "<imG>", 0, 0, 4);
+ MatcherTests.assertMatch(htm, " <img", 0, 1, 5);
+ MatcherTests.assertMatch(htm, " <img>", 0, 1, 5);
+ MatcherTests.assertMatch(htm, " <img", 1, 1, 5);
+ MatcherTests.assertMatch(htm, " <img>", 1, 1, 5);
+ MatcherTests.assertMatch(htm, " <img>", 0, 1, 5);
+ MatcherTests.assertMatch(htm, " <img> <src", 0, 1, 5, 7, 11);
+ }
+}
diff --git a/src/test/java/org/apache/commons/mail/re/MatcherTests.java b/src/test/java/org/apache/commons/mail/re/MatcherTests.java
new file mode 100644
index 0000000..ead2693
--- /dev/null
+++ b/src/test/java/org/apache/commons/mail/re/MatcherTests.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.mail.re;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** Helper class for matcher tests.
+ */
+public class MatcherTests {
+
+ public static void assertMatch(IMatcher matcher, String text, int startOffset, int... expectedOffsets) {
+ final List<Integer> list = new ArrayList<>();
+ matcher.find(text, startOffset, text.length(), (m,t,s,e) -> {
+ assertSame(matcher,m);
+ assertSame(text,t);
+ list.add(Integer.valueOf(s));
+ list.add(Integer.valueOf(e));
+ });
+ assertEquals(expectedOffsets.length, list.size());
+ for (int i = 0; i < expectedOffsets.length; i++) {
+ assertEquals(String.valueOf(i), expectedOffsets[i], list.get(i).intValue());
+ }
+ }
+
+ public static void assertNoMatch(IMatcher matcher, String text, int startOffset) {
+ matcher.find(text, startOffset, text.length(), (m,t,s,e) -> {
+ throw new IllegalStateException("Unexpected match");
+ });
+ }
+
+}