blob: 00337f4e608383b6270ca654b805e34cdea86b9d [file] [log] [blame]
/*
* 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.chemistry.opencmis.browser;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
/**
* CMIS Browser Servlet.
*/
public class BrowseServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final Logger LOG = LoggerFactory.getLogger(BrowseServlet.class);
private static final String CONTEXT_PREFIX = "{ctx}";
private static final String PARAM_URL = "url";
private static final String PARAM_OVERRIDE_STYLESHEET = "overrideStylesheet";
private static final int PARAM_URL_MIN_LEN = PARAM_URL.length() + "=".length() + "http".length() + "://".length()
+ 1;
private static final String INIT_PARAM_AUXROOT = "auxroot";
private static final String INIT_PARAM_ALLOW = "allow";
private static final String INIT_PARAM_STYLESHEET = "stylesheet:";
private static final String INIT_PARAM_OVERRIDE_STYLESHEET = "override-stylesheet:";
private static final int BUFFER_SIZE = 64 * 1024;
private String fAuxRoot = "";
private String fAllow = ".*";
private Map<String, Source> fStyleSheets;
private Map<String, Source> fOverrideStyleSheets;
/**
* Initializes the browser servlet.
*/
@SuppressWarnings("unchecked")
@Override
public void init(ServletConfig config) throws ServletException {
fStyleSheets = new HashMap<String, Source>();
fOverrideStyleSheets = new HashMap<String, Source>();
DocumentBuilder builder = null;
try {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
builderFactory.setNamespaceAware(true);
builder = builderFactory.newDocumentBuilder();
} catch (Exception e) {
LOG.error(e.getMessage(), e);
return;
}
Enumeration<String> initParams = config.getInitParameterNames();
while (initParams.hasMoreElements()) {
String param = initParams.nextElement();
String stylesheetKey = null;
boolean isOverride = false;
if (param.startsWith(INIT_PARAM_STYLESHEET)) {
stylesheetKey = param.substring(INIT_PARAM_STYLESHEET.length());
} else if (param.startsWith(INIT_PARAM_OVERRIDE_STYLESHEET)) {
stylesheetKey = param.substring(INIT_PARAM_OVERRIDE_STYLESHEET.length());
isOverride = true;
}
if (stylesheetKey != null) {
String stylesheetFileName = config.getInitParameter(param);
InputStream stream = config.getServletContext().getResourceAsStream(stylesheetFileName);
if (stream != null) {
try {
Document xslDoc = builder.parse(stream);
addStylesheet(stylesheetKey, new DOMSource(xslDoc), isOverride);
LOG.info("Stylesheet: '" + stylesheetKey + "' -> '" + stylesheetFileName + "'");
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
}
}
}
String initAuxRoot = config.getInitParameter(INIT_PARAM_AUXROOT);
if (initAuxRoot != null) {
fAuxRoot = initAuxRoot;
LOG.info("Auxiliary root: " + fAuxRoot);
}
String initAllow = config.getInitParameter(INIT_PARAM_ALLOW);
if (initAllow != null) {
fAllow = initAllow;
LOG.info("Allow pattern: " + fAllow);
}
}
/**
* Handles GET requests.
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String browseUrl = null;
String overrideStylesheet = null;
// The url parameter must come first!
String queryString = req.getQueryString();
if ((queryString != null) && (queryString.startsWith(PARAM_URL))) {
int urlEnd = queryString.indexOf(PARAM_OVERRIDE_STYLESHEET + "=");
if (urlEnd == -1) {
urlEnd = queryString.length();
} else {
overrideStylesheet = queryString.substring(urlEnd + PARAM_OVERRIDE_STYLESHEET.length() + 1);
--urlEnd;
}
browseUrl = queryString.substring(PARAM_URL.length() + 1, urlEnd);
if (browseUrl.length() < PARAM_URL_MIN_LEN) {
browseUrl = null;
}
}
if (browseUrl == null) {
printInput(req, resp);
return;
}
doBrowse(req, resp, browseUrl, overrideStylesheet);
}
/**
* Main method of the browser.
*/
protected void doBrowse(HttpServletRequest req, HttpServletResponse resp, String browseUrl,
String overrideStylesheet) throws ServletException, IOException {
// check if decoding is necessary
// (if the char after 'http' or 'https' is not a colon, then it must be
// decoded)
if (browseUrl.charAt(4) != ':' && browseUrl.charAt(5) != ':') {
browseUrl = URLDecoder.decode(browseUrl, "UTF-8");
}
// check URL
if (!browseUrl.matches(fAllow)) {
printError(req, resp, "Prohibited URL!", null);
return;
}
try {
// get content
URL url = new URL(browseUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true);
conn.setDoOutput(false);
conn.setRequestMethod("GET");
String authHeader = req.getHeader("Authorization");
if (authHeader != null) {
conn.setRequestProperty("Authorization", authHeader);
}
conn.connect();
// ask for login
if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
resp.setHeader("WWW-Authenticate", conn.getHeaderField("WWW-Authenticate"));
resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization Required");
return;
}
// debug messages
if (LOG.isDebugEnabled()) {
LOG.debug("'" + browseUrl + "' -> '" + conn.getContentType() + "'");
}
// find stylesheet
Source stylesheet = getStylesheet(conn.getContentType(), overrideStylesheet);
OutputStream out = null;
InputStream in = new BufferedInputStream(conn.getInputStream(), BUFFER_SIZE);
if (stylesheet == null) {
// no stylesheet found -> conduct content
resp.setContentType(conn.getContentType());
out = new BufferedOutputStream(resp.getOutputStream(), BUFFER_SIZE);
byte[] buffer = new byte[BUFFER_SIZE];
int b;
while ((b = in.read(buffer)) > -1) {
out.write(buffer, 0, b);
}
} else {
// apply stylesheet
TransformerFactory f = TransformerFactory.newInstance();
Transformer t = f.newTransformer(stylesheet);
t.setParameter("browseUrl", getServletUrl(req) + "?" + PARAM_URL + "=");
t.setParameter("auxRoot", getAuxRoot(req, fAuxRoot));
t.setParameter("browseOverrideStylesheet", "&" + PARAM_OVERRIDE_STYLESHEET + "=");
resp.setContentType("text/html");
out = new BufferedOutputStream(resp.getOutputStream(), BUFFER_SIZE);
Source s = new StreamSource(in);
Result r = new StreamResult(out);
t.transform(s, r);
}
try {
out.flush();
out.close();
} catch (Exception e) {
// ignore, there isn't anything we can do
}
try {
in.close();
} catch (Exception e) {
// ignore, there isn't anything we can do
}
} catch (Exception e) {
printError(req, resp, e.getMessage(), e);
return;
}
}
// ---- utilities ----
/**
* Assigns a stylesheet to a content type.
*/
private void addStylesheet(String contentType, Source source, boolean override) {
if ((contentType == null) || (source == null)) {
return;
}
if (override) {
fOverrideStyleSheets.put(contentType.trim().toLowerCase(), source);
} else {
fStyleSheets.put(contentType.trim().toLowerCase(), source);
}
}
/**
* Returns the stylesheet for the given override stylesheet, if there is is
* not override stylesheet given then the stylesheet for the given content
* type or <code>null</code> if no stylesheet is assigned to content type.
*/
private Source getStylesheet(String contentType, String overrideStylesheet) {
if (contentType == null) {
return null;
}
Source source = null;
// First check if there is an override given and it has a match.
if (overrideStylesheet != null && overrideStylesheet.length() > 0) {
source = fOverrideStyleSheets.get(overrideStylesheet);
}
// If there is not match then check the content type map.
if (source == null) {
String[] ctp = contentType.trim().toLowerCase().split(";");
StringBuilder match = new StringBuilder();
int i = 0;
while (source == null && i < ctp.length) {
if (i > 0) {
match.append(";");
}
match.append(ctp[i]);
source = fStyleSheets.get(match.toString());
i++;
}
}
return source;
}
/**
* Returns the context URL of this servlet.
*/
private String getContextUrl(HttpServletRequest request) {
String scheme = request.getScheme();
int port = request.getServerPort();
if ("http".equals(scheme) && (port == 80)) {
port = -1;
}
if ("https".equals(scheme) && (port == 443)) {
port = -1;
}
return scheme + "://" + request.getServerName() + (port > 0 ? ":" + port : "") + request.getContextPath();
}
/**
* Returns the URL of this servlet.
*/
private String getServletUrl(HttpServletRequest request) {
return getContextUrl(request) + request.getServletPath();
}
/**
* Returns the root URL of auxiliary files.
*/
private String getAuxRoot(HttpServletRequest request, String auxRoot) {
if (auxRoot == null) {
return getContextUrl(request);
} else if (auxRoot.startsWith(CONTEXT_PREFIX)) {
return getContextUrl(request) + auxRoot.substring(CONTEXT_PREFIX.length());
} else {
return auxRoot;
}
}
// --- HTML methods ----
/**
* Prints a HTML header with styles.
*/
private void printHeader(PrintWriter pw, String title) {
pw.print("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"");
pw.println("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
pw.println("<html xmlns=\"http://www.w3.org/1999/xhtml\">");
pw.println("<head>");
pw.println("<title>" + title + "</title>");
pw.println("<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />");
pw.println("<style type=\"text/css\">");
pw.println("body { font-family: arial,sans-serif; font-size: 10pt; }");
pw.println("div.box { background-color: #f6f1de; margin-top: 10px;"
+ " margin-bottom: 10px; margin-left: 0px; margin-right: 0px;"
+ " padding: 5px; border-style: solid; border-width: 1px; border-color: #888a85; }");
pw.println("</style>");
pw.println("</head>");
pw.println("<body>");
}
/**
* Prints a HTML footer.
*/
private void printFooter(PrintWriter pw) {
pw.println("</body>");
pw.println("</html>");
}
/**
* Prints a HTML error message.
*/
private void printError(HttpServletRequest req, HttpServletResponse resp, String message, Exception e)
throws IOException {
resp.setContentType("text/html;charset=utf-8");
PrintWriter pw = resp.getWriter();
printHeader(pw, "Error");
pw.println("<div class=\"box\">");
pw.println("<h3>" + message + "</h3>");
if (e != null) {
pw.print("<pre>");
e.printStackTrace(pw);
pw.println("</pre>");
}
pw.println("</div>");
printFooter(pw);
}
/**
* Prints an HTML input box.
*/
private void printInput(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.setContentType("text/html;charset=utf-8");
PrintWriter pw = resp.getWriter();
printHeader(pw, "OpenCMIS Browser");
pw.println("<img src=\"" + getAuxRoot(req, fAuxRoot) + "cmis.png\" " + "style=\"float: right;\" />");
pw.println("<h1 style=\"font-family: Georgia;\">OpenCMIS Browser</h1>");
pw.println("<div class=\"box\">");
pw.println("<form action=\"\" method=\"GET\">");
pw.println("CMIS AtomPub URL: ");
pw.println("<input name=\"url\" type=\"text\" size=\"100\" value=\"\"/>");
pw.println("<input type=\"submit\" value=\" GO \"/>");
pw.println("</form>");
pw.println("</div>");
printFooter(pw);
}
}