View Javadoc

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