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 try (OutputStream out = new FileOutputStream(tmp,false))
223 {
224 if (toRead >= 0)
225 IO.copy(in, out, toRead);
226 else
227 IO.copy(in, out);
228 }
229
230 if (!tmp.renameTo(file))
231 throw new IOException("rename from "+tmp+" to "+file+" failed");
232 }
233 else
234 {
235 try (OutputStream out = new FileOutputStream(file,false))
236 {
237 if (toRead >= 0)
238 IO.copy(in, out, toRead);
239 else
240 IO.copy(in, out);
241 }
242 }
243
244 response.setStatus(exists ? HttpServletResponse.SC_OK : HttpServletResponse.SC_CREATED);
245 response.flushBuffer();
246 ok=true;
247 }
248 catch (Exception ex)
249 {
250 _context.log(ex.toString(),ex);
251 response.sendError(HttpServletResponse.SC_FORBIDDEN);
252 }
253 finally
254 {
255 if (!ok)
256 {
257 try
258 {
259 if (file.exists())
260 file.delete();
261 }
262 catch(Exception e)
263 {
264 _context.log(e.toString(),e);
265 }
266 }
267 _hidden.remove(pathInContext);
268 }
269 }
270 }
271
272
273 public void handleDelete(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file) throws ServletException, IOException
274 {
275 try
276 {
277
278 if (file.delete())
279 {
280 response.setStatus(HttpServletResponse.SC_NO_CONTENT);
281 response.flushBuffer();
282 }
283 else
284 response.sendError(HttpServletResponse.SC_FORBIDDEN);
285 }
286 catch (SecurityException sex)
287 {
288 _context.log(sex.toString(),sex);
289 response.sendError(HttpServletResponse.SC_FORBIDDEN);
290 }
291 }
292
293
294 public void handleMove(HttpServletRequest request, HttpServletResponse response, String pathInContext, File file)
295 throws ServletException, IOException, URISyntaxException
296 {
297 String newPath = URIUtil.canonicalPath(request.getHeader("new-uri"));
298 if (newPath == null)
299 {
300 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
301 return;
302 }
303
304 String contextPath = request.getContextPath();
305 if (contextPath != null && !newPath.startsWith(contextPath))
306 {
307 response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
308 return;
309 }
310 String newInfo = newPath;
311 if (contextPath != null)
312 newInfo = newInfo.substring(contextPath.length());
313
314 String new_resource = URIUtil.addPaths(_baseURI,newInfo);
315 File new_file=new File(new URI(new_resource));
316
317 file.renameTo(new_file);
318
319 response.setStatus(HttpServletResponse.SC_NO_CONTENT);
320 response.flushBuffer();
321 }
322
323
324 public void handleOptions(FilterChain chain, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
325 {
326 chain.doFilter(request,new HttpServletResponseWrapper(response)
327 {
328 @Override
329 public void setHeader(String name, String value)
330 {
331 if ("Allow".equalsIgnoreCase(name))
332 {
333 Set<String> options = new HashSet<String>();
334 options.addAll(Arrays.asList(value.split(" *, *")));
335 options.addAll(_operations);
336 value=null;
337 for (String o : options)
338 value=value==null?o:(value+", "+o);
339 }
340
341 super.setHeader(name,value);
342 }
343 });
344
345 }
346
347
348
349
350
351 protected boolean passConditionalHeaders(HttpServletRequest request, HttpServletResponse response, File file) throws IOException
352 {
353 long date = 0;
354
355 if ((date = request.getDateHeader("if-unmodified-since")) > 0)
356 {
357 if (file.lastModified() / 1000 > date / 1000)
358 {
359 response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED);
360 return false;
361 }
362 }
363
364 if ((date = request.getDateHeader("if-modified-since")) > 0)
365 {
366 if (file.lastModified() / 1000 <= date / 1000)
367 {
368 response.reset();
369 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
370 response.flushBuffer();
371 return false;
372 }
373 }
374 return true;
375 }
376 }