diff --git a/src/main/java/org/olat/admin/layout/FooterInformations.java b/src/main/java/org/olat/admin/layout/FooterInformations.java index 750d0a008fdaaedcb0b74fdc1a99d0d083a06d22..9f20811882e30e6b24dbcda6135a7c52caeaa15c 100644 --- a/src/main/java/org/olat/admin/layout/FooterInformations.java +++ b/src/main/java/org/olat/admin/layout/FooterInformations.java @@ -19,8 +19,8 @@ */ package org.olat.admin.layout; +import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; -import org.olat.core.util.mail.MailHelper; /** * @@ -61,9 +61,10 @@ public class FooterInformations { } String parsedFooterLine = null; - parsedFooterLine = dbFooterLine.replaceAll(MailHelper.MAIL_REGEX, "<a href=\"mailto:$0\">$0</a>"); - String urlregex = "((http(s?)://)|(www.))(([\\w-.]+)*(/[^[:space:]]+)*)"; - parsedFooterLine = parsedFooterLine.replaceAll(urlregex, "<a href=\"http$3://$4$5\" target=\"_blank\">$2$4$5</a>"); + + parsedFooterLine = Formatter.formatURLsAsLinks(dbFooterLine, false); + parsedFooterLine = Formatter.formatMailsAsLinks(parsedFooterLine, false); + return parsedFooterLine; } } \ No newline at end of file diff --git a/src/main/java/org/olat/commons/calendar/ui/CalendarDetailsController.java b/src/main/java/org/olat/commons/calendar/ui/CalendarDetailsController.java index a634dd7e8d5a7cb87c83ee8b1f776a5c2e945687..291c26d5ed78d1ad8f9d169ab5d2e6b5fa2a89d7 100644 --- a/src/main/java/org/olat/commons/calendar/ui/CalendarDetailsController.java +++ b/src/main/java/org/olat/commons/calendar/ui/CalendarDetailsController.java @@ -91,7 +91,7 @@ public class CalendarDetailsController extends BasicController { } else { mainVC.contextPut("subject", event.getSubject()); // format line breaks and render links as clickable links - StringBuilder description = Formatter.stripTabsAndReturns(Formatter.formatURLsAsLinks(event.getDescription())); + StringBuilder description = Formatter.stripTabsAndReturns(Formatter.formatURLsAsLinks(event.getDescription(), true)); mainVC.contextPut("description", description.toString()); if(StringHelper.containsNonWhitespace(event.getLocation())) { mainVC.contextPut("location", event.getLocation()); diff --git a/src/main/java/org/olat/commons/info/ui/InfoDisplayController.java b/src/main/java/org/olat/commons/info/ui/InfoDisplayController.java index e957958cfbe86eae24762203c2f1524efae7bb4e..eb642507d53e7bdee8b049917b831ef69b5aabf8 100644 --- a/src/main/java/org/olat/commons/info/ui/InfoDisplayController.java +++ b/src/main/java/org/olat/commons/info/ui/InfoDisplayController.java @@ -280,7 +280,7 @@ public class InfoDisplayController extends FormBasicController { message = message.toString(); } else if(StringHelper.containsNonWhitespace(message)) { message = Formatter.escWithBR(info.getMessage()).toString(); - message = Formatter.formatURLsAsLinks(message); + message = Formatter.formatURLsAsLinks(message, true); } Formatter formatter = Formatter.getInstance(getLocale()); diff --git a/src/main/java/org/olat/core/commons/services/license/ui/LicenseUIFactory.java b/src/main/java/org/olat/core/commons/services/license/ui/LicenseUIFactory.java index 46c9d28920ec6179960de2999364a9d4b8ff7c00..47528e9d7382185369b726e28f3a7d00684d3994 100644 --- a/src/main/java/org/olat/core/commons/services/license/ui/LicenseUIFactory.java +++ b/src/main/java/org/olat/core/commons/services/license/ui/LicenseUIFactory.java @@ -87,7 +87,7 @@ public class LicenseUIFactory { public static String getFormattedLicenseText(License license) { String licenseText = getLicenseText(license); - return Formatter.formatURLsAsLinks(Formatter.escWithBR(licenseText).toString()); + return Formatter.formatURLsAsLinks(Formatter.escWithBR(licenseText).toString(), true); } public static String getLicenseText(License license) { diff --git a/src/main/java/org/olat/core/gui/render/velocity/VelocityRenderDecorator.java b/src/main/java/org/olat/core/gui/render/velocity/VelocityRenderDecorator.java index aee9f3731762aea842a731d3d0428be3bd60f74f..f851a8f52fe3c275913bfe9e98cfad09fc53a1dd 100644 --- a/src/main/java/org/olat/core/gui/render/velocity/VelocityRenderDecorator.java +++ b/src/main/java/org/olat/core/gui/render/velocity/VelocityRenderDecorator.java @@ -1057,7 +1057,7 @@ public class VelocityRenderDecorator implements Closeable { * @return text with clickable links */ public static String formatURLsAsLinks(String textFragment) { - return Formatter.formatURLsAsLinks(textFragment); + return Formatter.formatURLsAsLinks(textFragment, true); } /** diff --git a/src/main/java/org/olat/core/util/Formatter.java b/src/main/java/org/olat/core/util/Formatter.java index 8d9cc400bfbac593669b65fa3753775c2e05aa18..fb6a2249dfae00bdbcea8c870f0d116ba0ebd0f6 100644 --- a/src/main/java/org/olat/core/util/Formatter.java +++ b/src/main/java/org/olat/core/util/Formatter.java @@ -694,9 +694,10 @@ public class Formatter { * HTML link objects. * * @param textFragment + * @param addIcon TODO * @return text with clickable links */ - public static String formatURLsAsLinks(String textFragment) { + public static String formatURLsAsLinks(String textFragment, boolean addIcon) { if(textFragment == null) return ""; Matcher matcher = urlPattern.matcher(textFragment); @@ -726,15 +727,18 @@ public class Formatter { } // OpenOLAT URL's are opened in same window, all other URL's in separate window else if (!url.startsWith(Settings.getServerContextPathURI())) { - sb.append(" target=\"_blank\""); + sb.append(" target=\"_blank\"") + .append(" rel=\"noopener noreferrer\""); } sb.append(">"); - if (url.startsWith("mailto")) { - sb.append("<i class='o_icon o_icon_mail'> </i> "); - } else if (!url.startsWith(Settings.getServerContextPathURI())) { - sb.append("<i class='o_icon o_icon_link_extern'> </i> "); - } else { - sb.append("<i class='o_icon o_icon_star'> </i> "); + if (addIcon) { + if (url.startsWith("mailto")) { + sb.append("<i class='o_icon o_icon_mail'> </i> "); + } else if (!url.startsWith(Settings.getServerContextPathURI())) { + sb.append("<i class='o_icon o_icon_link_extern'> </i> "); + } else { + sb.append("<i class='o_icon o_icon_star'> </i> "); + } } sb.append(url); @@ -746,6 +750,22 @@ public class Formatter { return sb.toString(); } + private static final Pattern mailPattern = Pattern.compile("(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])"); + + /** + * Search in given text fragment for mail addresses and surround them with clickable links + * + * @param textFragment + * @return + */ + public static String formatMailsAsLinks(String textFragment, boolean addIcon) { + if(textFragment == null) return ""; + + Matcher matcher = mailPattern.matcher(textFragment); + + return matcher.replaceAll("<a href=\"mailto:$0\">" + (addIcon ? "<i class='o_icon o_icon_mail'> </i> $0" : "$0") + "</a>"); + } + /* emoticon patterns */ private static final Pattern angelPattern = Pattern.compile("(O\\:-*(\\)|3))"); diff --git a/src/main/java/org/olat/core/util/mail/MailHelper.java b/src/main/java/org/olat/core/util/mail/MailHelper.java index f6304194d5b027450ba00389680f423a498d93a0..5c98ddca8b3b4d97ba2b129444f47767902acdd5 100644 --- a/src/main/java/org/olat/core/util/mail/MailHelper.java +++ b/src/main/java/org/olat/core/util/mail/MailHelper.java @@ -63,8 +63,6 @@ import org.olat.user.propertyhandlers.UserPropertyHandler; */ public class MailHelper { - public static final String MAIL_REGEX = "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])"; - private static final Comparator<String> CASE_INSENSTIVE = Comparator.comparing(Function.identity(), String.CASE_INSENSITIVE_ORDER); private static Map<String, Translator> translators = new HashMap<>(); diff --git a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamMetadataController.java b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamMetadataController.java index d36d9c171d757f50227f3df6fa67acc71a08f59b..7c654677aa0b695ea56e594b01d5dde254f006a7 100644 --- a/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamMetadataController.java +++ b/src/main/java/org/olat/course/nodes/livestream/ui/LiveStreamMetadataController.java @@ -65,7 +65,7 @@ public class LiveStreamMetadataController extends BasicController { mainVC.contextPut("id", event.getId()); mainVC.contextPut("title", event.getSubject()); addDateToMainVC(event); - StringBuilder description = Formatter.stripTabsAndReturns(Formatter.formatURLsAsLinks(event.getDescription())); + StringBuilder description = Formatter.stripTabsAndReturns(Formatter.formatURLsAsLinks(event.getDescription(), true)); mainVC.contextPut("description", description.toString()); if (StringHelper.containsNonWhitespace(event.getLocation())) { mainVC.contextPut("location", event.getLocation()); diff --git a/src/main/java/org/olat/instantMessaging/ui/ChatController.java b/src/main/java/org/olat/instantMessaging/ui/ChatController.java index cd3ac937b5c9b9e770329c1132821e2ef43fdb90..8dd01db681b183fad37be11ba4d42741f8ba4901 100644 --- a/src/main/java/org/olat/instantMessaging/ui/ChatController.java +++ b/src/main/java/org/olat/instantMessaging/ui/ChatController.java @@ -406,7 +406,7 @@ public class ChatController extends BasicController implements GenericEventListe } private String prepareMsgBody(String body) { - body = Formatter.formatURLsAsLinks(body); + body = Formatter.formatURLsAsLinks(body, true); body = Formatter.formatEmoticonsAsImages(body); return body; } diff --git a/src/main/java/org/olat/modules/lecture/ui/TeacherRollCallController.java b/src/main/java/org/olat/modules/lecture/ui/TeacherRollCallController.java index a27872f58b2c2e53b4abf39735042c22c98703a6..24ac47df75f552ca881e93bfc38d192bb3ca3f75 100644 --- a/src/main/java/org/olat/modules/lecture/ui/TeacherRollCallController.java +++ b/src/main/java/org/olat/modules/lecture/ui/TeacherRollCallController.java @@ -187,9 +187,9 @@ public class TeacherRollCallController extends FormBasicController { layoutCont.contextPut("teachers", sb.toString()); layoutCont.contextPut("lectureBlockTitle", StringHelper.escapeHtml(lectureBlock.getTitle())); layoutCont.contextPut("lectureBlockExternalId", StringHelper.escapeHtml(lectureBlock.getExternalId())); - StringBuilder description = Formatter.stripTabsAndReturns(Formatter.formatURLsAsLinks(lectureBlock.getDescription())); + StringBuilder description = Formatter.stripTabsAndReturns(Formatter.formatURLsAsLinks(lectureBlock.getDescription(), true)); layoutCont.contextPut("lectureBlockDescription", StringHelper.xssScan(description)); - StringBuilder preparation = Formatter.stripTabsAndReturns(Formatter.formatURLsAsLinks(lectureBlock.getPreparation())); + StringBuilder preparation = Formatter.stripTabsAndReturns(Formatter.formatURLsAsLinks(lectureBlock.getPreparation(), true)); layoutCont.contextPut("lectureBlockPreparation", StringHelper.xssScan(preparation)); layoutCont.contextPut("lectureBlockLocation", StringHelper.escapeHtml(lectureBlock.getLocation())); layoutCont.contextPut("lectureBlock",lectureBlock); diff --git a/src/main/java/org/olat/modules/qpool/ui/metadata/RightsMetadataEditController.java b/src/main/java/org/olat/modules/qpool/ui/metadata/RightsMetadataEditController.java index a85e123d0d2db6a6f2dd4d28d43d015e9a7cbfec..68ac093ff2a9cf5b50fb1efc47a88f3728a3f5f2 100644 --- a/src/main/java/org/olat/modules/qpool/ui/metadata/RightsMetadataEditController.java +++ b/src/main/java/org/olat/modules/qpool/ui/metadata/RightsMetadataEditController.java @@ -191,7 +191,7 @@ public class RightsMetadataEditController extends FormBasicController { sb.append("'> </i></div>"); } String licenseTypeText = licenseType.getText() != null? licenseType.getText(): ""; - String formattedLicenseText = Formatter.formatURLsAsLinks(Formatter.escWithBR(licenseTypeText).toString()); + String formattedLicenseText = Formatter.formatURLsAsLinks(Formatter.escWithBR(licenseTypeText).toString(), true); if (StringHelper.containsNonWhitespace(formattedLicenseText)) { sb.append(formattedLicenseText); } diff --git a/src/test/java/org/olat/core/util/FormatterTest.java b/src/test/java/org/olat/core/util/FormatterTest.java index 54db23da304b7717546b2c5174c20340bc2cad97..4749d2b29277a95f044e36235262b3261c2b5fa8 100644 --- a/src/test/java/org/olat/core/util/FormatterTest.java +++ b/src/test/java/org/olat/core/util/FormatterTest.java @@ -19,8 +19,10 @@ */ package org.olat.core.util; +import java.util.ArrayList; import java.util.Date; import java.util.GregorianCalendar; +import java.util.List; import java.util.Locale; import java.util.UUID; @@ -141,4 +143,44 @@ public class FormatterTest { Assert.assertNotNull(latextFormatterJs); Assert.assertTrue(latextFormatterJs.contains(domId)); } + + @Test + public void testMailTransformation() { + // Valid Mails + List<String> validMails = new ArrayList<>(); + validMails.add("abc.def@mail.cc"); + validMails.add("abc.def@mail-archive.com"); + validMails.add("abc.def@mail.org"); + validMails.add("abc.def@mail.com"); + validMails.add("abc.def@mail.com"); + validMails.add("abc@mail.com"); + validMails.add("abc_def@mail.com"); + validMails.add("abc-d@mail.com"); + + // Invalid Mails + List<String> invalidMails = new ArrayList<>(); + invalidMails.add("abc.def@mail#archive.com"); + invalidMails.add("abc.def@mail"); + invalidMails.add("abc.def@mail..com"); + + for (String validMail : validMails) { + String valid = Formatter.formatMailsAsLinks(validMail, false); + String validIcon = Formatter.formatMailsAsLinks(validMail, true); + + Assert.assertTrue(valid.contains("<a")); + Assert.assertTrue(valid.contains("</a>")); + + Assert.assertTrue(validIcon.contains("<a")); + Assert.assertTrue(validIcon.contains("</a>")); + Assert.assertTrue(validIcon.contains("<i")); + Assert.assertTrue(validIcon.contains("</i>")); + } + + for (String invalidMail: invalidMails) { + invalidMail = Formatter.formatMailsAsLinks(invalidMail, false); + + Assert.assertTrue(!invalidMail.contains("<a")); + Assert.assertTrue(!invalidMail.contains("</a>")); + } + } }