This is an automated email from the ASF dual-hosted git repository.

jochen pushed a commit to branch Features/Email198
in repository https://gitbox.apache.org/repos/asf/commons-email.git

commit 1992a9d751fabcb4cd566b6ca37d127fce13c620
Author: Jochen Wiedmann <jochen.wiedm...@gmail.com>
AuthorDate: Thu Aug 12 12:24:17 2021 +0200

    Initial work on EMAIL-198
---
 .../apache/commons/mail/re/AbstractMatcher.java    | 51 +++++++++++++
 .../apache/commons/mail/re/AttributeSkipper.java   | 62 ++++++++++++++++
 .../commons/mail/re/AttributeValueMatcher.java     | 45 ++++++++++++
 .../org/apache/commons/mail/re/HtmlTagMatcher.java | 57 +++++++++++++++
 .../java/org/apache/commons/mail/re/IMatcher.java  | 69 ++++++++++++++++++
 .../apache/commons/mail/re/ImageTagHandler.java    | 84 ++++++++++++++++++++++
 .../org/apache/commons/mail/re/package-info.java   | 31 ++++++++
 .../org/apache/commons/mail/AbstractEmailTest.java | 10 ++-
 .../apache/commons/mail/ImageHtmlEmailTest.java    | 22 +++++-
 .../commons/mail/re/AttributeSkipperTest.java      | 59 +++++++++++++++
 .../apache/commons/mail/re/HtmlTagMatcherTest.java | 36 ++++++++++
 .../org/apache/commons/mail/re/MatcherTests.java   | 49 +++++++++++++
 12 files changed, 572 insertions(+), 3 deletions(-)

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*[^&gt;]*?\\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>&lt;img</pre>,
+ * or <pre>&lt;src</pre>. Ignores attribute definitions, and the closing
+ * &gt; 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>
+ *   
(&lt;[Ii][Mm][Gg]\\s*[^&gt;]*?\\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>
+ *   
(&lt;[Ii][Mm][Gg]\\s*[^&gt;]*?\\s+[Ss][Rr][Cc]\\s*=\\s*[\"'])([^\"']+?)([\"'])
+ * </pre>
+ * , and,
+ * <pre>
+ *   
(&lt;[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>
+ *   
(&lt;[Ii][Mm][Gg]\\s*[^&gt;]*?\\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.BufferedOutputStream;
 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 @@ public abstract class AbstractEmailTest
 
             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 @@ public class ImageHtmlEmailTest extends HtmlEmailTest {
                      email.getCcAddresses(), email.getBccAddresses(), true);
     }
 
+    @Test
+    public void testSlowRegularExpressions() throws Exception {
+        ImageHtmlEmail mail = new ImageHtmlEmail();
+        mail.setHostName("example.com");
+        mail.setFrom("f...@example.com");
+        mail.addTo("t...@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 @@ public class ImageHtmlEmailTest extends HtmlEmailTest {
     }
 
     private static final class MockDataSourceClassPathResolver extends 
DataSourceClassPathResolver {
-
         public MockDataSourceClassPathResolver(final String classPathBase, 
final boolean lenient) {
             super(classPathBase, lenient);
         }
@@ -504,6 +523,5 @@ public class ImageHtmlEmailTest extends HtmlEmailTest {
             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");
+               });
+       }
+
+}

Reply via email to