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