/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.felix.http.jetty.internal; import java.io.IOException; import java.net.ServerSocket; import java.security.KeyStore; import java.util.ArrayList; import java.util.Collection; import java.util.Dictionary; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import org.apache.felix.http.base.internal.logger.SystemLogger; import org.osgi.framework.BundleContext; public final class JettyConfig { /** Felix specific property to set the interface to listen on. Applies to both HTTP and HTTP */ public static final String FELIX_HOST = "org.apache.felix.http.host"; /** Standard OSGi port property for HTTP service */ public static final String HTTP_PORT = "org.osgi.service.http.port"; /** Standard OSGi port property for HTTPS service */ public static final String HTTPS_PORT = "org.osgi.service.http.port.secure"; /** Felix specific property to set http reaching timeout limit */ public static final String HTTP_TIMEOUT = "org.apache.felix.http.timeout"; /** Felix specific property to override the keystore file location. */ public static final String FELIX_KEYSTORE = "org.apache.felix.https.keystore"; private static final String OSCAR_KEYSTORE = "org.ungoverned.osgi.bundle.https.keystore"; /** Felix specific property to override the keystore password. */ public static final String FELIX_KEYSTORE_PASSWORD = "org.apache.felix.https.keystore.password"; private static final String OSCAR_KEYSTORE_PASSWORD = "org.ungoverned.osgi.bundle.https.password"; /** Felix specific property to override the keystore key password. */ public static final String FELIX_KEYSTORE_KEY_PASSWORD = "org.apache.felix.https.keystore.key.password"; private static final String OSCAR_KEYSTORE_KEY_PASSWORD = "org.ungoverned.osgi.bundle.https.key.password"; /** Felix specific property to override the type of keystore (JKS). */ public static final String FELIX_KEYSTORE_TYPE = "org.apache.felix.https.keystore.type"; /** Felix specific property to control whether to enable HTTPS. */ public static final String FELIX_HTTPS_ENABLE = "org.apache.felix.https.enable"; private static final String OSCAR_HTTPS_ENABLE = "org.ungoverned.osgi.bundle.https.enable"; /** Felix specific property to control whether to enable HTTP. */ public static final String FELIX_HTTP_ENABLE = "org.apache.felix.http.enable"; /** Felix specific property to override the truststore file location. */ public static final String FELIX_TRUSTSTORE = "org.apache.felix.https.truststore"; /** Felix specific property to override the truststore password. */ public static final String FELIX_TRUSTSTORE_PASSWORD = "org.apache.felix.https.truststore.password"; /** Felix specific property to override the type of truststore (JKS). */ public static final String FELIX_TRUSTSTORE_TYPE = "org.apache.felix.https.truststore.type"; /** Felix specific property to control whether to want or require HTTPS client certificates. Valid values are "none", "wants", "needs". Default is "none". */ public static final String FELIX_HTTPS_CLIENT_CERT = "org.apache.felix.https.clientcertificate"; /** Felix specific property to configure the session timeout in minutes (same session-timout in web.xml). Default is servlet container specific */ public static final String FELIX_SESSION_TIMEOUT = "org.apache.felix.http.session.timeout"; /** Felix specific property to control the maximum size of the jetty thread pool */ public static final String FELIX_JETTY_THREADPOOL_MAX = "org.apache.felix.http.jetty.threadpool.max"; /** Felix specific property to control the number of jetty acceptor threads */ public static final String FELIX_JETTY_ACCEPTORS = "org.apache.felix.http.jetty.acceptors"; /** Felix specific property to control the number of jetty selector threads */ public static final String FELIX_JETTY_SELECTORS = "org.apache.felix.http.jetty.selectors"; /** Felix specific property to configure the request buffer size. Default is 16KB (instead of Jetty's default of 4KB) */ public static final String FELIX_JETTY_HEADER_BUFFER_SIZE = "org.apache.felix.http.jetty.headerBufferSize"; /** Felix specific property to configure the request buffer size. Default is 8KB */ public static final String FELIX_JETTY_REQUEST_BUFFER_SIZE = "org.apache.felix.http.jetty.requestBufferSize"; /** Felix specific property to configure the request buffer size. Default is 24KB */ public static final String FELIX_JETTY_RESPONSE_BUFFER_SIZE = "org.apache.felix.http.jetty.responseBufferSize"; /** Felix specific property to configure the max form size. Default is 200KB */ public static final String FELIX_JETTY_MAX_FORM_SIZE = "org.apache.felix.http.jetty.maxFormSize"; /** Felix specific property to enable Jetty MBeans. Valid values are "true", "false". Default is false */ public static final String FELIX_HTTP_MBEANS = "org.apache.felix.http.mbeans"; /** Felix specific property to set the servlet context path of the Http Service */ public static final String FELIX_HTTP_CONTEXT_PATH = "org.apache.felix.http.context_path"; /** Felix specific property to set the list of path exclusions for Web Application Bundles */ public static final String FELIX_HTTP_PATH_EXCLUSIONS = "org.apache.felix.http.path_exclusions"; /** Felix specific property to configure the excluded cipher suites. @deprecated use {@link #FELIX_JETTY_EXCLUDED_SUITES} instead. */ @Deprecated public static final String FELIX_JETTY_EXCLUDED_SUITES_OLD = "org.apache.felix.https.jetty.cipersuites.excluded"; /** Felix specific property to configure the excluded cipher suites */ public static final String FELIX_JETTY_EXCLUDED_SUITES = "org.apache.felix.https.jetty.ciphersuites.excluded"; /** Felix specific property to configure the included cipher suites. @deprecated use {@link #FELIX_JETTY_INCLUDED_SUITES} instead. */ @Deprecated public static final String FELIX_JETTY_INCLUDED_SUITES_OLD = "org.apache.felix.https.jetty.cipersuites.included"; /** Felix specific property to configure the included cipher suites. */ public static final String FELIX_JETTY_INCLUDED_SUITES = "org.apache.felix.https.jetty.ciphersuites.included"; /** Felix specific property to specify whether a server header should be sent (defaults to true) */ public static final String FELIX_JETTY_SEND_SERVER_HEADER = "org.apache.felix.http.jetty.sendServerHeader"; /** Felix specific property to configure the included protocols */ public static final String FELIX_JETTY_INCLUDED_PROTOCOLS = "org.apache.felix.https.jetty.protocols.included"; /** Felix specific property to configure the excluded protocols */ public static final String FELIX_JETTY_EXCLUDED_PROTOCOLS = "org.apache.felix.https.jetty.protocols.excluded"; /** Felix specific properties to be able to disable renegotiation protocol for TLSv1 */ public static final String FELIX_JETTY_RENEGOTIATION_ALLOWED = "org.apache.felix.https.jetty.renegotiateAllowed"; /** Felix specific property to control whether to enable Proxy/Load Balancer Connection */ public static final String FELIX_PROXY_LOAD_BALANCER_CONNECTION_ENABLE = "org.apache.felix.proxy.load.balancer.connection.enable"; /** Felix specific property to configure the session cookie httpOnly flag */ public static final String FELIX_JETTY_SESSION_COOKIE_HTTP_ONLY = "org.apache.felix.https.jetty.session.cookie.httpOnly"; /** Felix specific property to configure the session cookie secure flag */ public static final String FELIX_JETTY_SESSION_COOKIE_SECURE = "org.apache.felix.https.jetty.session.cookie.secure"; /** Felix specific property to configure session id path parameter*/ public static final String FELIX_JETTY_SERVLET_SESSION_ID_PATH_PARAMETER_NAME = "org.eclipse.jetty.servlet.SessionIdPathParameterName"; /** Felix specific property to configure whether JSESSIONID parameter will be added when encoding external URLs */ public static final String FELIX_JETTY_SERVLET_CHECK_REMOTE_SESSION_ENCODING = "org.eclipse.jetty.servlet.CheckingRemoteSessionIdEncoding"; /** Felix specific property to configure session cookie name */ public static final String FELIX_JETTY_SERVLET_SESSION_COOKIE_NAME = "org.eclipse.jetty.servlet.SessionCookie"; /** Felix specific property to configure session domain */ public static final String FELIX_JETTY_SERVLET_SESSION_DOMAIN = "org.eclipse.jetty.servlet.SessionDomain"; /** Felix specific property to configure session path */ public static final String FELIX_JETTY_SERVLET_SESSION_PATH = "org.eclipse.jetty.servlet.SessionPath"; /** Felix specific property to configure session max age */ public static final String FELIX_JETTY_SERVLET_SESSION_MAX_AGE = "org.eclipse.jetty.servlet.MaxAge"; /** Felix specific property to set HTTP instance name. */ public static final String FELIX_HTTP_SERVICE_NAME = "org.apache.felix.http.name"; private static String validateContextPath(String ctxPath) { // undefined, empty, or root context path if (ctxPath == null || ctxPath.length() == 0 || "/".equals(ctxPath)) { return "/"; } // ensure leading but no trailing slash if (!ctxPath.startsWith("/")) { ctxPath = "/".concat(ctxPath); } while (ctxPath.endsWith("/")) { ctxPath = ctxPath.substring(0, ctxPath.length() - 1); } return ctxPath; } private final BundleContext context; /** * Properties from the configuration not matching any of the * predefined properties. These properties can be accessed from the * getProperty* methods. * <p> * This map is indexed by String objects (the property names) and * the values are just objects as provided by the configuration. */ private volatile Dictionary<String, ?> config; public JettyConfig(final BundleContext context) { this.context = context; reset(); } /** * Returns the named generic configuration property from the * configuration or the bundle context. If neither property is defined * return the defValue. */ public boolean getBooleanProperty(String name, boolean defValue) { String value = getProperty(name, null); if (value != null) { return "true".equalsIgnoreCase(value) || "yes".equalsIgnoreCase(value); } return defValue; } public String getClientcert() { return getProperty(FELIX_HTTPS_CLIENT_CERT, "none"); } public String getContextPath() { return validateContextPath(getProperty(FELIX_HTTP_CONTEXT_PATH, null)); } public String[] getExcludedCipherSuites() { return getStringArrayProperty(FELIX_JETTY_EXCLUDED_SUITES, getStringArrayProperty(FELIX_JETTY_EXCLUDED_SUITES_OLD, null)); } public String[] getIncludedProtocols() { return getStringArrayProperty(FELIX_JETTY_INCLUDED_PROTOCOLS, null); } public String[] getExcludedProtocols() { return getStringArrayProperty(FELIX_JETTY_EXCLUDED_PROTOCOLS, null); } public int getHeaderSize() { return getIntProperty(FELIX_JETTY_HEADER_BUFFER_SIZE, 16 * 1024); } public String getHost() { return getProperty(FELIX_HOST, null); } public int getHttpPort() { return determinePort(String.valueOf(getProperty(HTTP_PORT)), 8080); } public int getHttpsPort() { return determinePort(String.valueOf(getProperty(HTTPS_PORT)), 8443); } public int getHttpTimeout() { return getIntProperty(HTTP_TIMEOUT, 60000); } public String[] getIncludedCipherSuites() { return getStringArrayProperty(FELIX_JETTY_INCLUDED_SUITES, getStringArrayProperty(FELIX_JETTY_INCLUDED_SUITES_OLD, null)); } /** * Returns the named generic configuration property from the * configuration or the bundle context. If neither property is defined * return the defValue. */ public int getIntProperty(String name, int defValue) { return parseInt(getProperty(name, null), defValue); } public String getKeyPassword() { return getProperty(FELIX_KEYSTORE_KEY_PASSWORD, this.context.getProperty(OSCAR_KEYSTORE_KEY_PASSWORD)); } public String getKeystore() { return getProperty(FELIX_KEYSTORE, this.context.getProperty(OSCAR_KEYSTORE)); } public String getKeystoreType() { return getProperty(FELIX_KEYSTORE_TYPE, KeyStore.getDefaultType()); } public String getPassword() { return getProperty(FELIX_KEYSTORE_PASSWORD, this.context.getProperty(OSCAR_KEYSTORE_PASSWORD)); } public String[] getPathExclusions() { return getStringArrayProperty(FELIX_HTTP_PATH_EXCLUSIONS, new String[] { "/system" }); } /** * Returns the named generic configuration property from the * configuration or the bundle context. If neither property is defined * return the defValue. */ public String getProperty(String name, String defValue) { Object value = getProperty(name); return value != null ? String.valueOf(value) : defValue; } public int getThreadPoolMax() { return getIntProperty(FELIX_JETTY_THREADPOOL_MAX, -1); } public int getAcceptors() { return getIntProperty(FELIX_JETTY_ACCEPTORS, -1); } public int getSelectors() { return getIntProperty(FELIX_JETTY_SELECTORS, -1); } public int getRequestBufferSize() { return getIntProperty(FELIX_JETTY_REQUEST_BUFFER_SIZE, 8 * 1024); } public int getResponseBufferSize() { return getIntProperty(FELIX_JETTY_RESPONSE_BUFFER_SIZE, 24 * 1024); } public int getMaxFormSize() { return getIntProperty(FELIX_JETTY_MAX_FORM_SIZE, 200 * 1024); } /** * Returns the configured session timeout in minutes or zero if not * configured. */ public int getSessionTimeout() { return getIntProperty(FELIX_SESSION_TIMEOUT, 0); } public String getTrustPassword() { return getProperty(FELIX_TRUSTSTORE_PASSWORD, null); } public String getTruststore() { String value = getProperty(FELIX_TRUSTSTORE, null); return value == null || value.trim().length() == 0 ? null : value; } public String getTruststoreType() { return getProperty(FELIX_TRUSTSTORE_TYPE, KeyStore.getDefaultType()); } public boolean isRegisterMBeans() { return getBooleanProperty(FELIX_HTTP_MBEANS, false); } /** * Returns <code>true</code> if HTTP is configured to be used ( * {@link #FELIX_HTTP_ENABLE}) and * the configured HTTP port ({@link #HTTP_PORT}) is higher than zero. */ public boolean isUseHttp() { boolean useHttp = getBooleanProperty(FELIX_HTTP_ENABLE, true); return useHttp && getHttpPort() > 0; } public boolean isSendServerHeader() { return getBooleanProperty(FELIX_JETTY_SEND_SERVER_HEADER, false); } /** * Returns <code>true</code> if HTTPS is configured to be used ( * {@link #FELIX_HTTPS_ENABLE}) and * the configured HTTP port ({@link #HTTPS_PORT}) is higher than zero. */ public boolean isUseHttps() { boolean useHttps = getBooleanProperty(FELIX_HTTPS_ENABLE, getBooleanProperty(OSCAR_HTTPS_ENABLE, false)); return useHttps && getHttpsPort() > 0; } public boolean isProxyLoadBalancerConnection() { return getBooleanProperty(FELIX_PROXY_LOAD_BALANCER_CONNECTION_ENABLE, false); } public boolean isRenegotiationAllowed() { return getBooleanProperty(FELIX_JETTY_RENEGOTIATION_ALLOWED, false); } public String getHttpServiceName() { return (String) getProperty(FELIX_HTTP_SERVICE_NAME); } public void reset() { update(null); } public void setServiceProperties(Hashtable<String, Object> props) { props.put(HTTP_PORT, Integer.toString(getHttpPort())); props.put(HTTPS_PORT, Integer.toString(getHttpsPort())); props.put(FELIX_HTTP_ENABLE, Boolean.toString(isUseHttp())); props.put(FELIX_HTTPS_ENABLE, Boolean.toString(isUseHttps())); if (getHttpServiceName() != null) { props.put(FELIX_HTTP_SERVICE_NAME, getHttpServiceName()); } } /** * Updates this configuration with the given dictionary. * * @param props the dictionary with the new configuration values, can be <code>null</code> to reset this configuration to its defaults. * @return <code>true</code> if the configuration was updated due to a changed value, or <code>false</code> if no change was found. */ public boolean update(Dictionary<String, ?> props) { if (props == null) { props = new Hashtable<>(); } // FELIX-4312 Check whether there's something changed in our configuration... Dictionary<String, ?> currentConfig = this.config; if (currentConfig == null || !props.equals(currentConfig)) { this.config = props; return true; } return false; } private void closeSilently(ServerSocket resource) { if (resource != null) { try { resource.close(); } catch (IOException e) { // Ignore... } } } /** * Determine the appropriate port to use. <code>portProp</code> is based * "version range" as described in OSGi Core Spec v4.2 3.2.6. It can use the * following forms: * <dl> * <dd>8000 | 8000</dd> * <dd>[8000,9000] | 8000 <= port <= 9000</dd> * <dd>[8000,9000) | 8000 <= port < 9000</dd> * <dd>(8000,9000] | 8000 < port <= 9000</dd> * <dd>(8000,9000) | 8000 < port < 9000</dd> * <dd>[,9000) | 1 < port < 9000</dd> * <dd>[8000,) | 8000 <= port < 65534</dd> * </dl> * * @param portProp * The port property value to parse. * @return The port determined to be usable. -1 if failed to find a port. */ private int determinePort(String portProp, int dflt) { // Default cases include null/empty range pattern or pattern == *. if (portProp == null || "".equals(portProp.trim())) { return dflt; } // asking for random port, so let ServerSocket handle it and return the answer portProp = portProp.trim(); if ("*".equals(portProp) || "0".equals(portProp)) { return getSocketPort(0); } else { // check that the port property is a version range as described in // OSGi Core Spec v4.2 3.2.6. // deviations from the spec are limited to: // * start, end of interval defaults to 1, 65535, respectively, if missing. char startsWith = portProp.charAt(0); char endsWith = portProp.charAt(portProp.length() - 1); int minPort = 1; int maxPort = 65535; if (portProp.contains(",") && (startsWith == '[' || startsWith == '(') && (endsWith == ']' || endsWith == ')')) { String interval = portProp.substring(1, portProp.length() - 1); int comma = interval.indexOf(','); // check if the comma is first (start port in range is missing) int start = (comma == 0) ? minPort : parseInt(interval.substring(0, comma), minPort); // check if the comma is last (end port in range is missing) int end = (comma == interval.length() - 1) ? maxPort : parseInt(interval.substring(comma + 1), maxPort); // check for exclusive notation if (startsWith == '(') { start++; } if (endsWith == ')') { end--; } // find a port in the requested range int port = start - 1; for (int i = start; port < start && i <= end; i++) { port = getSocketPort(i); } return (port < start) ? dflt : port; } else { // We don't recognize the pattern as special, so try to parse it to an int return parseInt(portProp, dflt); } } } private int getSocketPort(int i) { int port = -1; ServerSocket ss = null; try { ss = new ServerSocket(i); port = ss.getLocalPort(); } catch (IOException e) { SystemLogger.debug("Unable to bind to port: " + i); } finally { closeSilently(ss); } return port; } private Object getProperty(final String name) { Dictionary<String, ?> conf = this.config; Object value = (conf != null) ? conf.get(name) : null; if (value == null) { value = this.context.getProperty(name); } return value; } /** * Get the property value as a string array. * Empty values are filtered out - if the resulting array is empty * the default value is returned. */ private String[] getStringArrayProperty(String name, String[] defValue) { Object value = getProperty(name); if (value instanceof String) { final String stringVal = ((String) value).trim(); if (stringVal.length() > 0) { return stringVal.split(","); } } else if (value instanceof String[]) { final String[] stringArr = (String[]) value; final List<String> list = new ArrayList<String>(); for (final String stringVal : stringArr) { if (stringVal.trim().length() > 0) { list.add(stringVal.trim()); } } if (list.size() > 0) { return list.toArray(new String[list.size()]); } } else if (value instanceof Collection) { final ArrayList<String> conv = new ArrayList<String>(); for (Iterator<?> vi = ((Collection<?>) value).iterator(); vi.hasNext();) { Object object = vi.next(); if (object != null) { conv.add(String.valueOf(object)); } } if (conv.size() > 0) { return conv.toArray(new String[conv.size()]); } } return defValue; } private int parseInt(String value, int dflt) { try { return Integer.parseInt(value); } catch (NumberFormatException e) { return dflt; } } }