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}