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 GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, 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.site; 029 030import org.opencms.main.CmsLog; 031import org.opencms.util.CmsStringUtil; 032 033import java.io.Serializable; 034import java.net.URI; 035import java.net.URISyntaxException; 036import java.util.Arrays; 037 038import org.apache.commons.logging.Log; 039 040/** 041 * A matcher object to compare request data against the configured sites.<p> 042 * 043 * @since 6.0.0 044 */ 045public final class CmsSiteMatcher implements Cloneable, Serializable { 046 047 /** 048 * Represents the different redirect modes for a site alias. 049 */ 050 public static enum RedirectMode { 051 052 /** Don't redirect. */ 053 none, 054 /** HTTP 302 */ 055 temporary, 056 /** HTTP 301 */ 057 permanent; 058 059 /** 060 * Converts a redirect mode string from the configuration to the corresponding enum value. 061 * 062 * @param strValue the string value 063 * @return the enum value 064 */ 065 public static RedirectMode parse(String strValue) { 066 067 if (strValue == null) { 068 return none; 069 } 070 strValue = strValue.toLowerCase(); 071 if ("true".equals(strValue)) { 072 return temporary; 073 } else if ("permanent".equals(strValue)) { 074 return permanent; 075 } 076 077 return none; 078 079 } 080 } 081 082 /** The serial version id. */ 083 private static final long serialVersionUID = -3988887650237005342L; 084 085 /** The logger instance for this class. */ 086 private static final Log LOG = CmsLog.getLog(CmsSiteMatcher.class); 087 088 /** Constant for the "http" port. */ 089 private static final int PORT_HTTP = 80; 090 091 /** Constant for the "https" port. */ 092 private static final int PORT_HTTPS = 443; 093 094 /** Constant for the "http" scheme. */ 095 private static final String SCHEME_HTTP = "http"; 096 097 /** Constant for the "https" scheme. */ 098 private static final String SCHEME_HTTPS = "https"; 099 100 /** Wildcard for string matching. */ 101 private static final String WILDCARD = "*"; 102 103 /** Default matcher that always matches all other Site matchers. */ 104 public static final CmsSiteMatcher DEFAULT_MATCHER = new CmsSiteMatcher(WILDCARD, WILDCARD, 0); 105 106 /** Hashcode buffer to save multiple calculations. */ 107 private transient Integer m_hashCode; 108 109 /** The hostname (e.g. localhost) which is required to access this site. */ 110 private String m_serverName; 111 112 /** The port (e.g. 80) which is required to access this site. */ 113 private int m_serverPort; 114 115 /** The protocol (e.g. "http", "https") which is required to access this site. */ 116 private String m_serverProtocol; 117 118 /** The time offset. */ 119 private long m_timeOffset; 120 121 /**Redirect (only for aliase). */ 122 private RedirectMode m_redirect = RedirectMode.none; 123 124 /** 125 * Construct a new site matcher from a String which should be in default URL notation.<p> 126 * 127 * If no port is provided, the default port 80 or 443 will be used for http or https respectively. 128 * If no protocol is provided, the default protocol "http" will be used. 129 * 130 * @param serverString the String, e.g. http://localhost:8080 131 */ 132 public CmsSiteMatcher(String serverString) { 133 134 this(serverString, 0); 135 } 136 137 /** 138 * Construct a new site matcher from a String which should be in default URL notation.<p> 139 * 140 * If no port is provided, the default port 80 or 443 will be used for http or https respectively. 141 * If no protocol is provided, the default protocol "http" will be used. 142 * 143 * @param serverString the String, e.g. http://localhost:8080 144 * @param timeOffset the time offset 145 */ 146 public CmsSiteMatcher(String serverString, long timeOffset) { 147 148 if (serverString == null) { 149 init(WILDCARD, WILDCARD, 0, timeOffset); 150 return; 151 } 152 try { 153 URI uri = new URI(serverString); 154 int serverPort; 155 String serverProtocol, serverName; 156 if (uri.getScheme() != null) { 157 serverProtocol = uri.getScheme(); 158 } else { 159 // handle missing protocol, for backward compatibility 160 serverProtocol = SCHEME_HTTP; 161 uri = new URI(serverProtocol + "://" + serverString); 162 } 163 serverName = uri.getHost(); 164 if (serverName == null) { 165 // handle weird cases like trailing dashes in a backward compatible way 166 LOG.error("Invalid server name: " + serverString); 167 String authority = uri.getAuthority(); 168 String candidate = authority.replaceFirst(":[0-9]+$", ""); 169 int atIndex = candidate.indexOf("@"); 170 if (atIndex != -1) { 171 candidate = candidate.substring(atIndex + 1); 172 } 173 serverName = candidate; 174 } 175 serverPort = uri.getPort(); 176 if (serverPort == -1) { 177 if (SCHEME_HTTPS.equals(serverProtocol)) { 178 serverPort = PORT_HTTPS; 179 } else { 180 serverPort = PORT_HTTP; 181 } 182 } 183 init(serverProtocol, serverName, serverPort, timeOffset); 184 } catch (URISyntaxException e) { 185 LOG.error("Invalid server name: " + serverString, e); 186 init(WILDCARD, WILDCARD, 0, timeOffset); 187 188 } 189 } 190 191 /** 192 * Constructs a new site matcher object.<p> 193 * 194 * @param serverProtocol to protocol required to access this site 195 * @param serverName the server URL prefix to which this site is mapped 196 * @param serverPort the port required to access this site 197 */ 198 public CmsSiteMatcher(String serverProtocol, String serverName, int serverPort) { 199 200 init(serverProtocol, serverName, serverPort, 0); 201 } 202 203 /** 204 * Constructs a new site matcher object.<p> 205 * 206 * @param serverProtocol to protocol required to access this site 207 * @param serverName the server URL prefix to which this site is mapped 208 * @param serverPort the port required to access this site 209 * @param timeOffset the time offset 210 */ 211 public CmsSiteMatcher(String serverProtocol, String serverName, int serverPort, long timeOffset) { 212 213 init(serverProtocol, serverName, serverPort, timeOffset); 214 } 215 216 /** 217 * Returns a clone of this Objects instance.<p> 218 * 219 * @return a clone of this instance 220 */ 221 @Override 222 public Object clone() { 223 224 try { 225 return super.clone(); 226 } catch (CloneNotSupportedException e) { 227 // should not happen 228 throw new RuntimeException(e); 229 } 230 } 231 232 /** 233 * @see java.lang.Object#equals(java.lang.Object) 234 */ 235 @Override 236 public boolean equals(Object obj) { 237 238 if (obj == this) { 239 return true; 240 } 241 if (!(obj instanceof CmsSiteMatcher)) { 242 return false; 243 } 244 // if one of the object is the default matcher the result is always true 245 if ((this == DEFAULT_MATCHER) || (obj == DEFAULT_MATCHER)) { 246 return true; 247 } 248 CmsSiteMatcher other = (CmsSiteMatcher)obj; 249 return (m_serverPort == other.m_serverPort) 250 && m_serverName.equalsIgnoreCase(other.m_serverName) 251 && m_serverProtocol.equals(other.m_serverProtocol); 252 } 253 254 /** 255 * Checks if this site matcher equals another site matcher, ignoring the scheme. 256 * 257 * @param matcher the matcher to compare 258 * @return true if the site matchers are equal 259 */ 260 public boolean equalsIgnoreScheme(CmsSiteMatcher matcher) { 261 262 for (String scheme : Arrays.asList("http", "https")) { 263 if (this.forDifferentScheme(scheme).equals(matcher)) { 264 return true; 265 } 266 } 267 return false; 268 } 269 270 /** 271 * Generates a site matcher equivalent to this one but with a different scheme.<p> 272 * 273 * @param scheme the new scheme 274 * @return the new site matcher 275 */ 276 public CmsSiteMatcher forDifferentScheme(String scheme) { 277 278 try { 279 URI uri = new URI(getUrl()); 280 URI changedUri = new URI(scheme, uri.getAuthority(), uri.getPath(), uri.getQuery(), uri.getFragment()); 281 CmsSiteMatcher res = new CmsSiteMatcher(changedUri.toString(), m_timeOffset); 282 res.m_redirect = m_redirect; 283 return res; 284 } catch (Exception e) { 285 LOG.error(e.getLocalizedMessage(), e); 286 return null; 287 } 288 } 289 290 /** 291 * Gets the redirect mode. 292 * 293 * @return the redirect mode 294 */ 295 public RedirectMode getRedirectMode() { 296 297 return m_redirect; 298 } 299 300 /** 301 * Returns the hostname (e.g. localhost) which is required to access this site.<p> 302 * 303 * @return the hostname (e.g. localhost) which is required to access this site 304 */ 305 public String getServerName() { 306 307 return m_serverName; 308 } 309 310 /** 311 * Returns the port (e.g. 80) which is required to access this site.<p> 312 * 313 * @return the port (e.g. 80) which is required to access this site 314 */ 315 public int getServerPort() { 316 317 return m_serverPort; 318 } 319 320 /** 321 * Returns the protocol (e.g. "http", "https") which is required to access this site.<p> 322 * 323 * @return the protocol (e.g. "http", "https") which is required to access this site 324 */ 325 public String getServerProtocol() { 326 327 return m_serverProtocol; 328 } 329 330 /** 331 * Returns the time Offset.<p> 332 * 333 * @return the time Offset 334 */ 335 public long getTimeOffset() { 336 337 return m_timeOffset; 338 } 339 340 /** 341 * Returns the url of this site matcher.<p> 342 * 343 * @return the url, i.e. {protocol}://{servername}[:{port}], port appened only if != 80 344 */ 345 public String getUrl() { 346 347 return m_serverProtocol 348 + "://" 349 + m_serverName 350 + (((m_serverPort != PORT_HTTP) && (m_serverPort != PORT_HTTPS)) ? ":" + m_serverPort : ""); 351 } 352 353 /** 354 * @see java.lang.Object#hashCode() 355 */ 356 @Override 357 public int hashCode() { 358 359 if (m_hashCode == null) { 360 m_hashCode = Integer.valueOf(toString().hashCode()); 361 } 362 return m_hashCode.intValue(); 363 } 364 365 /** 366 * Is alias to be redirected? 367 * 368 * @return boolean 369 */ 370 public boolean isRedirect() { 371 372 return m_redirect != RedirectMode.none; 373 } 374 375 /** 376 * Set redirect.<p> 377 * 378 * @param redirect boolean 379 */ 380 public void setRedirectMode(RedirectMode redirect) { 381 382 m_redirect = redirect; 383 } 384 385 /** 386 * @see java.lang.Object#toString() 387 */ 388 @Override 389 public String toString() { 390 391 StringBuffer result = new StringBuffer(32); 392 if ((m_serverProtocol != null) && !(WILDCARD.equals(m_serverProtocol))) { 393 result.append(m_serverProtocol); 394 result.append("://"); 395 } 396 result.append(m_serverName); 397 if ((m_serverPort > 0) 398 && (!(SCHEME_HTTP.equals(m_serverProtocol) && (m_serverPort == PORT_HTTP))) 399 && (!(SCHEME_HTTPS.equals(m_serverProtocol) && (m_serverPort == PORT_HTTPS)))) { 400 result.append(":"); 401 result.append(m_serverPort); 402 } 403 return result.toString(); 404 } 405 406 /** 407 * Sets the hostname (e.g. localhost) which is required to access this site.<p> 408 * 409 * Setting the hostname to "*" is a wildcard that matches all hostnames 410 * 411 * @param serverName the hostname (e.g. localhost) which is required to access this site 412 */ 413 protected void setServerName(String serverName) { 414 415 if (CmsStringUtil.isEmpty(serverName) || (WILDCARD.equals(serverName))) { 416 m_serverName = WILDCARD; 417 } else { 418 m_serverName = serverName.trim(); 419 } 420 } 421 422 /** 423 * Sets the port (e.g. 80) which is required to access this site.<p> 424 * 425 * Setting the port to 0 (zero) is a wildcard that matches all ports 426 * 427 * @param serverPort the port (e.g. 80) which is required to access this site 428 */ 429 protected void setServerPort(int serverPort) { 430 431 m_serverPort = serverPort; 432 if (m_serverPort < 0) { 433 m_serverPort = 0; 434 } 435 } 436 437 /** 438 * Sets the protocol (e.g. "http", "https") which is required to access this site.<p> 439 * 440 * Setting the protocol to "*" is a wildcard that matches all protocols.<p> 441 * 442 * @param serverProtocol the protocol (e.g. "http", "https") which is required to access this site 443 */ 444 protected void setServerProtocol(String serverProtocol) { 445 446 if (CmsStringUtil.isEmpty(serverProtocol) || (WILDCARD.equals(serverProtocol))) { 447 m_serverProtocol = WILDCARD; 448 } else { 449 int pos = serverProtocol.indexOf("/"); 450 if (pos > 0) { 451 m_serverProtocol = serverProtocol.substring(0, pos).toLowerCase(); 452 } else { 453 m_serverProtocol = serverProtocol.toLowerCase().trim(); 454 } 455 } 456 } 457 458 /** 459 * Sets the time Offset in seconds.<p> 460 * 461 * @param timeOffset the time Offset to set 462 */ 463 protected void setTimeOffset(long timeOffset) { 464 465 m_timeOffset = timeOffset * 1000L; 466 } 467 468 /** 469 * Initializes the member variables.<p> 470 * 471 * @param serverProtocol to protocol required to access this site 472 * @param serverName the server URL prefix to which this site is mapped 473 * @param serverPort the port required to access this site 474 * @param timeOffset the time offset 475 */ 476 private void init(String serverProtocol, String serverName, int serverPort, long timeOffset) { 477 478 setServerProtocol(serverProtocol); 479 setServerName(serverName); 480 setServerPort(serverPort); 481 setTimeOffset(timeOffset); 482 } 483}