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