View Javadoc

1   // ========================================================================
2   // Copyright (c) 2009-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.servlets;
15  
16  import java.io.File;
17  import java.io.FileOutputStream;
18  import java.io.IOException;
19  import java.io.InputStream;
20  import java.io.OutputStream;
21  import java.net.URI;
22  import java.net.URISyntaxException;
23  import java.util.HashSet;
24  import java.util.Set;
25  import java.util.concurrent.ConcurrentHashMap;
26  import java.util.concurrent.ConcurrentMap;
27  
28  import javax.servlet.Filter;
29  import javax.servlet.FilterChain;
30  import javax.servlet.FilterConfig;
31  import javax.servlet.ServletContext;
32  import javax.servlet.ServletException;
33  import javax.servlet.ServletRequest;
34  import javax.servlet.ServletResponse;
35  import javax.servlet.UnavailableException;
36  import javax.servlet.http.HttpServletRequest;
37  import javax.servlet.http.HttpServletResponse;
38  
39  import org.eclipse.jetty.util.IO;
40  import org.eclipse.jetty.util.URIUtil;
41  
42  /**
43   * PutFilter
44   * 
45   * A Filter that handles PUT, DELETE and MOVE methods.
46   * Files are hidden during PUT operations, so that 404's result.
47   * 
48   * The following init paramters pay be used:<ul>
49   * <li><b>baseURI</b> - The file URI of the document root for put content.
50   * <li><b>delAllowed</b> - boolean, if true DELETE and MOVE methods are supported.
51   * </ul>
52   *
53   */
54  public class PutFilter implements Filter 
55  {
56      public final static String __PUT="PUT";
57      public final static String __DELETE="DELETE";
58      public final static String __MOVE="MOVE";
59      public final static String __OPTIONS="OPTIONS";
60  
61      Set<String> _operations = new HashSet<String>();
62      protected ConcurrentMap<String,String> _hidden = new ConcurrentHashMap<String, String>();
63  
64      protected ServletContext _context;
65      protected String _baseURI;
66      protected boolean _delAllowed;
67      
68      /* ------------------------------------------------------------ */
69      public void init(FilterConfig config) throws ServletException
70      {
71          _context=config.getServletContext();
72          if (_context.getRealPath("/")==null)
73             throw new UnavailableException("Packed war");
74          
75          String b = config.getInitParameter("baseURI");
76          if (b != null)
77          {
78              _baseURI=b;
79          }
80          else
81          {
82              File base=new File(_context.getRealPath("/"));
83              _baseURI=base.toURI().toString();
84          }
85  
86          _delAllowed = getInitBoolean(config,"delAllowed");
87  
88          _operations.add(__OPTIONS);
89          _operations.add(__PUT);
90          if (_delAllowed)
91          {
92              _operations.add(__DELETE);
93              _operations.add(__MOVE);
94          }
95      }
96  
97      /* ------------------------------------------------------------ */
98      private boolean getInitBoolean(FilterConfig config,String name)
99      {
100         String value = config.getInitParameter(name);
101         return value != null && value.length() > 0 && (value.startsWith("t") || value.startsWith("T") || value.startsWith("y") || value.startsWith("Y") || value.startsWith("1"));
102     }
103 
104     /* ------------------------------------------------------------ */
105     public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException
106     {
107        
108         HttpServletRequest request=(HttpServletRequest)req;
109         HttpServletResponse response=(HttpServletResponse)res;
110 
111         String servletPath =request.getServletPath();
112         String pathInfo = request.getPathInfo();
113         String pathInContext = URIUtil.addPaths(servletPath, pathInfo);    
114 
115         String resource = URIUtil.addPaths(_baseURI,pathInContext); 
116        
117         String method = request.getMethod();
118         boolean op = _operations.contains(method);
119         
120         if (op)
121         {
122             File file = null;
123             try
124             {
125                 if (method.equals(__OPTIONS))
126                     handleOptions(request, response);
127                 else
128                 {
129                     file=new File(new URI(resource));
130                     boolean exists = file.exists();
131                     if (exists && !passConditionalHeaders(request, response, file))
132                         return;
133                     
134                     if (method.equals(__PUT))
135                         handlePut(request, response,pathInContext, file);
136                     else if (method.equals(__DELETE))
137                         handleDelete(request, response, pathInContext, file);
138                     else if (method.equals(__MOVE))
139                         handleMove(request, response, pathInContext, file);
140                     else
141                         throw new IllegalStateException();
142                 }
143             }
144             catch(Exception e)
145             {
146                 _context.log(e.toString(),e);
147                 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
148             }
149         }
150         else
151         {
152             if (isHidden(pathInContext))
153                 response.sendError(HttpServletResponse.SC_NOT_FOUND);
154             else
155                 chain.doFilter(request,response);
156             return;
157         }
158     }
159 
160     /* ------------------------------------------------------------ */
161     private boolean isHidden(String pathInContext)
162     {
163         return _hidden.containsKey(pathInContext);
164     }
165 
166     /* ------------------------------------------------------------ */
167     public void destroy()
168     {
169     }
170 
171     /* ------------------------------------------------------------------- */
172     public void handlePut(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) throws ServletException, IOException
173     {
174         boolean exists = file.exists();
175         if (pathInContext.endsWith("/"))
176         {
177             if (!exists)
178             {
179                 if (!file.mkdirs())
180                     response.sendError(HttpServletResponse.SC_FORBIDDEN);
181                 else
182                 {
183                     response.setStatus(HttpServletResponse.SC_CREATED);
184                     response.flushBuffer();
185                 }
186             }
187             else
188             {
189                 response.setStatus(HttpServletResponse.SC_OK);
190                 response.flushBuffer();
191             }
192         }
193         else
194         {
195             boolean ok=false;
196             try
197             {
198                 _hidden.put(pathInContext,pathInContext);
199                 File parent = file.getParentFile();
200                 parent.mkdirs();
201                 int toRead = request.getContentLength();
202                 InputStream in = request.getInputStream();
203                 OutputStream out = new FileOutputStream(file,false);
204                 if (toRead >= 0)
205                     IO.copy(in, out, toRead);
206                 else
207                     IO.copy(in, out);
208                 out.close();
209 
210                 response.setStatus(exists ? HttpServletResponse.SC_OK : HttpServletResponse.SC_CREATED);
211                 response.flushBuffer();
212                 ok=true;
213             }
214             catch (Exception ex)
215             {
216                 _context.log(ex.toString(),ex);
217                 response.sendError(HttpServletResponse.SC_FORBIDDEN);
218             }
219             finally
220             {
221                 if (!ok)
222                 {
223                     try
224                     {
225                         if (file.exists())
226                             file.delete();
227                     }
228                     catch(Exception e)
229                     {
230                         _context.log(e.toString(),e);
231                     }
232                 }
233                 _hidden.remove(pathInContext);
234             }
235         }
236     }
237 
238     /* ------------------------------------------------------------------- */
239     public void handleDelete(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) throws ServletException, IOException
240     {
241         try
242         {
243             // delete the file
244             if (file.delete())
245             {
246                 response.setStatus(HttpServletResponse.SC_NO_CONTENT);
247                 response.flushBuffer();
248             }
249             else
250                 response.sendError(HttpServletResponse.SC_FORBIDDEN);
251         }
252         catch (SecurityException sex)
253         {
254             _context.log(sex.toString(),sex);
255             response.sendError(HttpServletResponse.SC_FORBIDDEN);
256         }
257     }
258 
259     /* ------------------------------------------------------------------- */
260     public void handleMove(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) 
261         throws ServletException, IOException, URISyntaxException
262     {
263         String newPath = URIUtil.canonicalPath(request.getHeader("new-uri"));
264         if (newPath == null)
265         {
266             response.sendError(HttpServletResponse.SC_BAD_REQUEST);
267             return;
268         }
269         
270         String contextPath = request.getContextPath();
271         if (contextPath != null && !newPath.startsWith(contextPath))
272         {
273             response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
274             return;
275         }
276         String newInfo = newPath;
277         if (contextPath != null)
278             newInfo = newInfo.substring(contextPath.length());
279 
280         String new_resource = URIUtil.addPaths(_baseURI,newInfo);
281         File new_file=new File(new URI(new_resource));
282 
283         file.renameTo(new_file);
284 
285         response.setStatus(HttpServletResponse.SC_NO_CONTENT);
286         response.flushBuffer();
287 
288 
289     }
290 
291     /* ------------------------------------------------------------ */
292     public void handleOptions(HttpServletRequest request, HttpServletResponse response) throws IOException
293     {
294         // TODO implement
295         throw new UnsupportedOperationException("Not Implemented");
296     }
297 
298     /* ------------------------------------------------------------ */
299     /*
300      * Check modification date headers.
301      */
302     protected boolean passConditionalHeaders(HttpServletRequest request, HttpServletResponse response, File file) throws IOException
303     {
304         long date = 0;
305         
306         if ((date = request.getDateHeader("if-unmodified-since")) > 0)
307         {
308             if (file.lastModified() / 1000 > date / 1000)
309             {
310                 response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
311                 return false;
312             }
313         }
314 
315         if ((date = request.getDateHeader("if-modified-since")) > 0)
316         {
317             if (file.lastModified() / 1000 <= date / 1000)
318             {
319                 response.reset();
320                 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
321                 response.flushBuffer();
322                 return false;
323             }
324         }
325         return true;
326     }
327 }