001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library 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 GNUAbstractCollection<String> 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.jsp.util; 029 030import org.opencms.file.CmsObject; 031import org.opencms.file.CmsResource; 032import org.opencms.jsp.CmsJspResourceWrapper; 033import org.opencms.main.CmsLog; 034import org.opencms.main.OpenCms; 035import org.opencms.relations.CmsLink; 036import org.opencms.relations.CmsRelationType; 037import org.opencms.site.CmsSite; 038import org.opencms.staticexport.CmsLinkProcessor; 039import org.opencms.staticexport.CmsLinkProcessor.ExternalLinkWhitelistInfo; 040import org.opencms.util.CmsStringUtil; 041import org.opencms.xml.types.CmsXmlVarLinkValue; 042 043import java.net.URI; 044import java.net.URISyntaxException; 045import java.util.AbstractCollection; 046import java.util.Collections; 047import java.util.Iterator; 048import java.util.Map; 049import java.util.Optional; 050import java.util.Set; 051import java.util.concurrent.ConcurrentHashMap; 052 053import org.apache.commons.lang3.StringUtils; 054import org.apache.commons.logging.Log; 055 056/** 057 * Wrapper for handling links in template/formatter JSP EL. 058 */ 059public class CmsJspLinkWrapper extends AbstractCollection<String> { 060 061 /** Logger instance for this class. */ 062 private static final Log LOG = CmsLog.getLog(CmsJspLinkWrapper.class); 063 064 /** Stored CMS context. */ 065 protected CmsObject m_cms; 066 067 /** Cached internal/external state. */ 068 protected Boolean m_internal; 069 070 /** The link literal from which this wrapper was created. */ 071 protected String m_link; 072 073 /** Cached link target resource. */ 074 protected Optional<CmsResource> m_resource; 075 076 /** Cached links (online, perma, server). */ 077 protected Map<String, String> m_stringCache = new ConcurrentHashMap<>(); 078 079 /** If <code>true</code> then empty links are allowed. */ 080 private boolean m_allowEmpty; 081 082 /** 083 * Creates a new link wrapper for a specific resource. 084 * 085 * @param cms the CMS context 086 * @param resource the resource to link to 087 */ 088 public CmsJspLinkWrapper(CmsObject cms, CmsResource resource) { 089 090 m_cms = cms; 091 m_link = cms.getSitePath(resource); 092 m_allowEmpty = false; 093 m_internal = Boolean.TRUE; 094 095 } 096 097 /** 098 * Creates a new link wrapper.<p> 099 * 100 * The link parameter should be in the same format that you enter in an XML content of field of type OpenCmsVarLink, i.e. 101 * either a full external URL or a site path with a query string attached. 102 * 103 * @param cms the CMS context 104 * @param link the link to wrap 105 */ 106 public CmsJspLinkWrapper(CmsObject cms, String link) { 107 108 this(cms, link, false); 109 } 110 111 /** 112 * Creates a new link wrapper.<p> 113 * 114 * The link parameter should be in the same format that you enter in an XML content of field of type OpenCmsVarLink, i.e. 115 * either a full external URL or a site path with a query string attached. 116 * 117 * @param cms the CMS context 118 * @param link the link to wrap 119 * @param allowEmpty if <code>true</code> then empty links are allowed 120 */ 121 public CmsJspLinkWrapper(CmsObject cms, String link, boolean allowEmpty) { 122 123 m_cms = cms; 124 m_link = link; 125 m_allowEmpty = allowEmpty; 126 } 127 128 /** 129 * @see java.lang.Object#equals(java.lang.Object) 130 */ 131 @Override 132 public boolean equals(Object obj) { 133 134 if (obj == this) { 135 return true; 136 } 137 if (obj instanceof CmsJspLinkWrapper) { 138 return obj.toString().equals(toString()); 139 } 140 return false; 141 } 142 143 /** 144 * Checks if the link is 'external', which is not just the opposite of 'internal', in this case. 145 * 146 * <p>A link is external if it's either an internal link pointing to a different site, or a link to a location outside 147 * OpenCms, unless the domain is listed in the sitemap attribute 'template.editor.links.externalWhitelist' for the current subsite. 148 * 149 * @return the 'external' status of the link 150 */ 151 public boolean getIsExternal() { 152 153 ExternalLinkWhitelistInfo whitelistInfo = CmsLinkProcessor.getExternalLinkWhitelistInfo(m_cms); 154 Set<String> whitelistSiteRoots = whitelistInfo.getSiteRoots(); 155 boolean result = false; 156 if (getIsInternal()) { 157 String rootPath = m_cms.getRequestContext().addSiteRoot(m_link); 158 CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(rootPath); 159 CmsSite currentSite = OpenCms.getSiteManager().getSiteForRootPath(m_cms.getRequestContext().getRootUri()); 160 if ((site != null) && (currentSite != null) && !currentSite.getSiteRoot().equals(site.getSiteRoot())) { 161 if (!whitelistSiteRoots.contains(site.getSiteRoot())) { 162 result = true; 163 } 164 } 165 } else { 166 result = true; 167 try { 168 URI uri = new URI(m_link); 169 if (uri.getHost() != null) { 170 if (whitelistInfo.getWhitelistEntries().stream().anyMatch( 171 entry -> entry.equals(uri.getHost()) || StringUtils.endsWith(uri.getHost(), "." + entry))) { 172 result = false; 173 } 174 } 175 } catch (Exception e) { 176 LOG.error(e.getLocalizedMessage(), e); 177 } 178 } 179 return result; 180 } 181 182 /** 183 * Returns <code>true</code> if the link is internal. 184 * 185 * @return <code>true</code> if the link is internal 186 */ 187 public boolean getIsInternal() { 188 189 if (m_internal == null) { 190 if (isEmpty()) { 191 m_internal = Boolean.FALSE; 192 } else { 193 m_internal = Boolean.valueOf( 194 null != CmsXmlVarLinkValue.getInternalPathAndQuery(m_cms, getServerLink())); 195 } 196 } 197 return m_internal.booleanValue(); 198 } 199 200 /** 201 * Performs normal link substitution. 202 * 203 * @return the substituted link 204 */ 205 public String getLink() { 206 207 return m_stringCache.computeIfAbsent( 208 "link", 209 k -> (!isEmpty() ? A_CmsJspValueWrapper.substituteLink(m_cms, m_link) : "")); 210 } 211 212 /** 213 * Gets the literal from which this wrapper was constructed. 214 * 215 * @return the original link literal 216 */ 217 public String getLiteral() { 218 219 return m_link; 220 } 221 222 /** 223 * Performs online link substitution. 224 * 225 * @return the online link 226 */ 227 public String getOnlineLink() { 228 229 return m_stringCache.computeIfAbsent( 230 "online", 231 k -> (!isEmpty() ? OpenCms.getLinkManager().getOnlineLink(m_cms, m_link) : "")); 232 } 233 234 /** 235 * Performs permalink substitution. 236 * 237 * @return the permalink 238 */ 239 public String getPermaLink() { 240 241 return m_stringCache.computeIfAbsent( 242 "perma", 243 k -> (!isEmpty() ? OpenCms.getLinkManager().getPermalink(m_cms, m_link) : "")); 244 } 245 246 /** 247 * Gets the resource wrapper for the link target. 248 * 249 * @return the resource wrapper for the target 250 */ 251 public CmsJspResourceWrapper getResource() { 252 253 if (m_resource == null) { 254 try { 255 String link = CmsXmlVarLinkValue.getInternalPathAndQuery(m_cms, getServerLink()); 256 if (link == null) { 257 m_resource = Optional.empty(); 258 } else { 259 CmsLink linkObj = new CmsLink(/*name=*/null, CmsRelationType.HYPERLINK, link, true); 260 linkObj.checkConsistency(m_cms); 261 m_resource = Optional.ofNullable(linkObj.getResource()); 262 } 263 } catch (Exception e) { 264 LOG.warn(e.getLocalizedMessage(), e); 265 m_resource = Optional.empty(); 266 } 267 } 268 if (m_resource.isPresent()) { 269 return CmsJspResourceWrapper.wrap(m_cms, m_resource.get()); 270 } else { 271 return null; 272 } 273 274 } 275 276 /** 277 * Performs server link substitution. 278 * 279 * @return the server link 280 */ 281 public String getServerLink() { 282 283 return m_stringCache.computeIfAbsent( 284 "server", 285 k -> (!isEmpty() ? OpenCms.getLinkManager().getServerLink(m_cms, m_link) : "")); 286 } 287 288 /** 289 * Returns the wrapped link as a String as in {@link #toString()}.<p> 290 * 291 * @return the wrapped link as a String 292 */ 293 public String getToString() { 294 295 return toString(); 296 } 297 298 /** 299 * Converts the wrapped string to an URI object and returns it. 300 * 301 * <p>If the wrapped string cannont be converted, returns null. 302 * 303 * @return the URI object for the wrapped string, or null if conversion fails 304 */ 305 public URI getToURI() { 306 307 return toURI(); 308 } 309 310 /** 311 * @see org.opencms.jsp.util.A_CmsJspValueWrapper#hashCode() 312 */ 313 @Override 314 public int hashCode() { 315 316 if (m_link == null) { 317 return 0; 318 } 319 return toString().hashCode(); 320 } 321 322 /** 323 * Returns <code>true</code> if the wrapped link has been initialized.<p> 324 * 325 * @return <code>true</code> if the wrapped link has been initialized 326 */ 327 @Override 328 public boolean isEmpty() { 329 330 if (m_allowEmpty) { 331 return m_link == null; 332 } 333 return CmsStringUtil.isEmptyOrWhitespaceOnly(m_link); 334 } 335 336 /** 337 * @see java.util.AbstractCollection#iterator() 338 */ 339 @Override 340 public Iterator<String> iterator() { 341 342 return isEmpty() ? Collections.emptyIterator() : Collections.singletonList(toString()).iterator(); 343 } 344 345 /** 346 * @see java.util.AbstractCollection#size() 347 */ 348 @Override 349 public int size() { 350 351 return isEmpty() ? 0 : 1; 352 } 353 354 /** 355 * Returns the wrapped link as a String as in {@link #getLink()}.<p> 356 * 357 * @return the wrapped link as a String 358 * 359 * @see #getLiteral() 360 */ 361 @Override 362 public String toString() { 363 364 return getLink(); 365 } 366 367 /** 368 * Converts the wrapped string to an URI object and returns it. 369 * 370 * <p>If the wrapped string cannont be converted, returns null. 371 * 372 * @return the URI object for the wrapped string, or null if conversion fails 373 */ 374 public URI toURI() { 375 376 if (m_link == null) { 377 return null; 378 } 379 try { 380 return new URI(m_link); 381 } catch (URISyntaxException e) { 382 return null; 383 } 384 } 385}