1    /*
2     * Copyright 2009 :torweg free software group
3     *
4     * This program is free software: you can redistribute it and/or modify
5     * it under the terms of the GNU General Public License as published by
6     * the Free Software Foundation, either version 3 of the License, or
7     * (at your option) any later version.
8     * 
9     * This program is distributed in the hope that it will be useful,
10    * but WITHOUT ANY WARRANTY; without even the implied warranty of
11    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    * GNU General Public License for more details.
13    * 
14    * You should have received a copy of the GNU General Public License
15    * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16    *
17    */
18   package org.torweg.pulse.webdav;
19   
20   import java.io.IOException;
21   import java.io.InputStream;
22   import java.io.OutputStream;
23   import java.net.URI;
24   import java.net.URISyntaxException;
25   
26   import javax.servlet.http.HttpServlet;
27   import javax.servlet.http.HttpServletRequest;
28   import javax.servlet.http.HttpServletResponse;
29   
30   import org.apache.log4j.NDC;
31   import org.hibernate.Session;
32   import org.hibernate.Transaction;
33   import org.jdom.Namespace;
34   import org.slf4j.Logger;
35   import org.slf4j.LoggerFactory;
36   import org.torweg.pulse.accesscontrol.User;
37   import org.torweg.pulse.invocation.lifecycle.Lifecycle;
38   import org.torweg.pulse.service.PulseException;
39   import org.torweg.pulse.util.MimeMap;
40   import org.torweg.pulse.util.streamscanner.InacceptableStreamException;
41   import org.torweg.pulse.util.streamscanner.InputStreamScannerChain;
42   import org.torweg.pulse.vfs.IllegalFileNameException;
43   import org.torweg.pulse.vfs.VFSDAVStore;
44   import org.torweg.pulse.vfs.VirtualFile;
45   import org.torweg.pulse.vfs.VirtualFileSystem;
46   import org.torweg.pulse.webdav.request.DAVMethod;
47   import org.torweg.pulse.webdav.request.HttpDAVServletRequest;
48   import org.torweg.pulse.webdav.request.PropFindRequest;
49   import org.torweg.pulse.webdav.request.PropPatchRequest;
50   import org.torweg.pulse.webdav.request.PropPatchRequest.OperationType;
51   import org.torweg.pulse.webdav.request.PropPatchRequest.PropPatchOperation;
52   import org.torweg.pulse.webdav.response.HttpDAVServletResponse;
53   import org.torweg.pulse.webdav.response.MultiStatusResponse;
54   import org.torweg.pulse.webdav.response.PropFindResponse;
55   import org.torweg.pulse.webdav.response.PropNameResponse;
56   import org.torweg.pulse.webdav.response.PropPatchResponse;
57   import org.torweg.pulse.webdav.response.VFSPropFindResponse;
58   import org.torweg.pulse.webdav.response.VFSPropNameResponse;
59   import org.torweg.pulse.webdav.util.BasicAuthentication;
60   import org.torweg.pulse.webdav.util.DeadProperty;
61   import org.torweg.pulse.webdav.util.StatusResponseElement;
62   
63   /**
64    * A DAV 1 compliant servlet.
65    * 
66    * @author Thomas Weber
67    * @version $Revision: 2043 $
68    */
69   public abstract class AbstractDAVServlet extends HttpServlet {
70   
71       /**
72        * serialVersionUID.
73        */
74       private static final long serialVersionUID = -8155839669380772866L;
75   
76       /**
77        * the logger.
78        */
79       private static final Logger LOGGER = LoggerFactory
80               .getLogger(AbstractDAVServlet.class);
81   
82       /**
83        * the DAV namespace.
84        */
85       public static final Namespace DAV_NAMESPACE = Namespace.getNamespace("d",
86               "DAV:");
87   
88       /**
89        * extracts the resource URI from the request URI.
90        * 
91        * @param req
92        *            the current request
93        * @return the resource URI
94        */
95       private URI getResourceURI(final HttpServletRequest req) {
96           try {
97               String pathInfo = req.getPathInfo();
98               if (pathInfo == null) {
99                   pathInfo = "/";
100              }
101              return new URI(pathInfo.replace(' ', '+'));
102          } catch (URISyntaxException e) {
103              LOGGER.warn(e.getLocalizedMessage());
104              return null;
105          }
106      }
107  
108      /**
109       * extracts the resource URI from the request URI.
110       * 
111       * @param req
112       *            the current request
113       * @param store
114       *            the DAVStore
115       * @return the translated resource URI
116       */
117      private URI getResourceURITranslated(final HttpServletRequest req,
118              final DAVStore store) {
119          String pathInfo = req.getPathInfo();
120          if (pathInfo == null) {
121              pathInfo = "/";
122          }
123          return store.translateDavURI(pathInfo);
124      }
125  
126      /**
127       * extracts the resource URI from the request URI.
128       * 
129       * @param davServletPath
130       *            the path to the DAVServlet
131       * @param requestURI
132       *            the request URI
133       * @param store
134       *            the DAVStore
135       * @return the translated resource URI
136       */
137      private URI getResourceURITranslated(final String davServletPath,
138              final String requestURI, final DAVStore store) {
139          return store.translateDavURI(requestURI.substring(requestURI
140                  .indexOf(davServletPath) + davServletPath.length()));
141      }
142  
143      /**
144       * takes care of the DAV requests and delegates to the actual worker
145       * methods.
146       * 
147       * @param req
148       *            the request
149       * @param res
150       *            the response
151       * @throws IOException
152       *             on i/o errors
153       */
154      // CHECKSTYLE:OFF
155      /*
156       * Cyclomatic complexity warning, can be ignored. Method delegation is ugly
157       * but necessary.
158       */
159      @Override
160      public final void service(final HttpServletRequest req,
161              final HttpServletResponse res) throws IOException {
162          // CHECKSTYLE:ON
163          try {
164              NDC.push(req.getRemoteAddr());
165  
166              long start = System.currentTimeMillis();
167  
168              res.addHeader("DAV", "1");
169              res.addHeader("MS-Author-Via", "DAV");
170  
171              User user;
172              try {
173                  user = BasicAuthentication.authenticate(req, res);
174              } catch (Exception e) {
175                  res.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
176                  LOGGER.error("Unexpected failure during authentication", e);
177                  return;
178              }
179              if (user == null) {
180                  return;
181              }
182  
183              /* delegate request */
184              HttpDAVServletRequest davRequest = new HttpDAVServletRequest(req);
185              HttpDAVServletResponse davResponse = new HttpDAVServletResponse(res);
186  
187              switch (davRequest.getDAVMethod()) {
188              case OPTIONS:
189                  options(davRequest, davResponse, user);
190                  break;
191              case PROPFIND:
192                  propFind(davRequest, davResponse, user);
193                  break;
194              case PROPPATCH:
195                  propPatch(davRequest, davResponse, user);
196                  break;
197              case MKCOL:
198                  mkcol(davRequest, davResponse, user);
199                  break;
200              case GET:
201                  get(davRequest, davResponse, user);
202                  break;
203              case HEAD:
204                  head(davRequest, davResponse, user);
205                  break;
206              case POST:
207                  post(davRequest, davResponse, user);
208                  break;
209              case DELETE:
210                  delete(davRequest, davResponse, user);
211                  break;
212              case PUT:
213                  put(davRequest, davResponse, user);
214                  break;
215              case COPY:
216                  copy(davRequest, davResponse, user);
217                  break;
218              case MOVE:
219                  move(davRequest, davResponse, user);
220                  break;
221              default:
222                  res.sendError(DAVStatus.NOT_IMPLEMENTED.getStatusCode(),
223                          DAVStatus.NOT_IMPLEMENTED.getStatusMessage());
224              }
225  
226              LOGGER.info(
227                      "{} ({}ms): {}",
228                      new Object[] { davRequest.getDAVMethod(),
229                              (System.currentTimeMillis() - start),
230                              davRequest.getRequestURI() });
231  
232          } catch (RuntimeException e) {
233              LOGGER.error(e.getLocalizedMessage(), e);
234              throw e;
235          } catch (IOException e) {
236              LOGGER.error(e.getLocalizedMessage(), e);
237              throw e;
238          } catch (Exception e) {
239              LOGGER.error(e.getLocalizedMessage(), e);
240              throw new PulseException(e.getLocalizedMessage(), e);
241          } finally {
242              NDC.pop();
243              NDC.remove();
244          }
245      }
246  
247      /**
248       * returns the DAV configuration.
249       * 
250       * @return the DAV configutaion
251       */
252      protected abstract DAVConfiguration getConfiguration();
253  
254      /**
255       * sets the the current user' allowed options for the requested URI.
256       * 
257       * @param req
258       *            the current request
259       * @param res
260       *            the response
261       * @param user
262       *            the user
263       */
264      protected final void options(final HttpDAVServletRequest req,
265              final HttpDAVServletResponse res, final User user) {
266          DAVStore store = getConfiguration().getDAVStore();
267          URI resourceURI = getResourceURITranslated(req, store);
268          boolean isCollection = store.isCollection(resourceURI);
269          boolean canWrite = store.canWrite(resourceURI, user);
270  
271          StringBuilder allowedMethods = new StringBuilder();
272          /* no read access, no options */
273          if (!store.canRead(resourceURI, user)) {
274              for (DAVMethod m : DAVMethod.values()) {
275                  if (isValidMethod(m, isCollection, canWrite)) {
276                      allowedMethods.append(m.getMethodName()).append(',');
277                  }
278              }
279              if (allowedMethods.length() > 2) {
280                  allowedMethods = allowedMethods.delete(
281                          allowedMethods.length() - 1, allowedMethods.length());
282              }
283          }
284          res.setHeader("Allow", allowedMethods.toString());
285          LOGGER.trace("OPTIONS: Allow {}", allowedMethods);
286      }
287  
288      /**
289       * evaluates, if the given method is valid for the request.
290       * 
291       * @param isCollection
292       *            flag, indicating whether the method is requested for a
293       *            collection
294       * @param canWrite
295       *            flag, indicating whether the user has write access
296       * @param m
297       *            the method to be evaluated
298       * @return {@code true}, if and only if the method is valid. Otherwise
299       *         {@code false}.
300       */
301      private boolean isValidMethod(final DAVMethod m,
302              final boolean isCollection, final boolean canWrite) {
303          if (m == DAVMethod.POST) {
304              return false;
305          }
306          /* MKCOL requires a collection and write access */
307          if ((!isCollection || !canWrite) && (m == DAVMethod.MKCOL)) {
308              return false;
309          }
310          /* PUT, DELETE, PROPPATCH and MOVE require write access */
311          // CHECKSTYLE:OFF
312          /* ugly, but true */
313          if (!canWrite
314                  && (m == DAVMethod.PUT || m == DAVMethod.DELETE
315                          || m == DAVMethod.PROPPATCH || m == DAVMethod.MOVE)) {
316              // CHECKSTYLE:ON
317              return false;
318          }
319          return true;
320      }
321  
322      /**
323       * performs a PROPFIND request.
324       * 
325       * @param req
326       *            the current request
327       * @param res
328       *            the response
329       * @param user
330       *            the user
331       * @throws IOException
332       *             on i/o errors
333       */
334      protected final void propFind(final HttpDAVServletRequest req,
335              final HttpDAVServletResponse res, final User user)
336              throws IOException {
337          PropFindRequest propFindRequest = new PropFindRequest(
338                  req.getInputStream(), getConfiguration().getDAVStore(),
339                  req.getDepth(), getDAVServletPath(req));
340  
341          URI resourceURI = getResourceURI(req);
342          LOGGER.debug("{} on: {}", propFindRequest, resourceURI);
343  
344          /* differentiate between "normal" PROPFIND and a propname request */
345          if (!propFindRequest.isPropNameRequest()) {
346              doPropFind(req, res, user, propFindRequest);
347          } else {
348              doPropName(req, res, user, propFindRequest);
349          }
350      }
351  
352      /**
353       * performs a propname PROPFIND request.
354       * 
355       * @param req
356       *            the HttpDAVServletRequest
357       * @param res
358       *            the HttpDAVServletResponse
359       * @param user
360       *            the current user
361       * @param propFindRequest
362       *            the PROPFIND request
363       * @throws IOException
364       *             on errors interacting with the DAVStore
365       */
366      private void doPropName(final HttpDAVServletRequest req,
367              final HttpDAVServletResponse res, final User user,
368              final PropFindRequest propFindRequest) throws IOException {
369          DAVStore store = getConfiguration().getDAVStore();
370          URI resourceURI = getResourceURI(req);
371          MultiStatusResponse propNameResponse;
372  
373          if (store instanceof VFSDAVStore) {
374              propNameResponse = new VFSPropNameResponse(resourceURI,
375                      req.getDepth(), user, (VFSDAVStore) store, propFindRequest,
376                      res);
377          } else {
378              propNameResponse = new PropNameResponse(resourceURI,
379                      req.getDepth(), user, store, res);
380          }
381          propNameResponse.closeResponse();
382      }
383  
384      /**
385       * actually performs a PROPFIND request and sends the response.
386       * 
387       * @param req
388       *            the HttpDAVServletRequest
389       * @param res
390       *            the HttpDAVServletResponse
391       * @param user
392       *            the current user
393       * @param propFindRequest
394       *            the PROPFIND request
395       * @throws IOException
396       *             on errors interacting with the DAVStore
397       */
398      private void doPropFind(final HttpDAVServletRequest req,
399              final HttpDAVServletResponse res, final User user,
400              final PropFindRequest propFindRequest) throws IOException {
401          DAVStore store = getConfiguration().getDAVStore();
402          URI resourceURI = getResourceURI(req);
403          MultiStatusResponse propfindResponse;
404  
405          if (store instanceof VFSDAVStore) {
406              String uriString = resourceURI.toString();
407              if (uriString.startsWith("/public")
408                      || uriString.startsWith("/private")) {
409                  /* "real" WebDAV request */
410                  /* send 404, for non existing objects */
411                  if (!store.objectExists(getResourceURITranslated(req, store))) {
412                      res.setStatus(DAVStatus.NOT_FOUND);
413                      return;
414                  }
415                  propfindResponse = new VFSPropFindResponse(resourceURI,
416                          propFindRequest, (VFSDAVStore) store, user, res);
417              } else {
418                  /*
419                   * a direct request on the DAVServlet, add root folders of the
420                   * VirtualFileSystem (i.e "public", "private").
421                   */
422                  if (!uriString.equals("/") && !uriString.equals("")) {
423                      /*
424                       * the "" and the slash are the servlet itself. Any other
425                       * resource which is not handled by the if clause above is
426                       * not existing!
427                       */
428                      res.setStatus(DAVStatus.NOT_FOUND);
429                      return;
430                  }
431                  propfindResponse = propFindAddVFSRootFolders(req, user,
432                          propFindRequest, res);
433              }
434          } else {
435              /* send 404, for non existing objects */
436              if (!store.objectExists(getResourceURITranslated(req, store))) {
437                  res.setStatus(DAVStatus.NOT_FOUND);
438                  return;
439              }
440              propfindResponse = new PropFindResponse(resourceURI,
441                      propFindRequest, store, user, res);
442          }
443  
444          propfindResponse.closeResponse();
445      }
446  
447      /**
448       * adds the VFS root folders for a PROPFIND request directly on the
449       * {@code AbstractDAVServlet}.
450       * 
451       * @param propFindRequest
452       *            the PROPFIND request
453       * @param user
454       *            the current user
455       * @param req
456       *            the HttpDAVServletRequest
457       * @param res
458       *            the HttpDAVServletResponse
459       * @return the prepared {@code PropFindResponse}
460       */
461      private PropFindResponse propFindAddVFSRootFolders(
462              final HttpDAVServletRequest req, final User user,
463              final PropFindRequest propFindRequest,
464              final HttpDAVServletResponse res) {
465          String base = req.getRequestURI();
466          String name = base.substring(base.lastIndexOf('/') + 1);
467          if (!base.endsWith("/")) {
468              base = new StringBuilder(base).append('/').toString();
469          }
470  
471          PropFindResponse propFindResponse;
472          propFindResponse = new PropFindResponse(propFindRequest, user, res);
473  
474          // List<IMultiStatusElement> resCollection = new
475          // ArrayList<IMultiStatusElement>();
476          try {
477              URI baseURI = new URI(base);
478              propFindResponse.add(PropFindResponse.buildDAVResponse(
479                      propFindRequest, name, baseURI));
480              propFindResponse.add(PropFindResponse.buildDAVResponse(
481                      propFindRequest, "public", new URI(base + "public")));
482              propFindResponse.add(PropFindResponse.buildDAVResponse(
483                      propFindRequest, "private", new URI(base + "private")));
484          } catch (Exception e) {
485              throw new PulseException(e);
486          }
487          // propFindResponse.setResponses(resCollection);
488  
489          return propFindResponse;
490      }
491  
492      /**
493       * performs a PROPPATCH request.
494       * 
495       * @param req
496       *            the current request
497       * @param res
498       *            the response
499       * @param user
500       *            the user
501       * @throws IOException
502       *             on i/o errors
503       */
504      protected final void propPatch(final HttpDAVServletRequest req,
505              final HttpDAVServletResponse res, final User user)
506              throws IOException {
507          DAVStore store = getConfiguration().getDAVStore();
508          URI resourceURI = getResourceURITranslated(req, store);
509  
510          if (!store.canWrite(resourceURI, user)) {
511              res.setStatus(DAVStatus.FORBIDDEN);
512              return;
513          }
514          PropPatchRequest propPatchRequest = new PropPatchRequest(
515                  req.getInputStream(), resourceURI);
516  
517          /* so far only VFSStores can store dead properties */
518          if (!(store instanceof VFSDAVStore)) {
519              PropPatchResponse propPatchResponse = new PropPatchResponse(
520                      propPatchRequest.getOperations().get(0),
521                      "Unsupported operation", propPatchRequest, res);
522              propPatchResponse.closeResponse();
523              return;
524          }
525  
526          /* do the patching */
527  
528          Session s = Lifecycle.getHibernateDataSource().createNewSession();
529          Transaction tx = s.beginTransaction();
530          try {
531              VirtualFile file = VirtualFileSystem.getInstance().loadVirtualFile(
532                      resourceURI, s);
533              for (PropPatchOperation operation : propPatchRequest
534                      .getOperations()) {
535                  if (operation.getProperty().getNamespaceURI()
536                          .equals(AbstractDAVServlet.DAV_NAMESPACE.getURI())) {
537                      /* live properties cannot be patched */
538                      PropPatchResponse propPatchResponse = new PropPatchResponse(
539                              operation, "DAV properties cannot be patched",
540                              propPatchRequest, res);
541                      propPatchResponse.closeResponse();
542                      return;
543                  } else if (operation.getProperty() instanceof DeadProperty) {
544                      /* operation concerns a dead property */
545                      if (operation.getType() == OperationType.SET) {
546                          file.addDeadProperty((DeadProperty) operation
547                                  .getProperty());
548                      } else if (operation.getType() == OperationType.REMOVE) {
549                          file.removeDeadProperty((DeadProperty) operation
550                                  .getProperty().extractPrototype());
551                      }
552                  }
553              }
554              tx.commit();
555          } catch (Exception e) {
556              tx.rollback();
557              throw new PulseException("Error: " + e.getLocalizedMessage(), e);
558          } finally {
559              s.close();
560          }
561  
562          /* everything has worked, send success response */
563          PropPatchResponse propPatchResponse = new PropPatchResponse(
564                  propPatchRequest, res);
565          propPatchResponse.closeResponse();
566  
567      }
568  
569      /**
570       * performs an MKCOL request.
571       * 
572       * @param req
573       *            the current request
574       * @param res
575       *            the response
576       * @param user
577       *            the user
578       * @throws IOException
579       *             on errors
580       */
581      protected final void mkcol(final HttpDAVServletRequest req,
582              final HttpDAVServletResponse res, final User user)
583              throws IOException {
584          DAVStore store = getConfiguration().getDAVStore();
585          URI resourceURI = getResourceURITranslated(req, store);
586  
587          /* mkcol is only allowed on unmapped URIs */
588          if (store.objectExists(resourceURI)) {
589              res.setStatus(DAVStatus.METHOD_NOT_ALLOWED);
590              return;
591          }
592  
593          URI parentURI = store.getParentURI(resourceURI);
594          /* parent must exist and be a collection */
595          if (!store.objectExists(parentURI) || !store.isCollection(parentURI)) {
596              res.setStatus(DAVStatus.CONFLICT);
597              return;
598          } else if (!store.canWrite(parentURI, user)) {
599              /*
600               * the parent collection of the URI exists but cannot accept members
601               */
602              res.setStatus(DAVStatus.FORBIDDEN);
603              return;
604          }
605  
606          /* actually create the collection */
607          if (store.createResourceCollection(resourceURI, user)) {
608              res.setStatus(DAVStatus.CREATED);
609          } else {
610              res.setStatus(DAVStatus.CONFLICT);
611          }
612      }
613  
614      /**
615       * performs a GET request.
616       * 
617       * @param req
618       *            the current request
619       * @param res
620       *            the response
621       * @param user
622       *            the user
623       * @throws IOException
624       *             on errors accessing the resource
625       */
626      protected final void get(final HttpDAVServletRequest req,
627              final HttpDAVServletResponse res, final User user)
628              throws IOException {
629          DAVStore store = getConfiguration().getDAVStore();
630          URI resourceURI = getResourceURITranslated(req, store);
631  
632          if (!store.canRead(resourceURI, user)) {
633              res.setStatus(DAVStatus.FORBIDDEN);
634              return;
635          }
636  
637          /* output resource contents */
638          if (store.isResource(resourceURI)) {
639              try {
640                  InputStream in = store.getContent(resourceURI, user);
641                  OutputStream out = res.getOutputStream();
642                  // gather head information
643                  addHeadResponseHeaders(res, user, store, resourceURI);
644                  try {
645                      byte[] buffer = new byte[res.getBufferSize()];
646                      int available = in.read(buffer);
647                      while (available > 0) {
648                          out.write(buffer, 0, available);
649                          available = in.read(buffer);
650                      }
651                  } finally {
652                      in.close();
653                      out.flush();
654                      out.close();
655                  }
656              } catch (IOException e) {
657                  res.setStatus(DAVStatus.NOT_FOUND);
658                  LOGGER.warn(e.getLocalizedMessage());
659              }
660          }
661      }
662  
663      /**
664       * performs a HEAD request.
665       * 
666       * @param req
667       *            the current request
668       * @param res
669       *            the response
670       * @param user
671       *            the user
672       * @throws IOException
673       *             on errors accessing the resource
674       */
675      protected final void head(final HttpDAVServletRequest req,
676              final HttpDAVServletResponse res, final User user)
677              throws IOException {
678          DAVStore store = getConfiguration().getDAVStore();
679          URI resourceURI = getResourceURITranslated(req, store);
680  
681          try {
682              if (!store.canRead(resourceURI, user)) {
683                  res.setStatus(DAVStatus.FORBIDDEN);
684                  return;
685              }
686          } catch (Exception e) {
687              LOGGER.warn(e.getLocalizedMessage(), e);
688              res.setStatus(DAVStatus.NOT_FOUND);
689              return;
690          }
691  
692          addHeadResponseHeaders(res, user, store, resourceURI);
693      }
694  
695      /**
696       * adds <tt>Content-length</tt> and <tt>Content-type</tt> headers to the
697       * response.
698       * 
699       * @param res
700       *            the response
701       * @param user
702       *            the current user for access checks
703       * @param store
704       *            the DAVStore
705       * @param resourceURI
706       *            the requested resource
707       */
708      private void addHeadResponseHeaders(final HttpDAVServletResponse res,
709              final User user, final DAVStore store, final URI resourceURI) {
710          if (store.isResource(resourceURI)) {
711              res.setContentLength((int) store
712                      .getContentLength(resourceURI, user));
713          } else {
714              res.setContentLength(0);
715          }
716          res.setContentType(store.getContentType(resourceURI, user));
717      }
718  
719      /**
720       * POST requests are not implemented, since they are not used by WebDAV.
721       * 
722       * @param req
723       *            the current request
724       * @param res
725       *            the response
726       * @param user
727       *            the user
728       * @throws IOException
729       *             on errors sending the error
730       */
731      protected final void post(final HttpDAVServletRequest req,
732              final HttpDAVServletResponse res, final User user)
733              throws IOException {
734          res.setStatus(DAVStatus.NOT_IMPLEMENTED);
735          return;
736      }
737  
738      /**
739       * performs a PUT request.
740       * 
741       * @param req
742       *            the current request
743       * @param res
744       *            the response
745       * @param user
746       *            the user
747       * @throws IOException
748       *             on errors
749       */
750      protected final void put(final HttpDAVServletRequest req,
751              final HttpDAVServletResponse res, final User user)
752              throws IOException {
753          DAVStore store = getConfiguration().getDAVStore();
754          URI resourceURI = getResourceURITranslated(req, store);
755          URI parentURI = resourceURI.resolve("..");
756  
757          if (!isPutAllowed(req, res, user, store, resourceURI, parentURI)) {
758              return;
759          }
760  
761          /* actually create the resource */
762          String contentType = req.getContentType();
763          if ((contentType == null) || (contentType.length() == 0)) {
764              contentType = MimeMap.getInstance().getMimeType(resourceURI);
765          }
766          InputStreamScannerChain filterChain = new InputStreamScannerChain(
767                  req.getInputStream(), contentType, user);
768          try {
769              store.setContent(resourceURI, user, filterChain, contentType,
770                      req.getCharacterEncoding(), res);
771          } catch (InacceptableStreamException e) {
772              if (LOGGER.isDebugEnabled()) {
773                  LOGGER.error(resourceURI + ": " + e.getLocalizedMessage(), e);
774              } else {
775                  LOGGER.error("{}: {}", resourceURI, e.getLocalizedMessage());
776              }
777              res.setStatus(DAVStatus.FORBIDDEN);
778              return;
779          } catch (IllegalFileNameException e) {
780              if (LOGGER.isDebugEnabled()) {
781                  LOGGER.error(resourceURI + ": " + e.getLocalizedMessage(), e);
782              } else {
783                  LOGGER.error("{}: {}", resourceURI, e.getLocalizedMessage());
784              }
785              res.setStatus(DAVStatus.CONFLICT);
786              return;
787          }
788          if (!res.isCommitted()) {
789              res.setStatus(DAVStatus.CREATED);
790          }
791      }
792  
793      /**
794       * applies preliminary checks, whether a PUT request is allowed on the given
795       * resource URI.
796       * 
797       * @param req
798       *            the request
799       * @param res
800       *            the response
801       * @param user
802       *            the current user
803       * @param store
804       *            the DAV store
805       * @param resourceURI
806       *            the resource URI
807       * @param parentURI
808       *            the parent URI
809       * @return {@code true}, if the PUT request is allowed. Otherwise
810       *         {@code false}
811       */
812      private boolean isPutAllowed(final HttpDAVServletRequest req,
813              final HttpDAVServletResponse res, final User user,
814              final DAVStore store, final URI resourceURI, final URI parentURI) {
815          if (store.objectExists(resourceURI) && store.isCollection(resourceURI)) {
816              /* no put on collections */
817              LOGGER.debug("PUT > METHOD_NOT_ALLOWED: no PUT on collections {}",
818                      resourceURI.toASCIIString());
819              res.setStatus(DAVStatus.METHOD_NOT_ALLOWED);
820              return false;
821          } else if (!store.canWrite(parentURI, user)) {
822              /* must have write access */
823              LOGGER.debug("PUT > CONFLICT: no write access to {}",
824                      resourceURI.toASCIIString());
825              res.setStatus(DAVStatus.CONFLICT);
826              return false;
827          } else if (store.objectExists(resourceURI) && !req.isOverwrite()
828                  && !store.canWrite(resourceURI, user)) {
829              /*
830               * resource already exists and overwriting is disabled and/or cannot
831               * write to resource
832               */
833              LOGGER.debug("PUT > CONFLICT: no overwriting requested for {}",
834                      resourceURI.toASCIIString());
835              res.setStatus(DAVStatus.CONFLICT);
836              return false;
837          }
838          return true;
839      }
840  
841      /**
842       * performs a DELETE request.
843       * 
844       * @param req
845       *            the current request
846       * @param res
847       *            the response
848       * @param user
849       *            the user
850       * @throws IOException
851       *             on errors
852       */
853      protected final void delete(final HttpDAVServletRequest req,
854              final HttpDAVServletResponse res, final User user)
855              throws IOException {
856          DAVStore store = getConfiguration().getDAVStore();
857          URI resourceURI = getResourceURITranslated(req, store);
858  
859          MultiStatusResponse multiStatus = new MultiStatusResponse(res);
860          deleteRecursively(multiStatus, store, user, resourceURI);
861  
862          /*
863           * if the multi-status is empty, set the status 204 (No Content), which
864           * is the default success code.
865           */
866          if (multiStatus.isEmpty()) {
867              res.setStatus(DAVStatus.NO_CONTENT);
868          } else {
869              /* send the multi-status */
870              multiStatus.closeResponse();
871          }
872      }
873  
874      /**
875       * performs the depth first traversal of the delete operation.
876       * 
877       * @param multiStatus
878       *            the multi-status response
879       * @param store
880       *            the store
881       * @param current
882       *            the URI to be processed
883       * @param user
884       *            the user
885       */
886      private void deleteRecursively(final MultiStatusResponse multiStatus,
887              final DAVStore store, final User user, final URI current) {
888          if (store.isCollection(current)) {
889              for (URI next : store.getChildren(current, user)) {
890                  deleteRecursively(multiStatus, store, user, next);
891              }
892              /*
893               * if the collection is empty now, try to delete it. If it is not
894               * empty, ignore it. Previous errors should have been recorded.
895               */
896              if (store.getChildren(current, user).isEmpty()) {
897                  deleteSingleURI(multiStatus, store, user, current);
898              }
899          } else if (store.isResource(current)) {
900              deleteSingleURI(multiStatus, store, user, current);
901          }
902          return;
903      }
904  
905      /**
906       * actually deletes an URI (resources or emtpy collections).
907       * 
908       * @param multiStatus
909       *            the multi-status response
910       * @param store
911       *            the store
912       * @param current
913       *            the URI to be processed
914       * @param user
915       *            the user
916       */
917      private void deleteSingleURI(final MultiStatusResponse multiStatus,
918              final DAVStore store, final User user, final URI current) {
919          if (store.canWrite(current, user)) {
920              try {
921                  store.removeObject(current, user);
922              } catch (IOException e) {
923                  StatusResponseElement response = new StatusResponseElement(
924                          current, store.isCollection(current),
925                          store.isResource(current));
926                  response.setStatus(DAVStatus.CONFLICT);
927                  multiStatus.add(response);
928                  LOGGER.warn("Detected conflict while trying to delete {}: {}",
929                          current.toASCIIString(), e.getLocalizedMessage());
930              }
931          } else {
932              /* the user is not allowed to delete */
933              StatusResponseElement response = new StatusResponseElement(current,
934                      store.isCollection(current), store.isResource(current));
935              response.setStatus(DAVStatus.FORBIDDEN);
936              multiStatus.add(response);
937              LOGGER.debug("Not allowed to delete {}", current.toASCIIString());
938          }
939      }
940  
941      /**
942       * performs a COPY request.
943       * 
944       * @param req
945       *            the current request
946       * @param res
947       *            the response
948       * @param user
949       *            the user
950       * @throws IOException
951       *             on errors
952       */
953      protected final void copy(final HttpDAVServletRequest req,
954              final HttpDAVServletResponse res, final User user)
955              throws IOException {
956          DAVStore store = getConfiguration().getDAVStore();
957          URI sourceURI = getResourceURITranslated(req, store);
958          URI destinationURI = getResourceURITranslated(getDAVServletPath(req),
959                  req.getDestinationURI(), store);
960  
961          /* preliminary checks */
962          if (!preliminaryChecksCopyOrMove(req, res, user, store, sourceURI,
963                  destinationURI)) {
964              return;
965          }
966          MultiStatusResponse multiStatus = new MultiStatusResponse(res);
967  
968          /* do copy */
969          store.copy(sourceURI, destinationURI, user, multiStatus);
970  
971          if (!multiStatus.isEmpty()) {
972              multiStatus.closeResponse();
973          } else {
974              res.setStatus(DAVStatus.CREATED);
975          }
976      }
977  
978      /**
979       * preliminary checks required both by COPY and MOVE.
980       * 
981       * @param req
982       *            the current request
983       * @param res
984       *            the response
985       * @param user
986       *            the user
987       * @param store
988       *            the DAVStore
989       * @param sourceURI
990       *            the source URI
991       * @param destinationURI
992       *            the destination URI
993       * @return {@code true}, if and only if, all checks have been passed.
994       *         Otherwise {@code false}.
995       * @throws IOException
996       *             on errors
997       */
998      private boolean preliminaryChecksCopyOrMove(
999              final HttpDAVServletRequest req, final HttpDAVServletResponse res,
1000             final User user, final DAVStore store, final URI sourceURI,
1001             final URI destinationURI) throws IOException {
1002         /* source does not exist or is not readable by user */
1003         if (!store.objectExists(sourceURI) || !store.canRead(sourceURI, user)) {
1004             res.setStatus(DAVStatus.NOT_FOUND);
1005             return false;
1006         }
1007 
1008         if (store.objectExists(destinationURI)) {
1009             /* destination exists and request is overwrite "false" */
1010             if (!req.isOverwrite()) {
1011                 res.setStatus(DAVStatus.PRECONDITION_FAILED);
1012                 return false;
1013             }
1014             /* collection check failed */
1015             if (store.isCollection(destinationURI) != store
1016                     .isCollection(sourceURI)) {
1017                 res.setStatus(DAVStatus.PRECONDITION_FAILED);
1018                 return false;
1019             }
1020             /* resource check failed */
1021             if (store.isResource(destinationURI) != store.isResource(sourceURI)) {
1022                 res.setStatus(DAVStatus.PRECONDITION_FAILED);
1023                 return false;
1024             }
1025             /* destination is not writable for user */
1026             if (!store.canWrite(destinationURI, user)) {
1027                 res.setStatus(DAVStatus.FORBIDDEN);
1028                 return false;
1029             }
1030         }
1031 
1032         /* check for parent */
1033         URI parentURI = store.enforceTailingSlash(destinationURI).resolve("..");
1034         if (!store.objectExists(parentURI)) {
1035             /*
1036              * the object cannot be created at the URI until one or more
1037              * intermediate collections have been created
1038              */
1039             res.setStatus(DAVStatus.CONFLICT);
1040             return false;
1041         }
1042 
1043         return true;
1044     }
1045 
1046     /**
1047      * performs a MOVE request.
1048      * 
1049      * @param req
1050      *            the current request
1051      * @param res
1052      *            the response
1053      * @param user
1054      *            the user
1055      * @throws IOException
1056      *             on errors
1057      */
1058     protected final void move(final HttpDAVServletRequest req,
1059             final HttpDAVServletResponse res, final User user)
1060             throws IOException {
1061         DAVStore store = getConfiguration().getDAVStore();
1062         URI sourceURI = getResourceURITranslated(req, store);
1063         URI destinationURI = getResourceURITranslated(getDAVServletPath(req),
1064                 req.getDestinationURI(), store);
1065 
1066         /* preliminary checks */
1067         if (!preliminaryChecksCopyOrMove(req, res, user, store, sourceURI,
1068                 destinationURI)) {
1069             return;
1070         }
1071         MultiStatusResponse multiStatus = new MultiStatusResponse(res);
1072 
1073         /* do copy */
1074         store.move(sourceURI, destinationURI, user, multiStatus);
1075 
1076         if (!multiStatus.isEmpty()) {
1077             multiStatus.closeResponse();
1078         } else {
1079             res.setStatus(DAVStatus.CREATED);
1080         }
1081     }
1082 
1083     /**
1084      * returns the path to the DAVServlet.
1085      * 
1086      * @param req
1087      *            the current request
1088      * @return the path to the servlet
1089      */
1090     protected final String getDAVServletPath(final HttpServletRequest req) {
1091         return req.getContextPath() + req.getServletPath();
1092     }
1093 
1094 }
1095