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