This project has retired. For details please refer to its Attic page.
BrowseServlet xref

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.chemistry.opencmis.browser;
20  
21  import java.io.BufferedInputStream;
22  import java.io.BufferedOutputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.OutputStream;
26  import java.io.PrintWriter;
27  import java.net.HttpURLConnection;
28  import java.net.URL;
29  import java.net.URLDecoder;
30  import java.util.Enumeration;
31  import java.util.HashMap;
32  import java.util.Map;
33  
34  import javax.servlet.ServletConfig;
35  import javax.servlet.ServletException;
36  import javax.servlet.http.HttpServlet;
37  import javax.servlet.http.HttpServletRequest;
38  import javax.servlet.http.HttpServletResponse;
39  import javax.xml.parsers.DocumentBuilder;
40  import javax.xml.parsers.DocumentBuilderFactory;
41  import javax.xml.transform.Result;
42  import javax.xml.transform.Source;
43  import javax.xml.transform.Transformer;
44  import javax.xml.transform.TransformerFactory;
45  import javax.xml.transform.dom.DOMSource;
46  import javax.xml.transform.stream.StreamResult;
47  import javax.xml.transform.stream.StreamSource;
48  
49  import org.apache.commons.logging.Log;
50  import org.apache.commons.logging.LogFactory;
51  import org.w3c.dom.Document;
52  
53  /**
54   * CMIS Browser Servlet.
55   */
56  public class BrowseServlet extends HttpServlet {
57  
58      private static final long serialVersionUID = 1L;
59  
60      private static final Log log = LogFactory.getLog(BrowseServlet.class);
61  
62      private static final String CONTEXT_PREFIX = "{ctx}";
63      private static final String PARAM_URL = "url";
64      private static final String PARAM_OVERRIDE_STYLESHEET = "overrideStylesheet";
65      private static final int PARAM_URL_MIN_LEN = PARAM_URL.length() + "=".length() + "http".length() + "://".length()
66              + 1;
67      private static final String INIT_PARAM_AUXROOT = "auxroot";
68      private static final String INIT_PARAM_ALLOW = "allow";
69      private static final String INIT_PARAM_STYLESHEET = "stylesheet:";
70      private static final String INIT_PARAM_OVERRIDE_STYLESHEET = "override-stylesheet:";
71  
72      private static final int BUFFER_SIZE = 64 * 1024;
73  
74      private String fAuxRoot = "";
75      private String fAllow = ".*";
76      private Map<String, Source> fStyleSheets;
77      private Map<String, Source> fOverrideStyleSheets;
78  
79      /**
80       * Initializes the browser servlet.
81       */
82      @SuppressWarnings("unchecked")
83      @Override
84      public void init(ServletConfig config) throws ServletException {
85          fStyleSheets = new HashMap<String, Source>();
86          fOverrideStyleSheets = new HashMap<String, Source>();
87  
88          DocumentBuilder builder = null;
89          try {
90              DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
91              builderFactory.setNamespaceAware(true);
92              builder = builderFactory.newDocumentBuilder();
93          } catch (Exception e) {
94              e.printStackTrace();
95              return;
96          }
97  
98          Enumeration<String> initParams = config.getInitParameterNames();
99          while (initParams.hasMoreElements()) {
100             String param = initParams.nextElement();
101             String stylesheetKey = null;
102             boolean isOverride = false;
103 
104             if (param.startsWith(INIT_PARAM_STYLESHEET)) {
105                 stylesheetKey = param.substring(INIT_PARAM_STYLESHEET.length());
106             } else if (param.startsWith(INIT_PARAM_OVERRIDE_STYLESHEET)) {
107                 stylesheetKey = param.substring(INIT_PARAM_OVERRIDE_STYLESHEET.length());
108                 isOverride = true;
109             }
110 
111             if (stylesheetKey != null) {
112                 String stylesheetFileName = config.getInitParameter(param);
113                 InputStream stream = config.getServletContext().getResourceAsStream(stylesheetFileName);
114                 if (stream != null) {
115                     try {
116                         Document xslDoc = builder.parse(stream);
117                         addStylesheet(stylesheetKey, new DOMSource(xslDoc), isOverride);
118 
119                         log.info("Stylesheet: '" + stylesheetKey + "' -> '" + stylesheetFileName + "'");
120                     } catch (Exception e) {
121                         e.printStackTrace();
122                     }
123                 }
124             }
125         }
126 
127         String initAuxRoot = config.getInitParameter(INIT_PARAM_AUXROOT);
128         if (initAuxRoot != null) {
129             fAuxRoot = initAuxRoot;
130             log.info("Auxiliary root: " + fAuxRoot);
131         }
132 
133         String initAllow = config.getInitParameter(INIT_PARAM_ALLOW);
134         if (initAllow != null) {
135             fAllow = initAllow;
136             log.info("Allow pattern: " + fAllow);
137         }
138     }
139 
140     /**
141      * Handles GET requests.
142      */
143     @Override
144     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
145         String browseUrl = null;
146         String overrideStylesheet = null;
147 
148         // The url parameter must come first!
149         String queryString = req.getQueryString();
150 
151         if ((queryString != null) && (queryString.startsWith(PARAM_URL))) {
152             int urlEnd = queryString.indexOf(PARAM_OVERRIDE_STYLESHEET + "=");
153             if (urlEnd == -1) {
154                 urlEnd = queryString.length();
155             } else {
156                 overrideStylesheet = queryString.substring(urlEnd + PARAM_OVERRIDE_STYLESHEET.length() + 1);
157                 --urlEnd;
158             }
159 
160             browseUrl = queryString.substring(PARAM_URL.length() + 1, urlEnd);
161 
162             if (browseUrl.length() < PARAM_URL_MIN_LEN) {
163                 browseUrl = null;
164             }
165         }
166 
167         if (browseUrl == null) {
168             printInput(req, resp);
169             return;
170         }
171 
172         doBrowse(req, resp, browseUrl, overrideStylesheet);
173     }
174 
175     /**
176      * Main method of the browser.
177      */
178     protected void doBrowse(HttpServletRequest req, HttpServletResponse resp, String browseUrl,
179             String overrideStylesheet) throws ServletException, IOException {
180         // check if decoding is necessary
181         // (if the char after 'http' or 'https' is not a colon, then it must be
182         // decoded)
183         if (browseUrl.charAt(4) != ':' && browseUrl.charAt(5) != ':') {
184             browseUrl = URLDecoder.decode(browseUrl, "UTF-8");
185         }
186 
187         // check URL
188         if (!browseUrl.matches(fAllow)) {
189             printError(req, resp, "Prohibited URL!", null);
190             return;
191         }
192 
193         try {
194             // get content
195             URL url = new URL(browseUrl);
196             HttpURLConnection conn = (HttpURLConnection) url.openConnection();
197             conn.setDoInput(true);
198             conn.setDoOutput(false);
199             conn.setRequestMethod("GET");
200             String authHeader = req.getHeader("Authorization");
201             if (authHeader != null) {
202                 conn.setRequestProperty("Authorization", authHeader);
203             }
204             conn.connect();
205 
206             // ask for login
207             if (conn.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
208                 resp.setHeader("WWW-Authenticate", conn.getHeaderField("WWW-Authenticate"));
209                 resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization Required");
210                 return;
211             }
212 
213             // debug messages
214             if (log.isDebugEnabled()) {
215                 log.debug("'" + browseUrl + "' -> '" + conn.getContentType() + "'");
216             }
217 
218             // find stylesheet
219             Source stylesheet = getStylesheet(conn.getContentType(), overrideStylesheet);
220 
221             OutputStream out = null;
222             InputStream in = new BufferedInputStream(conn.getInputStream(), BUFFER_SIZE);
223 
224             if (stylesheet == null) {
225                 // no stylesheet found -> conduct content
226                 resp.setContentType(conn.getContentType());
227                 out = new BufferedOutputStream(resp.getOutputStream(), BUFFER_SIZE);
228 
229                 byte[] buffer = new byte[BUFFER_SIZE];
230                 int b;
231                 while ((b = in.read(buffer)) > -1) {
232                     out.write(buffer, 0, b);
233                 }
234             } else {
235                 // apply stylesheet
236                 TransformerFactory f = TransformerFactory.newInstance();
237                 Transformer t = f.newTransformer(stylesheet);
238                 t.setParameter("browseUrl", getServletUrl(req) + "?" + PARAM_URL + "=");
239                 t.setParameter("auxRoot", getAuxRoot(req, fAuxRoot));
240                 t.setParameter("browseOverrideStylesheet", "&" + PARAM_OVERRIDE_STYLESHEET + "=");
241 
242                 resp.setContentType("text/html");
243                 out = new BufferedOutputStream(resp.getOutputStream(), BUFFER_SIZE);
244 
245                 Source s = new StreamSource(in);
246                 Result r = new StreamResult(out);
247                 t.transform(s, r);
248             }
249 
250             try {
251                 out.flush();
252                 out.close();
253             } catch (Exception e) {
254             }
255 
256             try {
257                 in.close();
258             } catch (Exception e) {
259             }
260         } catch (Exception e) {
261             printError(req, resp, e.getMessage(), e);
262             return;
263         }
264     }
265 
266     // ---- utilities ----
267 
268     /**
269      * Assigns a stylesheet to a content type.
270      */
271     private void addStylesheet(String contentType, Source source, boolean override) {
272         if ((contentType == null) || (source == null)) {
273             return;
274         }
275 
276         if (override) {
277             fOverrideStyleSheets.put(contentType.trim().toLowerCase(), source);
278         } else {
279             fStyleSheets.put(contentType.trim().toLowerCase(), source);
280         }
281     }
282 
283     /**
284      * Returns the stylesheet for the given override stylesheet, if there is is
285      * not override stylesheet given then the stylesheet for the given content
286      * type or <code>null</code> if no stylesheet is assigned to content type.
287      */
288     private Source getStylesheet(String contentType, String overrideStylesheet) {
289         if (contentType == null) {
290             return null;
291         }
292 
293         Source source = null;
294 
295         // First check if there is an override given and it has a match.
296         if (overrideStylesheet != null && overrideStylesheet.length() > 0) {
297             source = fOverrideStyleSheets.get(overrideStylesheet);
298         }
299 
300         // If there is not match then check the content type map.
301         if (source == null) {
302             String[] ctp = contentType.trim().toLowerCase().split(";");
303 
304             StringBuilder match = new StringBuilder();
305             int i = 0;
306             while (source == null && i < ctp.length) {
307                 if (i > 0) {
308                     match.append(";");
309                 }
310                 match.append(ctp[i]);
311                 source = fStyleSheets.get(match.toString());
312                 i++;
313             }
314         }
315 
316         return source;
317     }
318 
319     /**
320      * Returns the context URL of this servlet.
321      */
322     private String getContextUrl(HttpServletRequest request) {
323         String scheme = request.getScheme();
324         int port = request.getServerPort();
325 
326         if ("http".equals(scheme) && (port == 80)) {
327             port = -1;
328         }
329         if ("https".equals(scheme) && (port == 443)) {
330             port = -1;
331         }
332 
333         return scheme + "://" + request.getServerName() + (port > 0 ? ":" + port : "") + request.getContextPath();
334     }
335 
336     /**
337      * Returns the URL of this servlet.
338      */
339     private String getServletUrl(HttpServletRequest request) {
340         return getContextUrl(request) + request.getServletPath();
341     }
342 
343     /**
344      * Returns the root URL of auxiliary files.
345      */
346     private String getAuxRoot(HttpServletRequest request, String auxRoot) {
347         if (auxRoot == null) {
348             return getContextUrl(request);
349         } else if (auxRoot.startsWith(CONTEXT_PREFIX)) {
350             return getContextUrl(request) + auxRoot.substring(CONTEXT_PREFIX.length());
351         } else {
352             return auxRoot;
353         }
354     }
355 
356     // --- HTML methods ----
357 
358     /**
359      * Prints a HTML header with styles.
360      */
361     private void printHeader(PrintWriter pw, String title) {
362         pw.print("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"");
363         pw.println("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
364         pw.println("<html xmlns=\"http://www.w3.org/1999/xhtml\">");
365         pw.println("<head>");
366         pw.println("<title>" + title + "</title>");
367         pw.println("<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\" />");
368         pw.println("<style type=\"text/css\">");
369         pw.println("body { font-family: arial,sans-serif; font-size: 10pt; }");
370         pw.println("div.box { background-color: #f6f1de; margin-top: 10px;"
371                 + " margin-bottom: 10px; margin-left: 0px; margin-right: 0px;"
372                 + " padding: 5px; border-style: solid; border-width: 1px; border-color: #888a85; }");
373         pw.println("</style>");
374         pw.println("</head>");
375         pw.println("<body>");
376     }
377 
378     /**
379      * Prints a HTML footer.
380      */
381     private void printFooter(PrintWriter pw) {
382         pw.println("</body>");
383         pw.println("</html>");
384     }
385 
386     /**
387      * Prints a HTML error message.
388      */
389     private void printError(HttpServletRequest req, HttpServletResponse resp, String message, Exception e)
390             throws IOException {
391         resp.setContentType("text/html;charset=utf-8");
392         PrintWriter pw = resp.getWriter();
393 
394         printHeader(pw, "Error");
395         pw.println("<div class=\"box\">");
396         pw.println("<h3>" + message + "</h3>");
397 
398         if (e != null) {
399             pw.print("<pre>");
400             e.printStackTrace(pw);
401             pw.println("</pre>");
402         }
403 
404         pw.println("</div>");
405         printFooter(pw);
406     }
407 
408     /**
409      * Prints an HTML input box.
410      */
411     private void printInput(HttpServletRequest req, HttpServletResponse resp) throws IOException {
412         resp.setContentType("text/html;charset=utf-8");
413         PrintWriter pw = resp.getWriter();
414 
415         printHeader(pw, "OpenCMIS Browser");
416         pw.println("<img src=\"" + getAuxRoot(req, fAuxRoot) + "cmis.png\" " + "style=\"float: right;\" />");
417         pw.println("<h1 style=\"font-family: Georgia;\">OpenCMIS Browser</h1>");
418         pw.println("<div class=\"box\">");
419         pw.println("<form action=\"\" method=\"GET\">");
420         pw.println("CMIS AtomPub URL: ");
421         pw.println("<input name=\"url\" type=\"text\" size=\"100\" value=\"\"/>");
422         pw.println("<input type=\"submit\" value=\" GO \"/>");
423         pw.println("</form>");
424         pw.println("</div>");
425         printFooter(pw);
426     }
427 }