You are viewing a plain text version of this content. The canonical link for it is here.
Posted to portalapps-dev@portals.apache.org by wo...@apache.org on 2010/02/04 16:41:48 UTC

svn commit: r906529 - in /portals/applications/gems/trunk/src/main/java/org/apache/portals/gems/servlet: ./ WebAppClasspathResourceServlet.java

Author: woonsan
Date: Thu Feb  4 15:41:48 2010
New Revision: 906529

URL: http://svn.apache.org/viewvc?rev=906529&view=rev
Log:
APA-29: Adding webapp classpath resource serving servlet.
TODO: Apply this to some portlet(s) such as googlemaps.

Added:
    portals/applications/gems/trunk/src/main/java/org/apache/portals/gems/servlet/
    portals/applications/gems/trunk/src/main/java/org/apache/portals/gems/servlet/WebAppClasspathResourceServlet.java   (with props)

Added: portals/applications/gems/trunk/src/main/java/org/apache/portals/gems/servlet/WebAppClasspathResourceServlet.java
URL: http://svn.apache.org/viewvc/portals/applications/gems/trunk/src/main/java/org/apache/portals/gems/servlet/WebAppClasspathResourceServlet.java?rev=906529&view=auto
==============================================================================
--- portals/applications/gems/trunk/src/main/java/org/apache/portals/gems/servlet/WebAppClasspathResourceServlet.java (added)
+++ portals/applications/gems/trunk/src/main/java/org/apache/portals/gems/servlet/WebAppClasspathResourceServlet.java Thu Feb  4 15:41:48 2010
@@ -0,0 +1,320 @@
+/*
+ * 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.portals.gems.servlet;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Serves static resource files from the web application's classloader resources.
+ * 
+ * <P>
+ * <EM>Note: This servlet could serve resources from the classpath insecurely.
+ * Please configure the init parameters for secure use as described below!</EM>
+ * </P>
+ * 
+ * <P>This servlet has the ability to configure resource base path,
+ * allowed resource path prefix and postfix. This needs some configuration, which is described below. </P> 
+ * 
+ * <pre>
+ * &lt;init-param&gt;
+ *   &lt;param-name&gt;resourceBasePath&lt;/param-name&gt; 
+ *   &lt;param-value&gt;/META-INF/resources&lt;/param-value&gt; 
+ * &lt;/init-param&gt; 
+ * &lt;init-param&gt;
+ *   &lt;param-name&gt;allowedResourcePathPrefixes&lt;/param-name&gt; 
+ *   &lt;param-value&gt;/refdocs/&lt;/param-value&gt; 
+ * &lt;/init-param&gt; 
+ * &lt;init-param&gt;
+ *   &lt;param-name&gt;allowedResourcePathPostfixes&lt;/param-name&gt; 
+ *   &lt;param-value&gt;.gif, .jpg, .jpeg, .png, .ico, .js, .css&lt;/param-value&gt;
+ * &lt;/init-param&gt;
+ * </pre>
+ * 
+ * <P> With the above configuration the following resource request path infos are valid: 
+ * <UL>
+ * <LI>/icons/logo.gif --&gt; classpath:/META-INF/resources/icons/logo.gif</LI>
+ * <LI>/javascript/init.js --&gt; classpath:/META-INF/resources/javascript/init.js</LI>
+ * <LI>/refdocs/spec.txt --&gt; classpath:/META-INF/resources/refdocs/spec.txt</LI>
+ * </UL>
+ * </P>
+ * 
+ * <P> However, the following resource request path infos are forbidden: 
+ * <UL>
+ * <LI>/icons/logo.bmp (because the path info doesn't start with <CODE>/refdocs/</CODE> and <CODE>.bmp</CODE> postfix is not allowed.)</LI>
+ * <LI>/designs/README (because the path info doesn't start with <CODE>/refdocs/</CODE> and no prefix does not match with this path info.</LI>
+ * </UL>
+ * </P>
+ * 
+ * @version $Id$
+ */
+public class WebAppClasspathResourceServlet extends HttpServlet
+{
+    
+    private static final long serialVersionUID = 1L;
+    
+    public static final String RESOURCE_BASE_PATH_PARAM = "resourceBasePath";
+    
+    public static final String ALLOWED_RESOURCE_PATH_PREFIXES_PARAM = "allowedResourcePathPrefixes";
+    
+    public static final String ALLOWED_RESOURCE_PATH_POSTFIXES_PARAM = "allowedResourcePathPostfixes";
+    
+    private static final int BUF_SIZE = 4096;
+    
+    private String resourceBasePath = "/META-INF/resources";
+    
+    private String[] allowedResourcePathPrefixes = new String[0];
+    
+    private String[] allowedResourcePathPostfixes = new String[] { ".gif", ".jpg", ".jpeg", ".png", ".ico", ".js", ".css" };
+    
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void init(ServletConfig config) throws ServletException
+    {
+        super.init(config);
+        
+        String param = config.getInitParameter(RESOURCE_BASE_PATH_PARAM);
+        if (param != null)
+        {
+            resourceBasePath = param.trim();
+        }
+        
+        param = config.getInitParameter(ALLOWED_RESOURCE_PATH_PREFIXES_PARAM);
+        if (param != null)
+        {
+            allowedResourcePathPrefixes = split(param, ", \t\r\n");
+        }
+        
+        param = config.getInitParameter(ALLOWED_RESOURCE_PATH_POSTFIXES_PARAM);
+        if (param != null)
+        {
+            allowedResourcePathPostfixes = split(param, ", \t\r\n");
+        }
+    }
+
+    @Override
+    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
+    {
+        String path = getResourcePath(request);
+        
+        if (path == null || "".equals(path.trim()))
+        {
+            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+        
+        if (!isDownloadableResource(path))
+        {
+            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+            return;
+        }
+        
+        String resourcePath = resourceBasePath + path;
+        URL resourceURL = null;
+        
+        try
+        {
+            resourceURL = Thread.currentThread().getContextClassLoader().getResource(resourcePath);
+        }
+        catch (Throwable th)
+        {
+        }
+        
+        if (resourceURL == null)
+        {
+            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+        
+        InputStream is = null;
+        BufferedInputStream bis = null;
+        ServletOutputStream sos = null;
+        BufferedOutputStream bos = null;
+        
+        try
+        {
+            long lastModified = 0L;
+            String protocol = resourceURL.getProtocol();
+            
+            if ("file".equals(protocol))
+            {
+                File resourceFile = new File(resourceURL.toURI());
+                if (resourceFile.isFile())
+                {
+                    lastModified = resourceFile.lastModified();
+                }
+            }
+            
+            if ("jar".equals(protocol))
+            {
+                String jarFileOnlyURL = resourceURL.getFile();
+                int offset = jarFileOnlyURL.indexOf('!');
+                if (offset != -1) {
+                    jarFileOnlyURL = jarFileOnlyURL.substring(0, offset);
+                }
+                File jarFile = new File(URI.create(jarFileOnlyURL));
+                if (jarFile.isFile())
+                {
+                    lastModified = jarFile.lastModified();
+                }
+            }
+            
+            if (lastModified > 0L)
+            {
+                response.setDateHeader("Last-Modified", lastModified);
+                long expires = (System.currentTimeMillis() - lastModified);
+                response.setDateHeader("Expires", expires + System.currentTimeMillis());
+                response.setHeader("Cache-Control", "max-age=" + (expires / 1000));
+            }
+            
+            String mimeType = getServletContext().getMimeType(resourcePath);
+            if (mimeType == null)
+            {
+                mimeType = "application/octet-stream";
+            }
+            
+            response.setContentType(mimeType);
+            
+            is = resourceURL.openStream();
+            bis = new BufferedInputStream(is);
+            sos = response.getOutputStream();
+            bos = new BufferedOutputStream(sos);
+            
+            byte[] buffer = new byte[BUF_SIZE];
+            int readLen = bis.read(buffer, 0, BUF_SIZE);
+            while (readLen != -1)
+            {
+                bos.write(buffer, 0, readLen);
+                readLen = bis.read(buffer, 0, BUF_SIZE);
+            }
+            
+            bos.flush();
+        }
+        catch (Exception e)
+        {
+            throw new ServletException(e);
+        }
+        finally
+        {
+            if (bos != null)
+            {
+                try
+                {
+                    bos.close();
+                }
+                catch (Exception ce)
+                {
+                }
+            }
+            if (sos != null)
+            {
+                try
+                {
+                    sos.close();
+                }
+                catch (Exception ce)
+                {
+                }
+            }
+            if (bis != null)
+            {
+                try
+                {
+                    bis.close();
+                }
+                catch (Exception ce)
+                {
+                }
+            }
+            if (is != null)
+            {
+                try
+                {
+                    is.close();
+                }
+                catch (Exception ce)
+                {
+                }
+            }
+        }
+    }
+
+    private String getResourcePath(HttpServletRequest request) throws ServletException
+    {
+        String path = request.getPathInfo();
+        
+        try
+        {
+            return URLDecoder.decode(path, "ISO-8859-1");
+        }
+        catch (UnsupportedEncodingException e)
+        {
+            throw new ServletException("Failed to decode the path: " + path);
+        }
+    }
+
+    private boolean isDownloadableResource(String path)
+    {
+        for (String prefix : allowedResourcePathPrefixes)
+        {
+            if (path.startsWith(prefix))
+            {
+                return true;
+            }
+        }
+        for (String postfix : allowedResourcePathPostfixes)
+        {
+            if (path.endsWith(postfix))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    private static String [] split(String s, String delimiters)
+    {
+        List<String> tokens = new ArrayList<String>();
+        StringTokenizer st = new StringTokenizer(s, delimiters);
+        
+        while (st.hasMoreTokens()) {
+            tokens.add(st.nextToken());
+        }
+        
+        return tokens.toArray(new String[tokens.size()]);
+    }
+    
+}

Propchange: portals/applications/gems/trunk/src/main/java/org/apache/portals/gems/servlet/WebAppClasspathResourceServlet.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: portals/applications/gems/trunk/src/main/java/org/apache/portals/gems/servlet/WebAppClasspathResourceServlet.java
------------------------------------------------------------------------------
    svn:keywords = Id

Propchange: portals/applications/gems/trunk/src/main/java/org/apache/portals/gems/servlet/WebAppClasspathResourceServlet.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain