Java tutorial
/* * Copyright MITRE * * 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.mitre.dsmiley.httpproxy; import java.io.ByteArrayInputStream; import java.io.Closeable; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Constructor; import java.net.HttpCookie; import java.net.URI; import java.util.BitSet; import java.util.Enumeration; import java.util.Formatter; import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpHeaders; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.auth.AuthSchemeProvider; import org.apache.http.auth.AuthScope; import org.apache.http.auth.NTCredentials; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.CookieStore; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.HttpClient; import org.apache.http.client.config.AuthSchemes; import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.AbortableHttpRequest; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.params.ClientPNames; import org.apache.http.client.params.CookiePolicy; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.client.utils.URIUtils; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.entity.InputStreamEntity; import org.apache.http.impl.auth.BasicSchemeFactory; import org.apache.http.impl.auth.DigestSchemeFactory; import org.apache.http.impl.auth.KerberosSchemeFactory; import org.apache.http.impl.auth.NTLMSchemeFactory; import org.apache.http.impl.auth.SPNegoSchemeFactory; import org.apache.http.impl.auth.win.WindowsCredentialsProvider; import org.apache.http.impl.client.BasicCookieStore; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.SystemDefaultCredentialsProvider; import org.apache.http.impl.client.WinHttpClients; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHttpEntityEnclosingRequest; import org.apache.http.message.BasicHttpRequest; import org.apache.http.message.HeaderGroup; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpParams; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; import waffle.windows.auth.IWindowsCredentialsHandle; import waffle.windows.auth.IWindowsSecurityContext; import waffle.windows.auth.impl.WindowsAccountImpl; import waffle.windows.auth.impl.WindowsCredentialsHandleImpl; import waffle.windows.auth.impl.WindowsSecurityContextImpl; /** * An HTTP reverse proxy/gateway servlet. It is designed to be extended for * customization if desired. Most of the work is handled by * <a href="http://hc.apache.org/httpcomponents-client-ga/">Apache * HttpClient</a>. * <p> * There are alternatives to a servlet based proxy such as Apache mod_proxy if * that is available to you. However this servlet is easily customizable by * Java, secure-able by your web application's security (e.g. spring-security), * portable across servlet engines, and is embeddable into another web * application. * </p> * <p> * Inspiration: http://httpd.apache.org/docs/2.0/mod/mod_proxy.html * </p> * * @author David Smiley dsmiley@mitre.org */ @SuppressWarnings({ "deprecation", "serial" }) public class ProxyServlet extends HttpServlet { private class HttpClientRefreshThread extends Thread { long refreshTime; String refreshURL; HttpClientRefreshThread(long paramRefreshTime, String paramRefreshURL) { this.refreshTime = paramRefreshTime; this.refreshURL = paramRefreshURL; } public void run() { do { try { HttpClientRefreshThread.sleep(refreshTime); HttpGet get = new HttpGet(refreshURL); addBrowserHeader(get); getProxyClient().execute(get, proxyContext); get.reset(); } catch (InterruptedException e) { return; } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } while (true); } } /* INIT PARAMETER NAME CONSTANTS */ /** * A boolean parameter name to enable logging of input and target URLs to * the servlet log. */ public static final String P_LOG = "log"; private static final String SECURITY_PACKAGE = "Negotiate"; /** A boolean parameter name to enable forwarding of the client IP */ public static final String P_FORWARDEDFOR = "forwardip"; /** The parameter name for the target (destination) URI to proxy to. */ protected static final String P_TARGET_URI = "targetUri"; protected static final String ATTR_TARGET_URI = ProxyServlet.class.getSimpleName() + ".targetUri"; protected static final String ATTR_TARGET_HOST = ProxyServlet.class.getSimpleName() + ".targetHost"; /* MISC */ protected boolean doLog = false; protected boolean doActAsBrowser = false; protected boolean doForwardIP = true; /** User agents shouldn't send the url fragment but what if it does? */ protected boolean doSendUrlFragment = true; // These next 3 are cached here, and should only be referred to in // initialization logic. See the // ATTR_* parameters. /** From the configured parameter "targetUri". */ protected String targetUri; protected URI targetUriObj;// new URI(targetUri) protected HttpHost targetHost;// URIUtils.extractHost(targetUriObj); private HttpClient proxyClient; private HttpContext proxyContext; private String cookieString; @Override public String getServletInfo() { return "A proxy servlet by David Smiley, dsmiley@apache.org"; } protected String getTargetUri(HttpServletRequest servletRequest) { return (String) servletRequest.getAttribute(ATTR_TARGET_URI); } protected HttpHost getTargetHost(HttpServletRequest servletRequest) { return (HttpHost) servletRequest.getAttribute(ATTR_TARGET_HOST); } /** * Reads a configuration parameter. By default it reads servlet init * parameters but it can be overridden. */ protected String getConfigParam(String key) { return getServletConfig().getInitParameter(key); } @Override public void init() throws ServletException { String doLogStr = getConfigParam(P_LOG); if (doLogStr != null) { this.doLog = Boolean.parseBoolean(doLogStr); } String doForwardIPString = getConfigParam(P_FORWARDEDFOR); if (doForwardIPString != null) { this.doForwardIP = Boolean.parseBoolean(doForwardIPString); } initTarget();// sets target* HttpParams hcParams = new BasicHttpParams(); hcParams.setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.IGNORE_COOKIES); hcParams.setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, false); // See // #70 readConfigParam(hcParams, ClientPNames.HANDLE_REDIRECTS, Boolean.class); proxyClient = createHttpClient(hcParams); } protected void initTarget() throws ServletException { targetUri = getConfigParam(P_TARGET_URI); if (targetUri == null) throw new ServletException(P_TARGET_URI + " is required."); // test it's valid try { targetUriObj = new URI(targetUri); } catch (Exception e) { throw new ServletException("Trying to process targetUri init parameter: " + e, e); } targetHost = URIUtils.extractHost(targetUriObj); } /** * Called from {@link #init(javax.servlet.ServletConfig)}. HttpClient offers * many opportunities for customization. By default, <a href= * "http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/SystemDefaultHttpClient.html"> * SystemDefaultHttpClient</a> is used if available, otherwise it falls back * to: * * <pre> * new DefaultHttpClient(new ThreadSafeClientConnManager(), hcParams) * </pre> * * SystemDefaultHttpClient uses PoolingClientConnectionManager. In any case, * it should be thread-safe. */ protected HttpClient createHttpClient(HttpParams hcParams) { try { String negotiateURL = getConfigParam("negotiate.url"); String negotiateSPN = getConfigParam("negotiate.spn"); if (negotiateURL != null && negotiateSPN != null) { System.out.println("negotiate url:" + negotiateURL); System.out.println("negotiate spn:" + negotiateSPN); // initialize the Windows security Context to get the negotiate // client token IWindowsSecurityContext clientContext = null; IWindowsCredentialsHandle clientCredentials = null; clientContext = WindowsSecurityContextImpl.getCurrent(SECURITY_PACKAGE, negotiateSPN); clientCredentials = WindowsCredentialsHandleImpl.getCurrent(SECURITY_PACKAGE); clientCredentials.initialize(); String username = WindowsAccountImpl.getCurrentUsername(); System.out.println("credentials for user " + username + " get prepared"); byte[] token = clientContext.getToken(); // encode the token with Base64 to be able to add it to the http // header String clientToken = Base64.encodeBase64String(token); System.out.println("clientToken" + clientToken); // if there is only a negotiate url the rest of the // authorization is based on cookies // so we need to support them. CookieStore cookieStore = new BasicCookieStore(); RequestConfig globalConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.DEFAULT).build(); HttpClientContext context = HttpClientContext.create(); proxyContext = context; context.setCookieStore(cookieStore); HttpClient httpClient = HttpClients.custom().disableRedirectHandling() .setDefaultRequestConfig(globalConfig).setDefaultCookieStore(cookieStore).build(); // first we need to act as a normal browser to get a http 401 // with negotiate header doActAsBrowser = true; HttpGet browserHttpGet = new HttpGet(negotiateURL); addBrowserHeader(browserHttpGet); HttpResponse rep = httpClient.execute(browserHttpGet, context); if (rep.getStatusLine().getStatusCode() == 401) { System.out.println("negotiate requested - sending negotiate client token"); HttpGet negotiateHttpGet = new HttpGet(negotiateURL); addBrowserHeader(negotiateHttpGet); negotiateHttpGet.addHeader("Authorization", "Negotiate " + clientToken); HttpResponse response = httpClient.execute(negotiateHttpGet, context); System.out.println( "http result code of negotiate request:" + response.getStatusLine().getStatusCode()); // now the url needs to be called periodically to keep the // cookie and connection alive String refreshTimeString = getConfigParam("negotiate.refreshtime"); long refreshTime = 1000000; if (refreshTimeString != null) { refreshTime = Long.parseLong(refreshTimeString); } HttpClientRefreshThread thread = new HttpClientRefreshThread(refreshTime, negotiateURL); thread.start(); List<org.apache.http.cookie.Cookie> cookies = context.getCookieStore().getCookies(); cookieString = ""; int size = cookies.size() - 1; for (int i = 0; i < cookies.size(); i++) { cookieString += cookies.get(i).getName(); cookieString += "="; cookieString += cookies.get(i).getValue(); if (i != size) cookieString += "; "; } } else { System.out.println("No negotiate requested"); } } else { if (!WinHttpClients.isWinAuthAvailable()) { System.out.println("Integrated Win auth is not supported!!!"); } else { HttpClientBuilder builder = WinHttpClients.custom(); Registry<AuthSchemeProvider> authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create() .register(AuthSchemes.BASIC, new BasicSchemeFactory()) .register(AuthSchemes.DIGEST, new DigestSchemeFactory()) .register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory()) .register(AuthSchemes.NTLM, new NTLMSchemeFactory()) .register(AuthSchemes.KERBEROS, new KerberosSchemeFactory()).build(); builder.setDefaultAuthSchemeRegistry(authSchemeRegistry); String username = getConfigParam("user"); String password = getConfigParam("password"); String domain = getConfigParam("domain"); String host = getConfigParam("host"); if (username != null) { NTCredentials cred = new NTCredentials(username, password, host, domain); CredentialsProvider credsProvider = new WindowsCredentialsProvider( new SystemDefaultCredentialsProvider()); credsProvider.setCredentials(AuthScope.ANY, cred); builder.setDefaultCredentialsProvider(credsProvider); } builder.disableCookieManagement(); builder.disableRedirectHandling(); return builder.build(); } // as of HttpComponents v4.2, this class is better since it uses // System // Properties: Class<?> clientClazz = Class.forName("org.apache.http.impl.client.SystemDefaultHttpClient"); Constructor<?> constructor = clientClazz.getConstructor(HttpParams.class); return (HttpClient) constructor.newInstance(hcParams); } } catch (ClassNotFoundException e) { // no problem; use v4.1 below } catch (Exception e) { throw new RuntimeException(e); } // Fallback on using older client: return new DefaultHttpClient(new ThreadSafeClientConnManager(), hcParams); } private void addBrowserHeader(HttpRequest browserHttpGet) { browserHttpGet.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36"); browserHttpGet.addHeader("Cache-Control", "no-cache"); browserHttpGet.addHeader("Accept-Encoding", "deflate,br"); browserHttpGet.addHeader("Accept", "*/*"); browserHttpGet.addHeader("Accept-Language", "de-DE,de;q=0.8,en-US;q=0.6,en;q=0"); browserHttpGet.addHeader("Origin", "chrome-extension://fhbjgbiflinjbdqqehcddcbncdddomop"); } /** * The http client used. * * @see #createHttpClient(HttpParams) */ protected HttpClient getProxyClient() { return proxyClient; } /** * Reads a servlet config parameter by the name {@code hcParamName} of type * {@code type}, and set it in {@code hcParams}. */ protected void readConfigParam(HttpParams hcParams, String hcParamName, Class<?> type) { String val_str = getConfigParam(hcParamName); if (val_str == null) return; Object val_obj; if (type == String.class) { val_obj = val_str; } else { try { // noinspection unchecked val_obj = type.getMethod("valueOf", String.class).invoke(type, val_str); } catch (Exception e) { throw new RuntimeException(e); } } hcParams.setParameter(hcParamName, val_obj); } @Override public void destroy() { // As of HttpComponents v4.3, clients implement closeable if (proxyClient instanceof Closeable) {// TODO AutoCloseable in Java 1.6 try { ((Closeable) proxyClient).close(); } catch (IOException e) { System.out.println("While destroying servlet, shutting down HttpClient: " + e.getMessage()); } } else { // Older releases require we do this: if (proxyClient != null) proxyClient.getConnectionManager().shutdown(); } super.destroy(); } @Override protected void service(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws ServletException, IOException { // initialize request attributes from caches if unset by a subclass by // this point if (servletRequest.getAttribute(ATTR_TARGET_URI) == null) { servletRequest.setAttribute(ATTR_TARGET_URI, targetUri); } if (servletRequest.getAttribute(ATTR_TARGET_HOST) == null) { servletRequest.setAttribute(ATTR_TARGET_HOST, targetHost); } // Make the Request // note: we won't transfer the protocol version because I'm not sure it // would truly be compatible String method = servletRequest.getMethod(); String proxyRequestUri = rewriteUrlFromRequest(servletRequest); HttpRequest proxyRequest; // spec: RFC 2616, sec 4.3: either of these two headers signal that // there is a message body. System.out.println("proxyrequestURI" + proxyRequestUri); if (servletRequest.getHeader(HttpHeaders.CONTENT_LENGTH) != null || servletRequest.getHeader(HttpHeaders.TRANSFER_ENCODING) != null) { proxyRequest = newProxyRequestWithEntity(method, proxyRequestUri, servletRequest); } else { proxyRequest = new BasicHttpRequest(method, proxyRequestUri); } proxyRequest.addHeader(org.apache.http.cookie.SM.COOKIE, cookieString); System.out.println("added cookie::" + cookieString); addBrowserHeader(proxyRequest); copyRequestHeaders(servletRequest, proxyRequest); //setXForwardedForHeader(servletRequest, proxyRequest); System.out.println("method" + method); System.out.println("final header showing"); for (Header header : proxyRequest.getAllHeaders()) { System.out.println(header.getName() + "::" + header.getValue() + "::"); } System.out.println("end final header"); HttpResponse proxyResponse = null; try { // Execute the request if (doLog) { System.out.println("proxy " + method + " uri: " + servletRequest.getRequestURI() + " -- " + proxyRequest.getRequestLine().getUri()); } System.out.println("targethost:" + getTargetHost(servletRequest)); System.out.println(proxyRequest.getRequestLine().toString()); proxyResponse = proxyClient.execute(getTargetHost(servletRequest), proxyRequest, proxyContext); // Process the response: // Pass the response code. This method with the "reason phrase" is // deprecated but it's the // only way to pass the reason along too. int statusCode = proxyResponse.getStatusLine().getStatusCode(); System.out.println(proxyResponse.getStatusLine().getStatusCode() + proxyResponse.getStatusLine().getReasonPhrase()); // noinspection deprecation servletResponse.setStatus(statusCode, proxyResponse.getStatusLine().getReasonPhrase()); // Copying response headers to make sure SESSIONID or other Cookie // which comes from the remote // server will be saved in client when the proxied url was // redirected to another one. // See issue // [#51](https://github.com/mitre/HTTP-Proxy-Servlet/issues/51) copyResponseHeaders(proxyResponse, servletRequest, servletResponse); if (statusCode == HttpServletResponse.SC_NOT_MODIFIED) { // 304 needs special handling. See: // http://www.ics.uci.edu/pub/ietf/http/rfc1945.html#Code304 // Don't send body entity/content! servletResponse.setIntHeader(HttpHeaders.CONTENT_LENGTH, 0); } else { // Send the content to the client copyResponseEntity(proxyResponse, servletResponse, proxyRequest, servletRequest); } } catch (Exception e) { // abort request, according to best practice with HttpClient if (proxyRequest instanceof AbortableHttpRequest) { AbortableHttpRequest abortableHttpRequest = (AbortableHttpRequest) proxyRequest; abortableHttpRequest.abort(); } if (e instanceof RuntimeException) throw (RuntimeException) e; if (e instanceof ServletException) throw (ServletException) e; // noinspection ConstantConditions if (e instanceof IOException) throw (IOException) e; throw new RuntimeException(e); } finally { // make sure the entire entity was consumed, so the connection is // released if (proxyResponse != null) consumeQuietly(proxyResponse.getEntity()); // Note: Don't need to close servlet outputStream: // http://stackoverflow.com/questions/1159168/should-one-call-close-on-httpservletresponse-getoutputstream-getwriter } } protected HttpRequest newProxyRequestWithEntity(String method, String proxyRequestUri, HttpServletRequest servletRequest) throws IOException { HttpEntityEnclosingRequest eProxyRequest = new BasicHttpEntityEnclosingRequest(method, proxyRequestUri); // Add the input entity (streamed) // note: we don't bother ensuring we close the servletInputStream since // the container handles it String entity = IOUtils.toString(servletRequest.getInputStream()); System.out.println("Body:"); System.out.println(entity); eProxyRequest.setEntity(new InputStreamEntity(new ByteArrayInputStream(entity.getBytes()), -1)); return eProxyRequest; } // Get the header value as a long in order to more correctly proxy very // large requests private long getContentLength(HttpServletRequest request) { String contentLengthHeader = request.getHeader("Content-Length"); if (contentLengthHeader != null) { return Long.parseLong(contentLengthHeader); } return -1L; } protected void closeQuietly(Closeable closeable) { try { closeable.close(); } catch (IOException e) { System.out.println(e.getMessage()); } } /** * HttpClient v4.1 doesn't have the * {@link org.apache.http.util.EntityUtils#consumeQuietly(org.apache.http.HttpEntity)} * method. */ protected void consumeQuietly(HttpEntity entity) { try { EntityUtils.consume(entity); } catch (IOException e) {// ignore System.out.println(e.getMessage()); } } /** * These are the "hop-by-hop" headers that should not be copied. * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html I use an * HttpClient HeaderGroup class instead of Set<String> because this * approach does case insensitive lookup faster. */ protected static final HeaderGroup hopByHopHeaders; static { hopByHopHeaders = new HeaderGroup(); String[] headers = new String[] { "Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization", "TE", "Trailers", "Transfer-Encoding", "Upgrade" }; for (String header : headers) { hopByHopHeaders.addHeader(new BasicHeader(header, null)); } } /** Copy request headers from the servlet client to the proxy request. */ protected void copyRequestHeaders(HttpServletRequest servletRequest, HttpRequest proxyRequest) { // Get an Enumeration of all of the header names sent by the client @SuppressWarnings("unchecked") Enumeration<String> enumerationOfHeaderNames = servletRequest.getHeaderNames(); while (enumerationOfHeaderNames.hasMoreElements()) { String headerName = enumerationOfHeaderNames.nextElement(); copyRequestHeader(servletRequest, proxyRequest, headerName); } } /** * Copy a request header from the servlet client to the proxy request. This * is easily overwritten to filter out certain headers if desired. */ protected void copyRequestHeader(HttpServletRequest servletRequest, HttpRequest proxyRequest, String headerName) { // Instead the content-length is effectively set via InputStreamEntity if (headerName.equalsIgnoreCase(HttpHeaders.CONTENT_LENGTH)) return; if (hopByHopHeaders.containsHeader(headerName)) return; if (headerName.equalsIgnoreCase("Authorization")) { return; } else if (headerName.equalsIgnoreCase("User-Agent")) { return; } else if (headerName.equalsIgnoreCase("Accept-Encoding")) { return; } else if (headerName.equalsIgnoreCase("Cache-Control")) { return; } else if (headerName.equalsIgnoreCase("Accept")) { return; } else if (headerName.equalsIgnoreCase("Accept-Language")) { return; } else if (headerName.equalsIgnoreCase("Origin")) { return; } @SuppressWarnings("unchecked") Enumeration<String> headers = servletRequest.getHeaders(headerName); while (headers.hasMoreElements()) {// sometimes more than one value String headerValue = headers.nextElement(); // In case the proxy host is running multiple virtual servers, // rewrite the Host header to ensure that we get content from // the correct virtual server if (headerName.equalsIgnoreCase(HttpHeaders.HOST)) { HttpHost host = getTargetHost(servletRequest); headerValue = host.getHostName(); if (host.getPort() != -1) headerValue += ":" + host.getPort(); System.out.println("add request header " + headerName + " with value " + headerValue); } else if (headerName.equalsIgnoreCase(org.apache.http.cookie.SM.COOKIE)) { //headerValue = getRealCookie(headerValue); } else { System.out.println("add request header " + headerName + " with value " + headerValue); } System.out.println("Copying request header" + headerName + "headerValue"); proxyRequest.addHeader(headerName, headerValue); } } private void setXForwardedForHeader(HttpServletRequest servletRequest, HttpRequest proxyRequest) { if (doForwardIP) { String headerName = "X-Forwarded-For"; String newHeader = servletRequest.getRemoteAddr(); String existingHeader = servletRequest.getHeader(headerName); if (existingHeader != null) { newHeader = existingHeader + ", " + newHeader; } proxyRequest.setHeader(headerName, newHeader); } } /** Copy proxied response headers back to the servlet client. */ protected void copyResponseHeaders(HttpResponse proxyResponse, HttpServletRequest servletRequest, HttpServletResponse servletResponse) { for (Header header : proxyResponse.getAllHeaders()) { copyResponseHeader(servletRequest, servletResponse, header); log("Copying resp. header" + header.getName() + " value:" + header.getValue()); } } /** * Copy a proxied response header back to the servlet client. This is easily * overwritten to filter out certain headers if desired. */ protected void copyResponseHeader(HttpServletRequest servletRequest, HttpServletResponse servletResponse, Header header) { String headerName = header.getName(); if (hopByHopHeaders.containsHeader(headerName)) return; String headerValue = header.getValue(); if (headerName.equalsIgnoreCase(org.apache.http.cookie.SM.SET_COOKIE) || headerName.equalsIgnoreCase(org.apache.http.cookie.SM.SET_COOKIE2)) { copyProxyCookie(servletRequest, servletResponse, headerValue); } else if (headerName.equalsIgnoreCase(HttpHeaders.LOCATION)) { // LOCATION Header may have to be rewritten. servletResponse.addHeader(headerName, rewriteUrlFromResponse(servletRequest, headerValue)); } else { servletResponse.addHeader(headerName, headerValue); } } /** * Copy cookie from the proxy to the servlet client. Replaces cookie path to * local path and renames cookie to avoid collisions. */ protected void copyProxyCookie(HttpServletRequest servletRequest, HttpServletResponse servletResponse, String headerValue) { List<HttpCookie> cookies = HttpCookie.parse(headerValue); String path = servletRequest.getContextPath(); // path starts with / or // is empty string path += servletRequest.getServletPath(); // servlet path starts with / // or is empty string for (HttpCookie cookie : cookies) { // set cookie name prefixed w/ a proxy value so it won't collide w/ // other cookies String proxyCookieName = getCookieNamePrefix(cookie.getName()) + cookie.getName(); Cookie servletCookie = new Cookie(proxyCookieName, cookie.getValue()); servletCookie.setComment(cookie.getComment()); servletCookie.setMaxAge((int) cookie.getMaxAge()); servletCookie.setPath(path); // set to the path of the proxy servlet // don't set cookie domain servletCookie.setSecure(cookie.getSecure()); servletCookie.setVersion(cookie.getVersion()); servletResponse.addCookie(servletCookie); } } /** * Take any client cookies that were originally from the proxy and prepare * them to send to the proxy. This relies on cookie headers being set * correctly according to RFC 6265 Sec 5.4. This also blocks any local * cookies from being sent to the proxy. */ protected String getRealCookie(String cookieValue) { StringBuilder escapedCookie = new StringBuilder(); String cookies[] = cookieValue.split("; "); for (String cookie : cookies) { String cookieSplit[] = cookie.split("="); if (cookieSplit.length == 2) { String cookieName = cookieSplit[0]; if (cookieName.startsWith(getCookieNamePrefix(cookieName))) { cookieName = cookieName.substring(getCookieNamePrefix(cookieName).length()); if (escapedCookie.length() > 0) { escapedCookie.append("; "); } escapedCookie.append(cookieName).append("=").append(cookieSplit[1]); } } cookieValue = escapedCookie.toString(); } return cookieValue; } /** The string prefixing rewritten cookies. */ protected String getCookieNamePrefix(String name) { return "!Proxy!" + getServletConfig().getServletName(); } /** * Copy response body data (the entity) from the proxy to the servlet * client. */ protected void copyResponseEntity(HttpResponse proxyResponse, HttpServletResponse servletResponse, HttpRequest proxyRequest, HttpServletRequest servletRequest) throws IOException { HttpEntity entity = proxyResponse.getEntity(); if (entity != null) { OutputStream servletOutputStream = servletResponse.getOutputStream(); if (entity.getContentType() != null && entity.getContentType().getValue() != null && entity.getContentType().getValue().contains("application/json")) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); IOUtils.copy(entity.getContent(), baos); String response = new String(baos.toByteArray()); String targetURL = getConfigParam("targetUri"); String proxyURL = getConfigParam("proxyURL"); System.out.println("replacing " + targetURL + " by " + proxyURL); String replacedJSON = response.replaceAll(targetURL, proxyURL); IOUtils.write(replacedJSON, servletOutputStream); } else { entity.writeTo(servletOutputStream); } } } /** * Reads the request URI from {@code servletRequest} and rewrites it, * considering targetUri. It's used to make the new request. */ protected String rewriteUrlFromRequest(HttpServletRequest servletRequest) { StringBuilder uri = new StringBuilder(500); uri.append(getTargetUri(servletRequest)); // Handle the path given to the servlet if (servletRequest.getPathInfo() != null) {// ex: /my/path.html uri.append(encodeUriQuery(servletRequest.getPathInfo())); } // Handle the query string & fragment String queryString = servletRequest.getQueryString();// ex:(following // '?'): // name=value&foo=bar#fragment String fragment = null; // split off fragment from queryString, updating queryString if found if (queryString != null) { int fragIdx = queryString.indexOf('#'); if (fragIdx >= 0) { fragment = queryString.substring(fragIdx + 1); queryString = queryString.substring(0, fragIdx); } } queryString = rewriteQueryStringFromRequest(servletRequest, queryString); if (queryString != null && queryString.length() > 0) { uri.append('?'); uri.append(encodeUriQuery(queryString)); } if (doSendUrlFragment && fragment != null) { uri.append('#'); uri.append(encodeUriQuery(fragment)); } return uri.toString(); } protected String rewriteQueryStringFromRequest(HttpServletRequest servletRequest, String queryString) { return queryString; } /** * For a redirect response from the target server, this translates * {@code theUrl} to redirect to and translates it to one the original * client can use. */ protected String rewriteUrlFromResponse(HttpServletRequest servletRequest, String theUrl) { // TODO document example paths final String targetUri = getTargetUri(servletRequest); if (theUrl.startsWith(targetUri)) { /*- * The URL points back to the back-end server. * Instead of returning it verbatim we replace the target path with our * source path in a way that should instruct the original client to * request the URL pointed through this Proxy. * We do this by taking the current request and rewriting the path part * using this servlet's absolute path and the path from the returned URL * after the base target URL. */ StringBuffer curUrl = servletRequest.getRequestURL();// no query int pos; // Skip the protocol part if ((pos = curUrl.indexOf("://")) >= 0) { // Skip the authority part // + 3 to skip the separator between protocol and authority if ((pos = curUrl.indexOf("/", pos + 3)) >= 0) { // Trim everything after the authority part. curUrl.setLength(pos); } } // Context path starts with a / if it is not blank curUrl.append(servletRequest.getContextPath()); // Servlet path starts with a / if it is not blank curUrl.append(servletRequest.getServletPath()); curUrl.append(theUrl, targetUri.length(), theUrl.length()); theUrl = curUrl.toString(); } return theUrl; } /** The target URI as configured. Not null. */ public String getTargetUri() { return targetUri; } /** * Encodes characters in the query or fragment part of the URI. * * <p> * Unfortunately, an incoming URI sometimes has characters disallowed by the * spec. HttpClient insists that the outgoing proxied request has a valid * URI because it uses Java's {@link URI}. To be more forgiving, we must * escape the problematic characters. See the URI class for the spec. * * @param in * example: name=value&foo=bar#fragment */ protected static CharSequence encodeUriQuery(CharSequence in) { // Note that I can't simply use URI.java to encode because it will // escape pre-existing escaped things. StringBuilder outBuf = null; Formatter formatter = null; for (int i = 0; i < in.length(); i++) { char c = in.charAt(i); boolean escape = true; if (c < 128) { if (asciiQueryChars.get((int) c)) { escape = false; } } else if (!Character.isISOControl(c) && !Character.isSpaceChar(c)) {// not-ascii escape = false; } if (!escape) { if (outBuf != null) outBuf.append(c); } else { // escape if (outBuf == null) { outBuf = new StringBuilder(in.length() + 5 * 3); outBuf.append(in, 0, i); formatter = new Formatter(outBuf); } // leading %, 0 padded, width 2, capital hex formatter.format("%%%02X", (int) c);// TODO } } return outBuf != null ? outBuf : in; } protected static final BitSet asciiQueryChars; static { char[] c_unreserved = "_-!.~'()*".toCharArray();// plus alphanum char[] c_punct = ",;:$&+=".toCharArray(); char[] c_reserved = "?/[]@".toCharArray();// plus punct asciiQueryChars = new BitSet(128); for (char c = 'a'; c <= 'z'; c++) asciiQueryChars.set((int) c); for (char c = 'A'; c <= 'Z'; c++) asciiQueryChars.set((int) c); for (char c = '0'; c <= '9'; c++) asciiQueryChars.set((int) c); for (char c : c_unreserved) asciiQueryChars.set((int) c); for (char c : c_punct) asciiQueryChars.set((int) c); for (char c : c_reserved) asciiQueryChars.set((int) c); asciiQueryChars.set((int) '%');// leave existing percent escapes in // place } }