/**
*
* Copyright 2015 sourceforge.
*
* 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.gwtopenmaps.openlayers.server.cas;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.AssertionImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Giuseppe La Scaleia - CNR IMAA geoSDI Group
* @email giuseppe.lascaleia@geosdi.org
*/
public class CasGwtOpenLayersProxyServlet extends HttpServlet {
private static final long serialVersionUID = 6217076116826912811L;
private static final Logger logger = LoggerFactory.getLogger(
CasGwtOpenLayersProxyServlet.class);
public static final Pattern PATTERN = Pattern.compile("^[0-9]+$");
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
processRequest(request, response);
}
private void processRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
HttpURLConnection connection = null;
InputStream istream = null; // input to proxy
OutputStream ostream = null; // output from proxy
InputStream connectionIstream = null; // output for the target is input for the connection
OutputStream connectionOstream = null; // input for the target is output for the connection
String remoteHost = request.getRemoteHost(); // get host address of client - for checking allowedHosts
boolean allowedHost = isAllowedHost(remoteHost); // The allowedHosts are the hosts that are allowed to use the Open Proxy.
try {
// easy way to ignore case of param?
if ((request.getParameter("targetURL") != null) && (request.getParameter(
"targetURL") != "") && allowedHost) {
// HTTPUrlConnection looks at http.proxyHost and http.proxyPort system properties.
// Make sure these properties are set these if you are behind a proxy.
// step 1: initialize
String requestMethod = request.getMethod();
Assertion casAssertion = null;
if (request != null) {
casAssertion = (AssertionImpl) request.getSession().getAttribute(
AbstractCasFilter.CONST_CAS_ASSERTION);
}
String url = request.getParameter("targetURL");
URL targetURL = new URL(
appendProxyTicketToURL(url, casAssertion));
connection = (HttpURLConnection) targetURL.openConnection();
final String connectTimeout = getInitParameter("connectTimeout");
if (connectTimeout != null) {
if (PATTERN.matcher(connectTimeout.trim()).matches()) {
connection.setConnectTimeout(Integer.parseInt(
connectTimeout.trim()));
} else {
throw new IllegalStateException(MessageFormat.format(
"connectTimeout init-param {0} is non-numeric",
connectTimeout));
}
}
final String readTimeout = getInitParameter("readTimeout");
if (readTimeout != null) {
if (PATTERN.matcher(readTimeout.trim()).matches()) {
connection.setReadTimeout(Integer.parseInt(
readTimeout.trim()));
} else {
throw new IllegalStateException(MessageFormat.format(
"readTimeout init-param {0} is non-numeric",
readTimeout));
}
}
connection.setRequestMethod(requestMethod);
transferHTTPRequestHeaders(connection, request);
// step 2: proxy requests
if (requestMethod.equals("GET")) {
// default for setDoInput is true
connectionIstream = connection.getInputStream();
}
if (requestMethod.equals("POST")) {
transferHTTPRequestHeadersForPOST(connection, request);
int clength = request.getContentLength(); // clength is for checking if there is a POST body. Is that sufficient?
if (clength > 0) {
istream = request.getInputStream();
connection.setDoOutput(true); // for POST we need to write to connection
connection.setRequestProperty("Content-Length",
Integer.toString(clength)); // only valid for POST request
connectionOstream = connection.getOutputStream();
// copy the request body to remote outputStream
copy(istream, connectionOstream);
}
connectionIstream = connection.getInputStream();
}
// step 3: return output
// can output be the same for GET/POST? or different return headers?
// servlet may return 3 things: status code, response headers, response body
// status code and headers have to be set before response body
response.setContentType(connection.getContentType());
ostream = response.getOutputStream();
copy(connectionIstream, ostream);
}
// if not targetURL send page that targetURL is required param
} catch (Exception e) {
response.setStatus(500); // what will user get? default page for response code
// WMS/WFS have specific responses to errors
// response.getWriter();//will writing custom result help
e.printStackTrace();
} finally {
if (istream != null) {
istream.close();
}
if (ostream != null) {
ostream.close();
}
if (connectionIstream != null) {
connectionIstream.close();
}
if (connectionOstream != null) {
connectionOstream.close();
}
}
}
private void copy(InputStream istream,
OutputStream ostream) throws Exception {
int bufferSize = 4 * 4 * 1024; // same buffer size as in Jetty utils (2*8192)
byte[] buffer = new byte[bufferSize];
int read;
while ((read = istream.read(buffer)) != -1) {
ostream.write(buffer, 0, read);
}
}
private void transferHTTPRequestHeaders(HttpURLConnection connection,
HttpServletRequest request) {
// TODO make sure all headers are copied to target, see for HTTP headers http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
// Do request.getProperties to get request properties
if (request.getHeader("Accept") != null) {
connection.setRequestProperty("Accept", request.getHeader("Accept"));
}
if (request.getHeader("Accept-Charset") != null) {
connection.setRequestProperty("Accept-Charset", request.getHeader(
"Accept-Charset"));
}
if (request.getHeader("Accept-Encoding") != null) {
// TODO browsers accept gzipped, should proxy accept gzip and how to handle it?
// connection.setRequestProperty("Accept-Encoding", request.getHeader("Accept-Encoding"));
}
if (request.getHeader("Authorization") != null) {
connection.setRequestProperty("Authorization", request.getHeader(
"Authorization"));
}
if (request.getHeader("Connection") != null) {
// TODO HTTP/1.1 proxies MUST parse the Connection header field before a message is forwarded and, for each connection-token in this field, remove any header field(s) from the message with the same name as the connection-token.
// connection.setRequestProperty("Connection", request.getHeader("Connection"));
}
// set de-facto standard proxy headers (x-forwarded-for, others?s)
if (request.getHeader("X-Forwarded-For") != null) {
connection.setRequestProperty("X-Forwarded-For", request.getHeader(
"X-Forwarded-For")); // TODO append IP proxy
} else {
connection.setRequestProperty("X-Forwarded-For",
request.getRemoteAddr()); // TODO append IP proxy
}
}
private void transferHTTPRequestHeadersForPOST(HttpURLConnection connection,
HttpServletRequest request) {
if (request.getHeader("Content-Type") != null) {
connection.setRequestProperty("Content-Type",
request.getContentType());
} else {
// throw exception?
}
}
private boolean isAllowedHost(String remoteHost) {
// TODO checking of host
return true;
}
String appendProxyTicketToURL(String url, Assertion casAssertion) {
if (casAssertion == null) {
return url;
}
String proxyTicket = casAssertion.getPrincipal().getProxyTicketFor(url);
if (proxyTicket == null || proxyTicket.isEmpty()) {
return url;
}
try {
char parameterSeparator = '?';
if (url.contains("?")) {
parameterSeparator = '&';
}
url += parameterSeparator + "ticket=" + URLEncoder.
encode(proxyTicket, "UTF-8");
} catch (UnsupportedEncodingException ex) {
logger.error("Error on encoding proxy ticket {}", ex);
}
return url;
}
}