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