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