001/* 002 * Shredzone Commons 003 * 004 * Copyright (C) 2012 Richard "Shred" Körber 005 * http://commons.shredzone.org 006 * 007 * This program is free software: you can redistribute it and/or modify 008 * it under the terms of the GNU Library General Public License as 009 * published by the Free Software Foundation, either version 3 of the 010 * License, or (at your option) any later version. 011 * 012 * This program is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 015 * GNU General Public License for more details. 016 * 017 * You should have received a copy of the GNU Library General Public License 018 * along with this program. If not, see <http://www.gnu.org/licenses/>. 019 */ 020package org.shredzone.commons.text.filter; 021 022import java.util.regex.Matcher; 023import java.util.regex.Pattern; 024 025import edu.umd.cs.findbugs.annotations.Nullable; 026import org.shredzone.commons.text.TextFilter; 027 028/** 029 * A filter that detects links in a text, and creates an HTML <a> tag around each 030 * link. http, https and ftp protocols are detected. 031 * 032 * @author Richard "Shred" Körber 033 */ 034public class LinkToUrlFilter implements TextFilter { 035 036 // TODO: allow trailing period, comma etc. 037 private static final Pattern URL_PATTERN = Pattern.compile( 038 "(.*?)((?:https?|ftp)://\\S+?)(?=[.,;:!)]?\\s|$)", 039 Pattern.CASE_INSENSITIVE 040 ); 041 042 private boolean noFollow = false; 043 private boolean noReferrer = false; 044 private boolean noOpener = true; 045 private @Nullable String target = null; 046 private @Nullable String tag = null; 047 048 /** 049 * Creates a new {@link LinkToUrlFilter}. 050 */ 051 public LinkToUrlFilter() { 052 updateTag(); 053 } 054 055 /** 056 * Sets the way search engines evaluate the created link. If set to {@code false}, a 057 * {@code rel="nofollow"} attribute is added to the link, so web crawlers will not 058 * follow to the target. 059 * 060 * @param follow 061 * {@code true} if links should be followed by web crawlers. Defaults to 062 * {@code true}. 063 * @deprecated It is confusing that this property must be set to {@code false} in 064 * order to have a "nofollow" relationship. Use {@link #setNoFollow(boolean)} 065 * instead. 066 */ 067 @Deprecated 068 public void setFollow(boolean follow) { 069 setNoFollow(!follow); 070 } 071 072 /** 073 * Sets the way search engines evaluate the created link. If set to {@code true}, a 074 * {@code rel="nofollow"} attribute is added to the link, so web crawlers will not 075 * follow to the target. 076 * 077 * @param noFollow 078 * {@code true} if links should not be followed by web crawlers. Defaults to 079 * {@code false}. 080 * @since 2.6 081 */ 082 public void setNoFollow(boolean noFollow) { 083 this.noFollow = noFollow; 084 updateTag(); 085 } 086 087 /** 088 * Sets whether links with target "_blank" should have a "noopener" relationship. 089 * Activated by default. Note that deactivation poses a security risk for your 090 * website, and should only be done for a very good reason! 091 * 092 * @param noOpener 093 * {@code true} to set "noopener" relationships on all links with a "_blank" 094 * target. This is the default. 095 * @since 2.6 096 */ 097 public void setNoOpener(boolean noOpener) { 098 this.noOpener = noOpener; 099 updateTag(); 100 } 101 102 /** 103 * Sets wheter a "noreferrer" relationship shall be used. If {@code true} (and 104 * supported by the browser), the browser won't send a "Referer" header when following 105 * a link. 106 * 107 * @param noReferrer 108 * If {@code true}, a "noreferrer" relation is added to each link. {@code 109 * false} by default. 110 * @since 2.6 111 */ 112 public void setNoReferrer(boolean noReferrer) { 113 this.noReferrer = noReferrer; 114 updateTag(); 115 } 116 117 /** 118 * Sets the link's target attribute. 119 * 120 * @param target 121 * Link target, or {@code null} if no target is to be set. 122 */ 123 public void setTarget(@Nullable String target) { 124 this.target = target; 125 updateTag(); 126 } 127 128 /** 129 * Updates the tag template from the current settings. 130 */ 131 private void updateTag() { 132 StringBuilder sb = new StringBuilder("$1<a href=\"$2\""); 133 StringBuilder relSb = new StringBuilder(); 134 if (noFollow) { 135 relSb.append("nofollow "); 136 } 137 if (target != null) { 138 sb.append(" target=\"").append(target).append('"'); 139 if (noOpener && "_blank".equals(target)) { 140 relSb.append("noopener "); 141 } 142 } 143 if (noReferrer) { 144 relSb.append("noreferrer "); 145 } 146 if (relSb.length() > 0) { 147 sb.append(" rel=\"").append(relSb.toString().trim()).append('"'); 148 } 149 sb.append(">$2</a>"); 150 tag = sb.toString(); 151 } 152 153 @Override 154 public CharSequence apply(CharSequence text) { 155 Matcher m = URL_PATTERN.matcher(text); 156 if (!m.matches()) { 157 return text; 158 } 159 return m.replaceAll(tag); 160 } 161 162}