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