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<String> _operations = new HashSet<String>();
64      protected ConcurrentMap<String,String> _hidden = new ConcurrentHashMap<String, String>();
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         System.err.println("Doing PUT filter "+resource);
119    
120         String method = request.getMethod();
121         boolean op = _operations.contains(method);
122         
123         if (op)
124         {
125             File file = null;
126             try
127             {
128                 if (method.equals(__OPTIONS))
129                     handleOptions(request, response);
130                 else
131                 {
132                     file=new File(new URI(resource));
133                     boolean exists = file.exists();
134                     if (exists && !passConditionalHeaders(request, response, file))
135                         return;
136                     
137                     if (method.equals(__PUT))
138                         handlePut(request, response,pathInContext, file);
139                     else if (method.equals(__DELETE))
140                         handleDelete(request, response, pathInContext, file);
141                     else if (method.equals(__MOVE))
142                         handleMove(request, response, pathInContext, file);
143                     else
144                         throw new IllegalStateException();
145                 }
146             }
147             catch(Exception e)
148             {
149                 _context.log(e.toString(),e);
150                 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
151             }
152         }
153         else
154         {
155             if (isHidden(pathInContext))
156                 response.sendError(HttpServletResponse.SC_NOT_FOUND);
157             else
158                 chain.doFilter(request,response);
159             return;
160         }
161     }
162 
163     /* ------------------------------------------------------------ */
164     private boolean isHidden(String pathInContext)
165     {
166         return _hidden.containsKey(pathInContext);
167     }
168 
169     /* ------------------------------------------------------------ */
170     public void destroy()
171     {
172     }
173 
174     /* ------------------------------------------------------------------- */
175     public void handlePut(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) throws ServletException, IOException
176     {
177         boolean exists = file.exists();
178         if (pathInContext.endsWith("/"))
179         {
180             if (!exists)
181             {
182                 if (!file.mkdirs())
183                     response.sendError(HttpServletResponse.SC_FORBIDDEN);
184                 else
185                 {
186                     response.setStatus(HttpServletResponse.SC_CREATED);
187                     response.flushBuffer();
188                 }
189             }
190             else
191             {
192                 response.setStatus(HttpServletResponse.SC_OK);
193                 response.flushBuffer();
194             }
195         }
196         else
197         {
198             boolean ok=false;
199             try
200             {
201                 _hidden.put(pathInContext,pathInContext);
202                 File parent = file.getParentFile();
203                 parent.mkdirs();
204                 int toRead = request.getContentLength();
205                 InputStream in = request.getInputStream();
206                 OutputStream out = new FileOutputStream(file,false);
207                 if (toRead >= 0)
208                     IO.copy(in, out, toRead);
209                 else
210                     IO.copy(in, out);
211                 out.close();
212 
213                 response.setStatus(exists ? HttpServletResponse.SC_OK : HttpServletResponse.SC_CREATED);
214                 response.flushBuffer();
215                 ok=true;
216             }
217             catch (Exception ex)
218             {
219                 _context.log(ex.toString(),ex);
220                 response.sendError(HttpServletResponse.SC_FORBIDDEN);
221             }
222             finally
223             {
224                 if (!ok)
225                 {
226                     try
227                     {
228                         if (file.exists())
229                             file.delete();
230                     }
231                     catch(Exception e)
232                     {
233                         _context.log(e.toString(),e);
234                     }
235                 }
236                 _hidden.remove(pathInContext);
237             }
238         }
239     }
240 
241     /* ------------------------------------------------------------------- */
242     public void handleDelete(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) throws ServletException, IOException
243     {
244         try
245         {
246             // delete the file
247             if (file.delete())
248             {
249                 response.setStatus(HttpServletResponse.SC_NO_CONTENT);
250                 response.flushBuffer();
251             }
252             else
253                 response.sendError(HttpServletResponse.SC_FORBIDDEN);
254         }
255         catch (SecurityException sex)
256         {
257             _context.log(sex.toString(),sex);
258             response.sendError(HttpServletResponse.SC_FORBIDDEN);
259         }
260     }
261 
262     /* ------------------------------------------------------------------- */
263     public void handleMove(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) 
264         throws ServletException, IOException, URISyntaxException
265     {
266         String newPath = URIUtil.canonicalPath(request.getHeader("new-uri"));
267         if (newPath == null)
268         {
269             response.sendError(HttpServletResponse.SC_BAD_REQUEST);
270             return;
271         }
272         
273         String contextPath = request.getContextPath();
274         if (contextPath != null && !newPath.startsWith(contextPath))
275         {
276             response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
277             return;
278         }
279         String newInfo = newPath;
280         if (contextPath != null)
281             newInfo = newInfo.substring(contextPath.length());
282 
283         String new_resource = URIUtil.addPaths(_baseURI,newInfo);
284         File new_file=new File(new URI(new_resource));
285 
286         file.renameTo(new_file);
287 
288         response.setStatus(HttpServletResponse.SC_NO_CONTENT);
289         response.flushBuffer();
290 
291 
292     }
293 
294     /* ------------------------------------------------------------ */
295     public void handleOptions(HttpServletRequest request, HttpServletResponse response) throws IOException
296     {
297         // TODO implement
298         throw new UnsupportedOperationException("Not Implemented");
299     }
300 
301     /* ------------------------------------------------------------ */
302     /*
303      * Check modification date headers.
304      */
305     protected boolean passConditionalHeaders(HttpServletRequest request, HttpServletResponse response, File file) throws IOException
306     {
307         long date = 0;
308         
309         if ((date = request.getDateHeader("if-unmodified-since")) > 0)
310         {
311             if (file.lastModified() / 1000 > date / 1000)
312             {
313                 response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
314                 return false;
315             }
316         }
317 
318         if ((date = request.getDateHeader("if-modified-since")) > 0)
319         {
320             if (file.lastModified() / 1000 <= date / 1000)
321             {
322                 response.reset();
323                 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
324                 response.flushBuffer();
325                 return false;
326             }
327         }
328         return true;
329     }
330 }