1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
51
52
53
54
55
56
57
58
59
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
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
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
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
175 private boolean isHidden(String pathInContext)
176 {
177 return _hidden.containsKey(pathInContext);
178 }
179
180
181 public void destroy()
182 {
183 }
184
185
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
271 public void handleDelete(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) throws ServletException, IOException
272 {
273 try
274 {
275
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
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
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
348
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 }