/*******************************************************************************
* Copyright (c) 2006-2010 eBay Inc. 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
*******************************************************************************/
package org.ebayopensource.turmeric.runtime.spf.impl.transport.http;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import org.ebayopensource.turmeric.runtime.binding.BindingConstants;
import org.ebayopensource.turmeric.runtime.binding.utils.BindingUtils;
import org.ebayopensource.turmeric.runtime.common.exceptions.ErrorDataFactory;
import org.ebayopensource.turmeric.runtime.common.exceptions.ServiceException;
import org.ebayopensource.turmeric.runtime.common.impl.utils.HTTPCommonUtils;
import org.ebayopensource.turmeric.runtime.common.pipeline.Transport;
import org.ebayopensource.turmeric.runtime.common.service.HeaderMappingsDesc;
import org.ebayopensource.turmeric.runtime.common.types.Cookie;
import org.ebayopensource.turmeric.runtime.common.types.G11nOptions;
import org.ebayopensource.turmeric.runtime.common.types.SOAConstants;
import org.ebayopensource.turmeric.runtime.common.types.SOAHeaders;
import org.ebayopensource.turmeric.runtime.common.types.ServiceAddress;
import org.ebayopensource.turmeric.runtime.errorlibrary.ErrorConstants;
import org.ebayopensource.turmeric.runtime.spf.impl.internal.config.OperationMapping;
import org.ebayopensource.turmeric.runtime.spf.impl.internal.config.OperationMappings;
import org.ebayopensource.turmeric.runtime.spf.impl.internal.service.RequestParamsDescriptor;
import org.ebayopensource.turmeric.runtime.spf.impl.internal.service.RequestParamsDescriptor.RequestParams;
import org.ebayopensource.turmeric.runtime.spf.impl.internal.service.ServerServiceDesc;
import org.ebayopensource.turmeric.runtime.spf.impl.internal.service.UrlMappingsDesc;
import org.ebayopensource.turmeric.runtime.spf.impl.pipeline.ServerMessageContextBuilder;
import org.ebayopensource.turmeric.runtime.spf.impl.pipeline.ServiceResolver;
import org.ebayopensource.turmeric.runtime.spf.pipeline.PseudoOperationHelper;
import org.ebayopensource.turmeric.runtime.spf.pipeline.RequestMetaContext;
import com.ebay.kernel.logger.LogLevel;
import com.ebay.kernel.logger.Logger;
import com.ebay.kernel.util.IpAddressUtils;
import com.ebay.kernel.util.StringUtils;
/**
* @author rmurphy
* @author ichernyshev
*/
public final class HTTPServerUtils {
public static final String HTTP_1_1 = "HTTP/1.1";
public static final String HTTP_1_0 = "HTTP/1.0";
public static final String HTTP_SERVLET_REQUEST = "HttpServletRequest";
// This will be changed in the future to be a configurable parameter per
// service. This parameter signifies whether security header are allowed to
// be part
// of URL in GET requests
public static final boolean nonAnonGetAllowed = true;
// Namespace token prefix
private static final String NAMESPACE_PREFIX = "nvns:";
// Security header prefix
private static final String SECURITY_HEADER_PREFIX = "X-TURMERIC-SECURITY";
private final ISOATransportRequest m_request;
private final RequestMetaContext m_reqMetaCtx;
private final String m_rawRequestUri;
private final boolean m_isGetMethod;
private final boolean m_isDelete;
private final ServiceResolver m_serviceResolver;
private final List<ParamData> m_params;
private int m_systemParamCount;
private List<Throwable> m_errors;
private final int relativeOffset; // '0' based
private final static Logger s_logger = Logger.getInstance(HTTPServerUtils.class);
public HTTPServerUtils(ISOATransportRequest request,
String requiredAdminName, String urlMatchExpression)
throws ServiceException {
m_request = request;
m_rawRequestUri = request.getRequestURI();
m_isGetMethod = request.getMethod().equalsIgnoreCase("GET");
m_isDelete = request.getMethod().equalsIgnoreCase("DELETE");
m_reqMetaCtx = new RequestMetaContext(m_isGetMethod, m_isDelete, m_rawRequestUri,
requiredAdminName);
m_params = new ArrayList<ParamData>();
// we have to always parse parameters, as we do not know
// whether parameter index will be used in URL mappings
parseParameters();
m_systemParamCount = m_params.size();
extractTransportHeaders();
extractSystemParameters();
m_reqMetaCtx.setUrlMatchExpression(urlMatchExpression);
this.m_serviceResolver = new ServiceResolver(m_reqMetaCtx);
this.relativeOffset = getRelativeOffset(request.getServletPath());
checkRejectList();
getUrlMappedHeaders();
// After url mapping is done, set the query params
m_reqMetaCtx.setQueryParams(getQueryParams());
m_reqMetaCtx.setRawQueryParams(getRawQueryParams());
if (isGetOrDelete()) {
adjustRestHeaders();
}
}
public RequestMetaContext getReqMetaCtx() {
return m_reqMetaCtx;
}
public ServiceResolver getServiceResolver() {
return m_serviceResolver;
}
private void addError(Throwable th) {
if (m_errors == null) {
m_errors = new ArrayList<Throwable>();
}
m_errors.add(th);
}
private int getRelativeOffset(String servletPath) {
if(servletPath == null) {
return 0;
}
return servletPath.split("/").length - 2;
}
private boolean isGetOrDelete() {
return m_isGetMethod || m_isDelete;
}
public ServerMessageContextBuilder createMessageContext(
Transport responseTransport) throws ServiceException {
Map<String, String> rawTransportHeaders = m_reqMetaCtx
.getTransportHeaders();
String forwardedFor = rawTransportHeaders
.get(SOAConstants.HTTP_HEADER_FORWARDED_FOR);
String clientPoolName = rawTransportHeaders
.get(SOAConstants.HTTP_HEADER_CLIENT_POOL_NAME);
ServiceAddress clientAddress = m_request.getClientAddress();
ServiceAddress serviceAddress = m_request.getServiceAddress();
String requestTransport;
if (m_request.getProtocol().equals(HTTP_1_1)) {
requestTransport = SOAConstants.TRANSPORT_HTTP_11;
} else if (m_request.getProtocol().equals(HTTP_1_0)) {
requestTransport = SOAConstants.TRANSPORT_HTTP_10;
} else if (m_request.getProtocol().equals(SOAConstants.TRANSPORT_LOCAL)) {
requestTransport = SOAConstants.TRANSPORT_LOCAL;
} else
requestTransport = SOAConstants.TRANSPORT_HTTP_11;
Cookie[] soaCookies = m_request.retrieveCookies();
ServerMessageContextBuilder builder = new ServerMessageContextBuilder(
m_serviceResolver, m_rawRequestUri, requestTransport,
responseTransport, rawTransportHeaders, soaCookies,
clientAddress, serviceAddress, m_errors,
m_request.getServerName(),
m_request.getServerPort(),
m_reqMetaCtx.getQueryParams(), m_reqMetaCtx.getRawQueryParams());
if (forwardedFor != null) {
forwardedFor = forwardedFor.trim();
builder.setContextProperty(
SOAConstants.CTX_PROP_TRANSPORT_FORWARDED_FOR, forwardedFor);
}
if (clientPoolName != null) {
clientPoolName = clientPoolName.trim();
builder.setContextProperty(SOAConstants.CTX_PROP_CLIENT_POOL_NAME,
clientPoolName);
}
// Setting the HttpServlet Request into the MessageContext for later use
// by ClientIpHandler
if (!requestTransport.equalsIgnoreCase(SOAConstants.TRANSPORT_LOCAL)) {
builder.setContextProperty(HTTP_SERVLET_REQUEST,
m_request.getUnderlyingObject());
}
G11nOptions g11n = builder.getG11nOptions();
Charset charset = g11n.getCharset();
String encoding = charset.name();
if (!requestTransport.equalsIgnoreCase(SOAConstants.TRANSPORT_LOCAL)) {
InputStream inputStream = getInputStream(encoding);
builder.setInputStream(inputStream);
}
return builder;
}
private void checkRejectList() throws ServiceException {
ServerServiceDesc serviceDesc = m_serviceResolver.lookupServiceDesc();
UrlMappingsDesc urlMappings = serviceDesc.getUrlMappings();
Set<String> rejectList = urlMappings.getRejectList();
if (rejectList == null) {
return;
}
for (int i = 0; i < m_params.size(); i++) {
ParamData param = m_params.get(i);
String name = param.getDecodedName();
if (rejectList.contains(name)) {
addError(new ServiceException(ErrorDataFactory.createErrorData(
ErrorConstants.SVC_RT_HEADER_NOT_ALLOWED_IN_URL,
ErrorConstants.ERRORDOMAIN, new String[] { name })));
}
}
}
private void extractTransportHeaders() throws ServiceException {
Map<String, String> headers = m_request.getHeaderNames();
Set<Map.Entry<String, String>> headerSet = headers.entrySet();
for (Map.Entry<String, String> header : headerSet) {
String name = SOAHeaders.normalizeName(header.getKey(), true);
addTransportHeader(name, header.getValue());
}
}
private Map<String, String> getRawQueryParams() {
final Map<String, String> queryParams = new LinkedHashMap<String, String>();
for (int i = 0; i < m_params.size(); i++) {
ParamData param = m_params.get(i);
queryParams.put(param.getRawName(), param.getRawValue());
}
return Collections.unmodifiableMap(queryParams);
}
private void extractSystemParameters() throws ServiceException {
// get from query string
for (int i = 0; i < m_params.size(); i++) {
ParamData param = m_params.get(i);
String name = SOAHeaders
.normalizeName(param.getDecodedName(), true);
if (SOAHeaders.isSOAHeader(name)) {
if (isGetOrDelete() && name.equals(SOAHeaders.REST_PAYLOAD)) {
m_systemParamCount = i;
param.setConsumed();
continue;
}
String value = param.getDecodedValue();
addTransportHeader(name, value);
param.setConsumed();
} else if (PseudoOperationHelper.isPseudoOpParam(name)) {
String value = param.getDecodedValue();
addPseudoOpParameter(name, value, true, false);
param.setConsumed();
}
}
}
private void getUrlMappedHeaders() throws ServiceException {
ServerServiceDesc serviceDesc = m_serviceResolver.lookupServiceDesc();
UrlMappingsDesc mappingDesc = serviceDesc.getUrlMappings();
if(mappingDesc != null) {
applyQueryMap(mappingDesc);
applyQueryOp(mappingDesc);
applyPathMap(mappingDesc);
}
List<Throwable> errors = new ArrayList<Throwable>();
HeaderMappingsDesc requestHeaderMappings = serviceDesc.getRequestHeaderMappings();
HTTPCommonUtils.applyHeaderMap(requestHeaderMappings.getHeaderMap(),
m_reqMetaCtx.getTransportHeaders(), errors);
if (errors.size() > 0) {
if (m_errors == null)
m_errors = errors;
else
m_errors.addAll(errors);
}
HTTPCommonUtils.applySuppressHeaderSet(
requestHeaderMappings.getSuppressHeaderSet(),
m_reqMetaCtx.getTransportHeaders());
String operationName = m_reqMetaCtx.getTransportHeaders().get(SOAHeaders.SERVICE_OPERATION_NAME);
// do custom mapping of operation name
if (operationName != null) {
OperationMappings operationMappings = serviceDesc
.getOperationMappings();
String operationNameForMapping = operationName;
if (operationMappings != null) {
OperationMapping operationMapping = operationMappings
.getOperationMapping(operationNameForMapping);
if (operationMapping == null) {
operationNameForMapping = m_request.getMethod().toUpperCase()
+ operationNameForMapping;
operationMapping = operationMappings
.getOperationMapping(operationNameForMapping);
}
if (operationMapping != null) {
operationName = operationMapping.getOperationName();
HTTPCommonUtils.addTransportHeader(
SOAHeaders.SERVICE_OPERATION_NAME, operationName,
m_reqMetaCtx.getTransportHeaders(), false, false,
errors);
}
}
}
// Do the request parameter mapping first as it has the lease priority.
// If the same parameter specified in the query parameter,
// that has to be taken into account.
applyOperationRequestParamMap(operationName, serviceDesc
.getOperationRequestParamsDescriptor());
applyAliases(operationName, serviceDesc
.getOperationRequestParamsDescriptor());
}
private void applyAliases(String operationName,
RequestParamsDescriptor operationRequestParamsDescriptor) {
// If the request param mapping is not defined, exit immediately.
if (operationRequestParamsDescriptor == null || operationName == null) {
return;
}
RequestParams reqParams = operationRequestParamsDescriptor.getRequestParams(operationName);
if(reqParams == null) {
return;
}
List<ParamData> newParams = new ArrayList<ParamData>();
for(Iterator<ParamData> i = m_params.iterator() ; i.hasNext(); ) {
ParamData param = i.next();
String alias = param.getRawName();
String paramName = reqParams.getParamName(alias);
if(paramName != null) {
newParams.add(new ParamData(paramName, param.getRawValue(), param.getIndex()));
i.remove();
}
}
m_params.addAll(newParams);
}
private void applyOperationRequestParamMap(String operationName,
RequestParamsDescriptor operationRequestParamsDescriptor) {
// If the request param mapping is not defined, exit immediately.
if (operationRequestParamsDescriptor == null) {
return;
}
// If there is no request uri, exit.
if (m_rawRequestUri == null || m_rawRequestUri.trim().length() < 2) {
return;
}
List<String> pathParts = StringUtils.splitStr(m_rawRequestUri.substring(1), '/');
RequestParams reqParams = operationRequestParamsDescriptor.getRequestParams(operationName);
// Build the NV pair with name as seen in the ServiceConfig and value as
// passed in the URI. Append the NP pairs to the payload.
if (reqParams == null || reqParams.count() <= 0) {
return;
}
for (Map.Entry<String, String> entry : reqParams.entries()) {
int pathIndex = Integer.parseInt(entry.getKey());
if(pathIndex < 0) {
// negative index implies relative indexing
pathIndex = relativeOffset + -pathIndex;
}
String pname = entry.getValue();
if (pname != null && pathIndex < pathParts.size()) {
String pvalue = pathParts.get(pathIndex);
m_params.add(new ParamData(pname, pvalue, m_params.size()));
}
}
}
public String getRequestUriForLogging(String requestUri) {
if (requestUri != null && requestUri.length() > 80) {
return requestUri.substring(0, 80);
}
return requestUri;
}
private void applyPathMap(UrlMappingsDesc mappingDesc)
throws ServiceException {
Map<Integer, String> pathMap = mappingDesc.getPathMap();
if (m_rawRequestUri == null || m_rawRequestUri.trim().length() < 2)
return;
List<String> pathParts = StringUtils.splitStr(m_rawRequestUri.substring(1), '/');
for (Map.Entry<Integer, String> entry : pathMap.entrySet()) {
String headerName = entry.getValue();
checkIfSecurityCredential(headerName);
int i = entry.getKey().intValue();
if (i < 0) {
// Negative index indicates relative index
i = relativeOffset + -i;
}
if (i < pathParts.size()) {
String headerValue = pathParts.get(i);
addTransportHeader(headerName, headerValue);
}
}
}
private void applyQueryOp(UrlMappingsDesc mappingDesc)
throws ServiceException {
String queryOpMapping = mappingDesc.getQueryOpMapping();
if (queryOpMapping == null || m_systemParamCount == 0) {
return;
}
checkIfSecurityCredential(queryOpMapping);
ParamData param = m_params.get(0);
addTransportHeader(queryOpMapping, param.getDecodedName());
param.setConsumed();
}
private Map<String, String> getQueryParams() {
Map<String, String> queryParams = new LinkedHashMap<String, String>();
// Fix for issue bug 540599. Since payload can occur before the
// payload marker, we need to
for (int i = 0; i < m_params.size(); i++) {
ParamData param = m_params.get(i);
queryParams.put(param.getDecodedName(), param.getDecodedValue());
}
return queryParams;
}
private void applyQueryMap(UrlMappingsDesc mappingDesc)
throws ServiceException {
Map<String, String> queryMap = mappingDesc.getQueryMap();
Map<String, String> upperCaseQueryMap = mappingDesc
.getUpperCaseQueryMap();
for (int i = 0; i < m_params.size(); i++) {
ParamData param = m_params.get(i);
String paramName = param.getDecodedName();
String headerName = queryMap.get(paramName);
if (headerName == null) {
headerName = upperCaseQueryMap.get(paramName.toUpperCase());
}
if (headerName != null) {
checkIfSecurityCredential(headerName);
String paramValue = param.getDecodedValue();
addTransportHeader(headerName, paramValue);
param.setConsumed();
}
}
}
private void addTransportHeader(String name, String value)
throws ServiceException {
addTransportHeader(name, value, true, false);
}
private void addTransportHeader(String name, String value,
boolean checkConflicts, boolean keepOriginalValue)
throws ServiceException {
addParameter(name, value, m_reqMetaCtx.getTransportHeaders(),
checkConflicts, keepOriginalValue);
}
private void addPseudoOpParameter(String name, String value,
boolean checkConflicts, boolean keepOriginalValue)
throws ServiceException {
addParameter(name, value, m_reqMetaCtx.getPseudoOperationParameters(),
checkConflicts, keepOriginalValue);
}
private void addParameter(String name, String value,
Map<String, String> target, boolean checkConflicts,
boolean keepOriginalValue) throws ServiceException {
String oldValue = target.get(name);
if (oldValue != null) {
if (oldValue.equals(value)) {
return;
}
if (checkConflicts) {
addError(new ServiceException(ErrorDataFactory.createErrorData(
ErrorConstants.SVC_RT_PARAM_CONFLICT,
ErrorConstants.ERRORDOMAIN, new Object[] { name,
oldValue, value })));
return;
}
if (keepOriginalValue) {
return;
}
}
target.put(name, value);
}
private void parseParameters() {
String str = m_request.getQueryString();
if (str == null || str.length() == 0) {
return;
}
int startPos = 0;
int equalPos = -1;
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == '&') {
parseParameter(str, startPos, i, equalPos);
startPos = i + 1;
equalPos = -1;
continue;
}
if (c == '=' && equalPos == -1) {
equalPos = i;
}
}
if (startPos < str.length()) {
parseParameter(str, startPos, str.length(), equalPos);
}
}
private void parseParameter(String str, int start, int end, int equalPos) {
String name;
String value;
if (equalPos != -1) {
name = str.substring(start, equalPos);
value = str.substring(equalPos + 1, end);
} else {
name = str.substring(start, end);
value = "";
}
checkIfSecurityCredential(name);
m_params.add(new ParamData(name, value, m_params.size()));
}
/**
* This method checks in the case of the GET request the specified param
* name is a security-credential. If it is, an error is added to the
* error-list.
*
* @param paramName
* the parameter name
* @return true, if it is a security credential, false otherwise
*/
private void checkIfSecurityCredential(String paramName) {
if (!nonAnonGetAllowed) {
// Check if this query parameter name is a security header
if (isGetOrDelete()
&& (paramName != null)
&& paramName.toUpperCase().startsWith(
SECURITY_HEADER_PREFIX)) {
// do error
addError(new ServiceException(
ErrorDataFactory
.createErrorData(
org.ebayopensource.turmeric.security.errorlibrary.ErrorConstants.SVC_SECURITY_INVALID_URL_CREDENTIALS,
org.ebayopensource.turmeric.security.errorlibrary.ErrorConstants.ERRORDOMAIN,
new String[] { paramName })));
}
}
}
static String decode(String str) {
int idx = str.indexOf('%');
int idx2 = str.indexOf('+');
if (idx == -1 && idx2 == -1) {
return str;
}
// find first position
if (idx == -1) {
idx = idx2;
} else if (idx2 != -1 && idx > idx2) {
idx = idx2;
}
int len = str.length();
StringBuilder sb = new StringBuilder(len);
sb.append(str.substring(0, idx));
for (int i = idx; i < len; i++) {
char c = str.charAt(i);
if (c == '+') {
sb.append(' ');
continue;
}
if (c != '%') {
sb.append(c);
continue;
}
// read next 2 digits
if (i + 2 >= len) {
// invalid
sb.append(c);
continue;
}
char c1 = str.charAt(i + 1);
char c2 = str.charAt(i + 2);
i += 2;
int b1 = BindingUtils.getHexDigitValue(c1);
int b2 = BindingUtils.getHexDigitValue(c2);
if (b1 == -1 || b2 == -1) {
// invalid
sb.append(c1);
sb.append(c2);
continue;
}
sb.append((char) ((b1 << 4) | b2));
}
return sb.toString();
}
private String buildQueryString() {
StringBuilder sb = new StringBuilder(1024);
ParamData namespaceParamData = null;
// Per Ron, we need to take into account payload parameters
// that happen to appear before the payload-marker to
// fix bug 540599
int startPos = 0;
for (int i = startPos; i < m_params.size(); i++) {
ParamData param = m_params.get(i);
if (param.isConsumed()) {
continue;
}
String name = param.getRawName();
String value = param.getRawValue();
// case insensitive check to figure out of this is
// the first namespace parameter
if ((namespaceParamData == null)
&& name.toLowerCase().startsWith(NAMESPACE_PREFIX)) {
namespaceParamData = param;
continue;
}
if (sb.length() > 0) {
sb.append('&');
}
sb.append(name);
sb.append('=');
if (value != null && value.length() > 0) {
sb.append(value);
}
}
if (namespaceParamData == null) {
return sb.toString();
}
StringBuilder ns = new StringBuilder();
ns.append(namespaceParamData.getRawName());
if (namespaceParamData.getRawValue() != null) {
ns.append("=").append(namespaceParamData.getRawValue());
}
if (sb.length() == 0) {
return ns.toString();
}
return ns.append("&").append(sb.toString()).toString();
}
private InputStream getInputStream(String encoding) throws ServiceException {
try {
if (isGetOrDelete()) {
// remove consumed params
String queryString = buildQueryString();
return new ByteArrayInputStream(queryString.getBytes(encoding));
}
return m_request.getInputStream();
} catch (IOException e) {
throw new ServiceException(ErrorDataFactory.createErrorData(
ErrorConstants.SVC_TRANSPORT_INBOUND_IO_EXCEPTION,
ErrorConstants.ERRORDOMAIN), e);
}
}
// Has Req DF Has Resp DF Final Req DF Final Resp DF
// ======================================================================
// T T Incoming Req DF Incoming Resp DF
// T F Incoming Req DF Incoming Req DF
// F T NV Incoming Resp DF
// F F NV XML
private void adjustRestHeaders() throws ServiceException {
addTransportHeader(SOAHeaders.ELEMENT_ORDERING_PRESERVE, "FALSE",
false, true);
// addTransportHeader(SOAHeaders.REQUEST_DATA_FORMAT,
// BindingConstants.PAYLOAD_NV, false, true);
// addTransportHeader(SOAHeaders.RESPONSE_DATA_FORMAT,
// BindingConstants.PAYLOAD_XML, false, true);
if (!m_reqMetaCtx.getTransportHeaders().containsKey(
SOAHeaders.REQUEST_DATA_FORMAT)) {
addTransportHeader(SOAHeaders.REQUEST_DATA_FORMAT,
BindingConstants.PAYLOAD_NV, false, true);
if (!m_reqMetaCtx.getTransportHeaders().containsKey(
SOAHeaders.RESPONSE_DATA_FORMAT)) {
addTransportHeader(SOAHeaders.RESPONSE_DATA_FORMAT,
BindingConstants.PAYLOAD_XML, false, true);
}
}
}
private boolean isRestCall() {
if (m_isGetMethod
&& !m_reqMetaCtx.getTransportHeaders().containsKey(
SOAHeaders.REQUEST_DATA_FORMAT)
&& !m_reqMetaCtx.getTransportHeaders().containsKey(
SOAHeaders.RESPONSE_DATA_FORMAT)) {
return true;
} else {
return false;
}
}
/**
* This detects the first public proxy ip from the commas delimited IP list
* from the x-forwarded-for header going from left to right. An IP is
* considered PRIVATE if it falls in one of the following IP address spaces
* as noted in RFC1918,
*
* 10.0.0.0 - 10.255.255.255 (10/8 prefix) 172.16.0.0 - 172.31.255.255
* (172.16/12 prefix) 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)
*
* An empty string is returned should 1) the IP list from the header is
* invalid 2) no public proxy IP has been identified
*
* @param xForwardedForHeader
* @return String proxy ip
*/
public static String getFirstPublicProxyIP(final String xForwardedForHeader) {
if (xForwardedForHeader == null || xForwardedForHeader.length() == 0) {
if (isDebugOn()) {
log("getFirstPublicProxyIP() - x-forwarded-for header is empty!");
}
return "";
}
final StringTokenizer st = new StringTokenizer(xForwardedForHeader, ",");
String token = "";
while (st.hasMoreTokens()) {
token = st.nextToken().trim();
if (isPublicIpAddress(token)) {
if (isDebugOn()) {
log("The first *public* proxy IP (x-forwarded-for) - "
+ token);
}
return token;
}
}
if (isDebugOn()) {
log("getFirstPublicProxyIP() - no *public* proxy IP is found (x-forwarded-for)!");
}
return "";
}
private static final int RFC1918_10_0_0_0 = 0xA000000; // 10.0.0.0
private static final int RFC1918_10_255_255_255 = 0xAFFFFFF; // 10.255.255.255
private static final int RFC1918_172_16_0_0 = 0xAC100000; // 172.16.0.0
private static final int RFC1918_172_31_255_255 = 0xAC1FFFFF; // 172.31.255.255
private static final int RFC1918_192_168_0_0 = 0xC0A80000; // 192.168.0.0
private static final int RFC1918_192_168_255_255 = 0xC0A8FFFF; // 192.168.255.255
private static final String IP_ADDRESS_LOCALHOST = "127.0.0.1";
private static boolean isPublicIpAddress(final String ipAddress) {
if (ipAddress == null || ipAddress.length() == 0) {
if (isDebugOn()) {
log("isPublicIpAddress() - ip address is null or empty!");
}
return false;
}
// Exclude the self-looping scenario. Note that this should not happen.
// If it does, it's a good indication that the request has been
// 'hacked'.
if (ipAddress.equalsIgnoreCase(IP_ADDRESS_LOCALHOST)) {
log(LogLevel.WARN,
"Localhost ip detected from header, x-forwarded-for: "
+ ipAddress);
return false;
}
try {
final InetAddress inetAddr = IpAddressUtils.getByName(ipAddress);
final byte[] addrInBytes = inetAddr.getAddress();
// The byte array is in network order and the most significant
// byte of the address is in byte 0.
int converted = addrInBytes[3] & 0xFF;
converted |= ((addrInBytes[2] << 8) & 0xFF00);
converted |= ((addrInBytes[1] << 16) & 0xFF0000);
converted |= ((addrInBytes[0] << 24) & 0xFF000000);
return (!(converted >= RFC1918_10_0_0_0 && converted <= RFC1918_10_255_255_255)
&& !(converted >= RFC1918_172_16_0_0 && converted <= RFC1918_172_31_255_255) && !(converted >= RFC1918_192_168_0_0 && converted <= RFC1918_192_168_255_255));
} catch (UnknownHostException e) {
log(LogLevel.WARN, "UnknowHostException (x-forwarded-for): "
+ ipAddress);
return false;
}
}
// End of borrowed code
static boolean isDebugOn() {
return s_logger.isLogEnabled(LogLevel.DEBUG);
}
static void log(final String message) {
s_logger.debug(message);
}
static void log(final Throwable throwable) {
s_logger.log(LogLevel.DEBUG, throwable);
}
static void log(final LogLevel level, final String message) {
s_logger.log(level, message);
}
static class ParamData {
private final String m_rawName;
private final String m_rawValue;
private final boolean m_hasRawData;
private final int m_index;
private boolean m_isConsumed;
private String m_decodedName;
private String m_decodedValue;
public ParamData(String rawName, String rawValue, int index) {
this(rawName, null, rawValue, null, true, index);
}
public ParamData(String rawName, String decodedName, String rawValue,
String decodedValue, boolean hasRawData, int index) {
m_rawName = rawName;
m_rawValue = rawValue;
m_hasRawData = hasRawData;
m_decodedName = decodedName;
m_decodedValue = decodedValue;
m_index = index;
}
public String getRawName() {
if (!m_hasRawData) {
throw new IllegalStateException(
"Internel error: HTTP param RAW name is not available");
}
return m_rawName;
}
public String getRawValue() {
if (!m_hasRawData) {
throw new IllegalStateException(
"Internel error: HTTP param RAW value is not available");
}
return m_rawValue;
}
public boolean hasRawData() {
return m_hasRawData;
}
public String getDecodedName() {
if (m_decodedName == null) {
m_decodedName = decode(m_rawName);
}
return m_decodedName;
}
public String getDecodedValue() {
if (m_decodedValue == null) {
m_decodedValue = decode(m_rawValue);
}
return m_decodedValue;
}
public int getIndex() {
return m_index;
}
public boolean isConsumed() {
return m_isConsumed;
}
public void setConsumed() {
m_isConsumed = true;
}
@Override
public String toString() {
return m_rawName + "=" + m_rawValue;
}
}
}