/* * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * Licensed 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.wso2.carbon.transport.http.netty.common; import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.DefaultHttpResponse; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMessage; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpVersion; import org.wso2.carbon.messaging.CarbonMessage; import org.wso2.carbon.messaging.Header; import org.wso2.carbon.messaging.Headers; import org.wso2.carbon.messaging.MessageDataSource; import org.wso2.carbon.transport.http.netty.common.ssl.SSLConfig; import org.wso2.carbon.transport.http.netty.config.Parameter; import org.wso2.carbon.transport.http.netty.listener.RequestDataHolder; import java.io.File; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; /** * Includes utility methods for creating http requests and responses and their related properties. */ public class Util { private static final String DEFAULT_HTTP_METHOD_POST = "POST"; private static final String DEFAULT_VERSION_HTTP_1_1 = "HTTP/1.1"; public static Headers getHeaders(HttpMessage message) { List<Header> headers = new LinkedList<>(); if (message.headers() != null) { for (Map.Entry<String, String> k : message.headers().entries()) { headers.add(new Header(k.getKey(), k.getValue())); } } return new Headers(headers); } public static void setHeaders(HttpMessage message, Headers headers) { HttpHeaders httpHeaders = message.headers(); for (Header header : headers.getAll()) { httpHeaders.add(header.getName(), header.getValue()); } } public static String getStringValue(CarbonMessage msg, String key, String defaultValue) { String value = (String) msg.getProperty(key); if (value == null) { return defaultValue; } return value; } public static int getIntValue(CarbonMessage msg, String key, int defaultValue) { Integer value = (Integer) msg.getProperty(key); if (value == null) { return defaultValue; } return value; } public static HttpResponse createHttpResponse(CarbonMessage msg) { return createHttpResponse(msg, false); } @SuppressWarnings("unchecked") public static HttpResponse createHttpResponse(CarbonMessage msg, boolean connectionCloseAfterResponse) { HttpVersion httpVersion = new HttpVersion(Util.getStringValue(msg, Constants.HTTP_VERSION, HTTP_1_1.text()), true); int statusCode = Util.getIntValue(msg, Constants.HTTP_STATUS_CODE, 200); String reasonPhrase = Util.getStringValue(msg, Constants.HTTP_REASON_PHRASE, HttpResponseStatus.valueOf(statusCode).reasonPhrase()); HttpResponseStatus httpResponseStatus = new HttpResponseStatus(statusCode, reasonPhrase); DefaultHttpResponse outgoingResponse = new DefaultHttpResponse(httpVersion, httpResponseStatus, false); Headers headers = msg.getHeaders(); if (connectionCloseAfterResponse) { headers.set(Constants.HTTP_CONNECTION, Constants.CONNECTION_CLOSE); } Util.setHeaders(outgoingResponse, headers); return outgoingResponse; } @SuppressWarnings("unchecked") public static HttpRequest createHttpRequest(CarbonMessage msg) { HttpMethod httpMethod; if (null != msg.getProperty(Constants.HTTP_METHOD)) { httpMethod = new HttpMethod((String) msg.getProperty(Constants.HTTP_METHOD)); } else { httpMethod = new HttpMethod(DEFAULT_HTTP_METHOD_POST); } HttpVersion httpVersion; if (null != msg.getProperty(Constants.HTTP_VERSION)) { httpVersion = new HttpVersion((String) msg.getProperty(Constants.HTTP_VERSION), true); } else { httpVersion = new HttpVersion(DEFAULT_VERSION_HTTP_1_1, true); } if ((String) msg.getProperty(Constants.TO) == null) { msg.setProperty(Constants.TO, "/"); } HttpRequest outgoingRequest = new DefaultHttpRequest(httpVersion, httpMethod, (String) msg.getProperty(Constants.TO), false); Headers headers = msg.getHeaders(); Util.setHeaders(outgoingRequest, headers); return outgoingRequest; } /** * Prepare request message with Transfer-Encoding/Content-Length * * @param cMsg CarbonMessage */ public static void setupTransferEncodingForRequest(CarbonMessage cMsg) { if (cMsg.getHeader(Constants.HTTP_TRANSFER_ENCODING) != null) { cMsg.removeHeader(Constants.HTTP_CONTENT_LENGTH); } else if (cMsg.isAlreadyRead() || (cMsg.getHeader(Constants.HTTP_CONTENT_LENGTH) == null && !cMsg.isEmpty())) { int contentLength = cMsg.getFullMessageLength(); if (contentLength > 0) { cMsg.setHeader(Constants.HTTP_CONTENT_LENGTH, String.valueOf(contentLength)); } } } /** * Prepare response message with Transfer-Encoding/Content-Length * * @param cMsg CarbonMessage */ public static void setupTransferEncodingForResponse(CarbonMessage cMsg, RequestDataHolder requestDataHolder) { // 1. Remove Transfer-Encoding and Content-Length as per rfc7230#section-3.3.1 int statusCode = Util.getIntValue(cMsg, Constants.HTTP_STATUS_CODE, 200); String httpMethod = requestDataHolder.getHttpMethod(); if (statusCode == 204 || statusCode >= 100 && statusCode < 200 || (HttpMethod.CONNECT.name().equals(httpMethod) && statusCode >= 200 && statusCode < 300)) { cMsg.removeHeader(Constants.HTTP_TRANSFER_ENCODING); cMsg.removeHeader(Constants.HTTP_CONTENT_LENGTH); return; } // 2. Check for transfer encoding header is set in the request // As per RFC 2616, Section 4.4, Content-Length must be ignored if Transfer-Encoding header // is present and its value not equal to 'identity' String requestTransferEncodingHeader = requestDataHolder.getTransferEncodingHeader(); if (requestTransferEncodingHeader != null && !Constants.HTTP_TRANSFER_ENCODING_IDENTITY.equalsIgnoreCase(requestTransferEncodingHeader)) { cMsg.setHeader(Constants.HTTP_TRANSFER_ENCODING, requestTransferEncodingHeader); cMsg.removeHeader(Constants.HTTP_CONTENT_LENGTH); return; } // 3. Check for request Content-Length header String requestContentLength = requestDataHolder.getContentLengthHeader(); if (requestContentLength != null && (cMsg.isAlreadyRead() || (cMsg.getHeader(Constants.HTTP_CONTENT_LENGTH) == null && !cMsg.isEmpty()))) { int contentLength = cMsg.getFullMessageLength(); if (contentLength > 0) { cMsg.setHeader(Constants.HTTP_CONTENT_LENGTH, String.valueOf(contentLength)); } cMsg.removeHeader(Constants.HTTP_TRANSFER_ENCODING); return; } // 4. If request doesn't have Transfer-Encoding or Content-Length header look for response properties if (cMsg.getHeader(Constants.HTTP_TRANSFER_ENCODING) != null) { cMsg.getHeaders().remove(Constants.HTTP_CONTENT_LENGTH); // remove Content-Length if present } else if (cMsg.isAlreadyRead() || (cMsg.getHeader(Constants.HTTP_CONTENT_LENGTH) == null && !cMsg.isEmpty())) { int contentLength = cMsg.getFullMessageLength(); if (contentLength > 0) { cMsg.setHeader(Constants.HTTP_CONTENT_LENGTH, String.valueOf(contentLength)); } } } /** * Prepare built message to transfer through the wire. * This will populate the message content from the DataSource into the output stream of the carbon message * * @param cMsg Carbon Message */ public static void prepareBuiltMessageForTransfer(CarbonMessage cMsg) { if (cMsg.isAlreadyRead()) { MessageDataSource messageDataSource = cMsg.getMessageDataSource(); if (messageDataSource != null) { messageDataSource.serializeData(); cMsg.setEndOfMsgAdded(true); } } } public static SSLConfig getSSLConfigForListener(String certPass, String keyStorePass, String keyStoreFilePath, String trustStoreFilePath, String trustStorePass, List<Parameter> parametersList) { if (certPass == null) { certPass = keyStorePass; } if (keyStoreFilePath == null || keyStorePass == null) { throw new IllegalArgumentException("keyStoreFile or keyStorePass not defined for HTTPS scheme"); } File keyStore = new File(substituteVariables(keyStoreFilePath)); if (!keyStore.exists()) { throw new IllegalArgumentException("KeyStore File " + keyStoreFilePath + " not found"); } SSLConfig sslConfig = new SSLConfig(keyStore, keyStorePass).setCertPass(certPass); for (Parameter parameter : parametersList) { if (parameter.getName() .equals(Constants.SERVER_SUPPORT_CIPHERS)) { sslConfig.setCipherSuites(parameter.getValue()); } else if (parameter.getName() .equals(Constants.SERVER_SUPPORT_HTTPS_PROTOCOLS)) { sslConfig.setEnableProtocols(parameter.getValue()); } else if (parameter.getName() .equals(Constants.SERVER_SUPPORTED_SNIMATCHERS)) { sslConfig.setSniMatchers(parameter.getValue()); } else if (parameter.getName() .equals(Constants.SERVER_SUPPORTED_SERVER_NAMES)) { sslConfig.setServerNames(parameter.getValue()); } else if (parameter.getName() .equals(Constants.SERVER_ENABLE_SESSION_CREATION)) { sslConfig.setEnableSessionCreation(Boolean.parseBoolean(parameter.getValue())); } else if (parameter.getName() .equals(Constants.SSL_VERIFY_CLIENT)) { sslConfig.setNeedClientAuth(Boolean.parseBoolean(parameter.getValue())); } } if (trustStoreFilePath != null) { File trustStore = new File(substituteVariables(trustStoreFilePath)); if (!trustStore.exists()) { throw new IllegalArgumentException("trustStore File " + trustStoreFilePath + " not found"); } if (trustStorePass == null) { throw new IllegalArgumentException("trustStorePass is not defined for HTTPS scheme"); } sslConfig.setTrustStore(trustStore).setTrustStorePass(trustStorePass); } return sslConfig; } public static SSLConfig getSSLConfigForSender(String certPass, String keyStorePass, String keyStoreFilePath, String trustStoreFilePath, String trustStorePass, List<Parameter> parametersList) { if (certPass == null) { certPass = keyStorePass; } if (trustStoreFilePath == null || trustStorePass == null) { throw new IllegalArgumentException("TrusStoreFile or trustStorePass not defined for HTTPS scheme"); } SSLConfig sslConfig = new SSLConfig(null, null).setCertPass(null); if (keyStoreFilePath != null) { File keyStore = new File(substituteVariables(keyStoreFilePath)); if (!keyStore.exists()) { throw new IllegalArgumentException("KeyStore File " + trustStoreFilePath + " not found"); } sslConfig = new SSLConfig(keyStore, keyStorePass).setCertPass(certPass); } File trustStore = new File(substituteVariables(trustStoreFilePath)); sslConfig.setTrustStore(trustStore).setTrustStorePass(trustStorePass); sslConfig.setClientMode(true); if (parametersList != null) { for (Parameter parameter : parametersList) { String paramName = parameter.getName(); if (Constants.CLIENT_SUPPORT_CIPHERS.equals(paramName)) { sslConfig.setCipherSuites(parameter.getValue()); } else if (Constants.CLIENT_SUPPORT_HTTPS_PROTOCOLS.equals(paramName)) { sslConfig.setEnableProtocols(parameter.getValue()); } else if (Constants.CLIENT_ENABLE_SESSION_CREATION.equals(paramName)) { sslConfig.setEnableSessionCreation(Boolean.parseBoolean(parameter.getValue())); } } } return sslConfig; } /** * Get integer type property value from a property map. * <p> * If {@code properties} is null or property value is null, default value is returned * * @param properties map of properties * @param key property name * @param defaultVal default value of the property * @return integer value of the property, */ public static int getIntProperty(Map<String, Object> properties, String key, int defaultVal) { if (properties == null) { return defaultVal; } Object propertyVal = properties.get(key); if (propertyVal == null) { return defaultVal; } if (!(propertyVal instanceof Integer)) { throw new IllegalArgumentException("Property : " + key + " must be an integer"); } return (Integer) propertyVal; } /** * Get string type property value from a property map. * <p> * If {@code properties} is null or property value is null, default value is returned * * @param properties map of properties * @param key property name * @param defaultVal default value of the property * @return integer value of the property, */ public static String getStringProperty( Map<String, Object> properties, String key, String defaultVal) { if (properties == null) { return defaultVal; } Object propertyVal = properties.get(key); if (propertyVal == null) { return defaultVal; } if (!(propertyVal instanceof String)) { throw new IllegalArgumentException("Property : " + key + " must be a string"); } return (String) propertyVal; } /** * Get boolean type property value from a property map. * <p> * If {@code properties} is null or property value is null, default value is returned * * @param properties map of properties * @param key property name * @param defaultVal default value of the property * @return integer value of the property, */ public static Boolean getBooleanProperty( Map<String, Object> properties, String key, boolean defaultVal) { if (properties == null) { return defaultVal; } Object propertyVal = properties.get(key); if (propertyVal == null) { return defaultVal; } if (!(propertyVal instanceof Boolean)) { throw new IllegalArgumentException("Property : " + key + " must be a boolean"); } return (Boolean) propertyVal; } /** * Get long type property value from a property map. * <p> * If {@code properties} is null or property value is null, default value is returned * * @param properties map of properties * @param key property name * @param defaultVal default value of the property * @return integer value of the property, */ public static Long getLongProperty( Map<String, Object> properties, String key, long defaultVal) { if (properties == null) { return defaultVal; } Object propertyVal = properties.get(key); if (propertyVal == null) { return defaultVal; } if (!(propertyVal instanceof Long)) { throw new IllegalArgumentException("Property : " + key + " must be a long"); } return (Long) propertyVal; } //TODO Below code segment is directly copied from kernel. Once kernel Utils been moved as a separate dependency //Need to remove below part and use that. private static final Pattern varPattern = Pattern.compile("\\$\\{([^}]*)}"); /** * Replace system property holders in the property values. * e.g. Replace ${carbon.home} with value of the carbon.home system property. * * @param value string value to substitute * @return String substituted string */ public static String substituteVariables(String value) { Matcher matcher = varPattern.matcher(value); boolean found = matcher.find(); if (!found) { return value; } StringBuffer sb = new StringBuffer(); do { String sysPropKey = matcher.group(1); String sysPropValue = getSystemVariableValue(sysPropKey, null); if (sysPropValue == null || sysPropValue.length() == 0) { throw new RuntimeException("System property " + sysPropKey + " is not specified"); } // Due to reported bug under CARBON-14746 sysPropValue = sysPropValue.replace("\\", "\\\\"); matcher.appendReplacement(sb, sysPropValue); } while (matcher.find()); matcher.appendTail(sb); return sb.toString(); } /** * A utility which allows reading variables from the environment or System properties. * If the variable in available in the environment as well as a System property, the System property takes * precedence. * * @param variableName System/environment variable name * @param defaultValue default value to be returned if the specified system variable is not specified. * @return value of the system/environment variable */ public static String getSystemVariableValue(String variableName, String defaultValue) { String value; if (System.getProperty(variableName) != null) { value = System.getProperty(variableName); } else if (System.getenv(variableName) != null) { value = System.getenv(variableName); } else { value = defaultValue; } return value; } }