KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > catalina > servlets > WebdavServlet


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18
19 package org.apache.catalina.servlets;
20
21
22 import java.io.IOException JavaDoc;
23 import java.io.StringWriter JavaDoc;
24 import java.io.Writer JavaDoc;
25 import java.security.MessageDigest JavaDoc;
26 import java.security.NoSuchAlgorithmException JavaDoc;
27 import java.text.SimpleDateFormat JavaDoc;
28 import java.util.Date JavaDoc;
29 import java.util.Enumeration JavaDoc;
30 import java.util.Hashtable JavaDoc;
31 import java.util.Stack JavaDoc;
32 import java.util.TimeZone JavaDoc;
33 import java.util.Vector JavaDoc;
34
35 import javax.naming.NameClassPair JavaDoc;
36 import javax.naming.NamingEnumeration JavaDoc;
37 import javax.naming.NamingException JavaDoc;
38 import javax.naming.directory.DirContext JavaDoc;
39 import javax.servlet.ServletException JavaDoc;
40 import javax.servlet.UnavailableException JavaDoc;
41 import javax.servlet.http.HttpServletRequest JavaDoc;
42 import javax.servlet.http.HttpServletResponse JavaDoc;
43 import javax.xml.parsers.DocumentBuilder JavaDoc;
44 import javax.xml.parsers.DocumentBuilderFactory JavaDoc;
45 import javax.xml.parsers.ParserConfigurationException JavaDoc;
46
47 import org.apache.catalina.util.DOMWriter;
48 import org.apache.catalina.util.MD5Encoder;
49 import org.apache.catalina.util.RequestUtil;
50 import org.apache.catalina.util.XMLWriter;
51 import org.apache.naming.resources.CacheEntry;
52 import org.apache.naming.resources.Resource;
53 import org.apache.naming.resources.ResourceAttributes;
54 import org.apache.tomcat.util.http.FastHttpDateFormat;
55 import org.w3c.dom.Document JavaDoc;
56 import org.w3c.dom.Element JavaDoc;
57 import org.w3c.dom.Node JavaDoc;
58 import org.w3c.dom.NodeList JavaDoc;
59 import org.xml.sax.InputSource JavaDoc;
60 import org.xml.sax.SAXException JavaDoc;
61
62
63
64 /**
65  * Servlet which adds support for WebDAV level 2. All the basic HTTP requests
66  * are handled by the DefaultServlet.
67  *
68  * @author Remy Maucherat
69  * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
70  */

71
72 public class WebdavServlet
73     extends DefaultServlet {
74
75
76     // -------------------------------------------------------------- Constants
77

78
79     private static final String JavaDoc METHOD_HEAD = "HEAD";
80     private static final String JavaDoc METHOD_PROPFIND = "PROPFIND";
81     private static final String JavaDoc METHOD_PROPPATCH = "PROPPATCH";
82     private static final String JavaDoc METHOD_MKCOL = "MKCOL";
83     private static final String JavaDoc METHOD_COPY = "COPY";
84     private static final String JavaDoc METHOD_MOVE = "MOVE";
85     private static final String JavaDoc METHOD_LOCK = "LOCK";
86     private static final String JavaDoc METHOD_UNLOCK = "UNLOCK";
87
88
89     /**
90      * Default depth is infite.
91      */

92     private static final int INFINITY = 3; // To limit tree browsing a bit
93

94
95     /**
96      * PROPFIND - Specify a property mask.
97      */

98     private static final int FIND_BY_PROPERTY = 0;
99
100
101     /**
102      * PROPFIND - Display all properties.
103      */

104     private static final int FIND_ALL_PROP = 1;
105
106
107     /**
108      * PROPFIND - Return property names.
109      */

110     private static final int FIND_PROPERTY_NAMES = 2;
111
112
113     /**
114      * Create a new lock.
115      */

116     private static final int LOCK_CREATION = 0;
117
118
119     /**
120      * Refresh lock.
121      */

122     private static final int LOCK_REFRESH = 1;
123
124
125     /**
126      * Default lock timeout value.
127      */

128     private static final int DEFAULT_TIMEOUT = 3600;
129
130
131     /**
132      * Maximum lock timeout.
133      */

134     private static final int MAX_TIMEOUT = 604800;
135
136
137     /**
138      * Default namespace.
139      */

140     protected static final String JavaDoc DEFAULT_NAMESPACE = "DAV:";
141
142
143     /**
144      * Simple date format for the creation date ISO representation (partial).
145      */

146     protected static final SimpleDateFormat JavaDoc creationDateFormat =
147         new SimpleDateFormat JavaDoc("yyyy-MM-dd'T'HH:mm:ss'Z'");
148
149
150      /**
151      * MD5 message digest provider.
152      */

153     protected static MessageDigest JavaDoc md5Helper;
154
155
156     /**
157      * The MD5 helper object for this class.
158      */

159     protected static final MD5Encoder md5Encoder = new MD5Encoder();
160
161
162
163     static {
164         creationDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
165     }
166
167
168     // ----------------------------------------------------- Instance Variables
169

170
171     /**
172      * Repository of the locks put on single resources.
173      * <p>
174      * Key : path <br>
175      * Value : LockInfo
176      */

177     private Hashtable JavaDoc resourceLocks = new Hashtable JavaDoc();
178
179
180     /**
181      * Repository of the lock-null resources.
182      * <p>
183      * Key : path of the collection containing the lock-null resource<br>
184      * Value : Vector of lock-null resource which are members of the
185      * collection. Each element of the Vector is the path associated with
186      * the lock-null resource.
187      */

188     private Hashtable JavaDoc lockNullResources = new Hashtable JavaDoc();
189
190
191     /**
192      * Vector of the heritable locks.
193      * <p>
194      * Key : path <br>
195      * Value : LockInfo
196      */

197     private Vector JavaDoc collectionLocks = new Vector JavaDoc();
198
199
200     /**
201      * Secret information used to generate reasonably secure lock ids.
202      */

203     private String JavaDoc secret = "catalina";
204
205
206     // --------------------------------------------------------- Public Methods
207

208
209     /**
210      * Initialize this servlet.
211      */

212     public void init()
213         throws ServletException JavaDoc {
214
215         super.init();
216
217         if (getServletConfig().getInitParameter("secret") != null)
218             secret = getServletConfig().getInitParameter("secret");
219
220         // Load the MD5 helper used to calculate signatures.
221
try {
222             md5Helper = MessageDigest.getInstance("MD5");
223         } catch (NoSuchAlgorithmException JavaDoc e) {
224             throw new UnavailableException JavaDoc("No MD5");
225         }
226
227     }
228
229
230     // ------------------------------------------------------ Protected Methods
231

232
233     /**
234      * Return JAXP document builder instance.
235      */

236     protected DocumentBuilder JavaDoc getDocumentBuilder()
237         throws ServletException JavaDoc {
238         DocumentBuilder JavaDoc documentBuilder = null;
239         DocumentBuilderFactory JavaDoc documentBuilderFactory = null;
240         try {
241             documentBuilderFactory = DocumentBuilderFactory.newInstance();
242             documentBuilderFactory.setNamespaceAware(true);
243             documentBuilder = documentBuilderFactory.newDocumentBuilder();
244         } catch(ParserConfigurationException JavaDoc e) {
245             throw new ServletException JavaDoc
246                 (sm.getString("webdavservlet.jaxpfailed"));
247         }
248         return documentBuilder;
249     }
250
251
252     /**
253      * Handles the special WebDAV methods.
254      */

255     protected void service(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc resp)
256         throws ServletException JavaDoc, IOException JavaDoc {
257
258         String JavaDoc method = req.getMethod();
259
260         if (debug > 0) {
261             String JavaDoc path = getRelativePath(req);
262             log("[" + method + "] " + path);
263         }
264
265         if (method.equals(METHOD_PROPFIND)) {
266             doPropfind(req, resp);
267         } else if (method.equals(METHOD_PROPPATCH)) {
268             doProppatch(req, resp);
269         } else if (method.equals(METHOD_MKCOL)) {
270             doMkcol(req, resp);
271         } else if (method.equals(METHOD_COPY)) {
272             doCopy(req, resp);
273         } else if (method.equals(METHOD_MOVE)) {
274             doMove(req, resp);
275         } else if (method.equals(METHOD_LOCK)) {
276             doLock(req, resp);
277         } else if (method.equals(METHOD_UNLOCK)) {
278             doUnlock(req, resp);
279         } else {
280             // DefaultServlet processing
281
super.service(req, resp);
282         }
283
284     }
285
286
287     /**
288      * Check if the conditions specified in the optional If headers are
289      * satisfied.
290      *
291      * @param request The servlet request we are processing
292      * @param response The servlet response we are creating
293      * @param resourceAttributes The resource information
294      * @return boolean true if the resource meets all the specified conditions,
295      * and false if any of the conditions is not satisfied, in which case
296      * request processing is stopped
297      */

298     protected boolean checkIfHeaders(HttpServletRequest JavaDoc request,
299                                      HttpServletResponse JavaDoc response,
300                                      ResourceAttributes resourceAttributes)
301         throws IOException JavaDoc {
302
303         if (!super.checkIfHeaders(request, response, resourceAttributes))
304             return false;
305
306         // TODO : Checking the WebDAV If header
307
return true;
308
309     }
310
311
312     /**
313      * OPTIONS Method.
314      *
315      * @param req The request
316      * @param resp The response
317      * @throws ServletException If an error occurs
318      * @throws IOException If an IO error occurs
319      */

320     protected void doOptions(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc resp)
321         throws ServletException JavaDoc, IOException JavaDoc {
322
323         resp.addHeader("DAV", "1,2");
324
325         StringBuffer JavaDoc methodsAllowed = determineMethodsAllowed(resources,
326                                                               req);
327
328         resp.addHeader("Allow", methodsAllowed.toString());
329         resp.addHeader("MS-Author-Via", "DAV");
330
331     }
332
333
334     /**
335      * PROPFIND Method.
336      */

337     protected void doPropfind(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc resp)
338         throws ServletException JavaDoc, IOException JavaDoc {
339
340         if (!listings) {
341             // Get allowed methods
342
StringBuffer JavaDoc methodsAllowed = determineMethodsAllowed(resources,
343                                                                   req);
344
345             resp.addHeader("Allow", methodsAllowed.toString());
346             resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
347             return;
348         }
349
350         String JavaDoc path = getRelativePath(req);
351         if (path.endsWith("/"))
352             path = path.substring(0, path.length() - 1);
353
354         if ((path.toUpperCase().startsWith("/WEB-INF")) ||
355             (path.toUpperCase().startsWith("/META-INF"))) {
356             resp.sendError(WebdavStatus.SC_FORBIDDEN);
357             return;
358         }
359
360         // Properties which are to be displayed.
361
Vector JavaDoc properties = null;
362         // Propfind depth
363
int depth = INFINITY;
364         // Propfind type
365
int type = FIND_ALL_PROP;
366
367         String JavaDoc depthStr = req.getHeader("Depth");
368
369         if (depthStr == null) {
370             depth = INFINITY;
371         } else {
372             if (depthStr.equals("0")) {
373                 depth = 0;
374             } else if (depthStr.equals("1")) {
375                 depth = 1;
376             } else if (depthStr.equals("infinity")) {
377                 depth = INFINITY;
378             }
379         }
380
381         Node JavaDoc propNode = null;
382
383         DocumentBuilder JavaDoc documentBuilder = getDocumentBuilder();
384
385         try {
386             Document JavaDoc document = documentBuilder.parse
387                 (new InputSource JavaDoc(req.getInputStream()));
388
389             // Get the root element of the document
390
Element JavaDoc rootElement = document.getDocumentElement();
391             NodeList JavaDoc childList = rootElement.getChildNodes();
392
393             for (int i=0; i < childList.getLength(); i++) {
394                 Node JavaDoc currentNode = childList.item(i);
395                 switch (currentNode.getNodeType()) {
396                 case Node.TEXT_NODE:
397                     break;
398                 case Node.ELEMENT_NODE:
399                     if (currentNode.getNodeName().endsWith("prop")) {
400                         type = FIND_BY_PROPERTY;
401                         propNode = currentNode;
402                     }
403                     if (currentNode.getNodeName().endsWith("propname")) {
404                         type = FIND_PROPERTY_NAMES;
405                     }
406                     if (currentNode.getNodeName().endsWith("allprop")) {
407                         type = FIND_ALL_PROP;
408                     }
409                     break;
410                 }
411             }
412         } catch (SAXException JavaDoc e) {
413             // Most likely there was no content : we use the defaults.
414
} catch (IOException JavaDoc e) {
415             // Most likely there was no content : we use the defaults.
416
}
417
418         if (type == FIND_BY_PROPERTY) {
419             properties = new Vector JavaDoc();
420             NodeList JavaDoc childList = propNode.getChildNodes();
421
422             for (int i=0; i < childList.getLength(); i++) {
423                 Node JavaDoc currentNode = childList.item(i);
424                 switch (currentNode.getNodeType()) {
425                 case Node.TEXT_NODE:
426                     break;
427                 case Node.ELEMENT_NODE:
428                     String JavaDoc nodeName = currentNode.getNodeName();
429                     String JavaDoc propertyName = null;
430                     if (nodeName.indexOf(':') != -1) {
431                         propertyName = nodeName.substring
432                             (nodeName.indexOf(':') + 1);
433                     } else {
434                         propertyName = nodeName;
435                     }
436                     // href is a live property which is handled differently
437
properties.addElement(propertyName);
438                     break;
439                 }
440             }
441
442         }
443
444         boolean exists = true;
445         Object JavaDoc object = null;
446         try {
447             object = resources.lookup(path);
448         } catch (NamingException JavaDoc e) {
449             exists = false;
450             int slash = path.lastIndexOf('/');
451             if (slash != -1) {
452                 String JavaDoc parentPath = path.substring(0, slash);
453                 Vector JavaDoc currentLockNullResources =
454                     (Vector JavaDoc) lockNullResources.get(parentPath);
455                 if (currentLockNullResources != null) {
456                     Enumeration JavaDoc lockNullResourcesList =
457                         currentLockNullResources.elements();
458                     while (lockNullResourcesList.hasMoreElements()) {
459                         String JavaDoc lockNullPath = (String JavaDoc)
460                             lockNullResourcesList.nextElement();
461                         if (lockNullPath.equals(path)) {
462                             resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
463                             resp.setContentType("text/xml; charset=UTF-8");
464                             // Create multistatus object
465
XMLWriter generatedXML =
466                                 new XMLWriter(resp.getWriter());
467                             generatedXML.writeXMLHeader();
468                             generatedXML.writeElement
469                                 (null, "multistatus"
470                                  + generateNamespaceDeclarations(),
471                                  XMLWriter.OPENING);
472                             parseLockNullProperties
473                                 (req, generatedXML, lockNullPath, type,
474                                  properties);
475                             generatedXML.writeElement(null, "multistatus",
476                                                       XMLWriter.CLOSING);
477                             generatedXML.sendData();
478                             return;
479                         }
480                     }
481                 }
482             }
483         }
484
485         if (!exists) {
486             resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
487             return;
488         }
489
490         resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
491
492         resp.setContentType("text/xml; charset=UTF-8");
493
494         // Create multistatus object
495
XMLWriter generatedXML = new XMLWriter(resp.getWriter());
496         generatedXML.writeXMLHeader();
497
498         generatedXML.writeElement(null, "multistatus"
499                                   + generateNamespaceDeclarations(),
500                                   XMLWriter.OPENING);
501
502         if (depth == 0) {
503             parseProperties(req, generatedXML, path, type,
504                             properties);
505         } else {
506             // The stack always contains the object of the current level
507
Stack JavaDoc stack = new Stack JavaDoc();
508             stack.push(path);
509
510             // Stack of the objects one level below
511
Stack JavaDoc stackBelow = new Stack JavaDoc();
512
513             while ((!stack.isEmpty()) && (depth >= 0)) {
514
515                 String JavaDoc currentPath = (String JavaDoc) stack.pop();
516                 parseProperties(req, generatedXML, currentPath,
517                                 type, properties);
518
519                 try {
520                     object = resources.lookup(currentPath);
521                 } catch (NamingException JavaDoc e) {
522                     continue;
523                 }
524
525                 if ((object instanceof DirContext JavaDoc) && (depth > 0)) {
526
527                     try {
528                         NamingEnumeration JavaDoc enumeration = resources.list(currentPath);
529                         while (enumeration.hasMoreElements()) {
530                             NameClassPair JavaDoc ncPair =
531                                 (NameClassPair JavaDoc) enumeration.nextElement();
532                             String JavaDoc newPath = currentPath;
533                             if (!(newPath.endsWith("/")))
534                                 newPath += "/";
535                             newPath += ncPair.getName();
536                             stackBelow.push(newPath);
537                         }
538                     } catch (NamingException JavaDoc e) {
539                         resp.sendError
540                             (HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
541                              path);
542                         return;
543                     }
544
545                     // Displaying the lock-null resources present in that
546
// collection
547
String JavaDoc lockPath = currentPath;
548                     if (lockPath.endsWith("/"))
549                         lockPath =
550                             lockPath.substring(0, lockPath.length() - 1);
551                     Vector JavaDoc currentLockNullResources =
552                         (Vector JavaDoc) lockNullResources.get(lockPath);
553                     if (currentLockNullResources != null) {
554                         Enumeration JavaDoc lockNullResourcesList =
555                             currentLockNullResources.elements();
556                         while (lockNullResourcesList.hasMoreElements()) {
557                             String JavaDoc lockNullPath = (String JavaDoc)
558                                 lockNullResourcesList.nextElement();
559                             parseLockNullProperties
560                                 (req, generatedXML, lockNullPath, type,
561                                  properties);
562                         }
563                     }
564
565                 }
566
567                 if (stack.isEmpty()) {
568                     depth--;
569                     stack = stackBelow;
570                     stackBelow = new Stack JavaDoc();
571                 }
572
573                 generatedXML.sendData();
574
575             }
576         }
577
578         generatedXML.writeElement(null, "multistatus",
579                                   XMLWriter.CLOSING);
580
581         generatedXML.sendData();
582
583     }
584
585
586     /**
587      * PROPPATCH Method.
588      */

589     protected void doProppatch(HttpServletRequest JavaDoc req,
590                                HttpServletResponse JavaDoc resp)
591         throws ServletException JavaDoc, IOException JavaDoc {
592
593         if (readOnly) {
594             resp.sendError(WebdavStatus.SC_FORBIDDEN);
595             return;
596         }
597
598         if (isLocked(req)) {
599             resp.sendError(WebdavStatus.SC_LOCKED);
600             return;
601         }
602
603         resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
604
605     }
606
607
608     /**
609      * MKCOL Method.
610      */

611     protected void doMkcol(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc resp)
612         throws ServletException JavaDoc, IOException JavaDoc {
613
614         if (readOnly) {
615             resp.sendError(WebdavStatus.SC_FORBIDDEN);
616             return;
617         }
618
619         if (isLocked(req)) {
620             resp.sendError(WebdavStatus.SC_LOCKED);
621             return;
622         }
623
624         String JavaDoc path = getRelativePath(req);
625
626         if ((path.toUpperCase().startsWith("/WEB-INF")) ||
627             (path.toUpperCase().startsWith("/META-INF"))) {
628             resp.sendError(WebdavStatus.SC_FORBIDDEN);
629             return;
630         }
631
632         boolean exists = true;
633         Object JavaDoc object = null;
634         try {
635             object = resources.lookup(path);
636         } catch (NamingException JavaDoc e) {
637             exists = false;
638         }
639
640         // Can't create a collection if a resource already exists at the given
641
// path
642
if (exists) {
643             // Get allowed methods
644
StringBuffer JavaDoc methodsAllowed = determineMethodsAllowed(resources,
645                                                                   req);
646
647             resp.addHeader("Allow", methodsAllowed.toString());
648
649             resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
650             return;
651         }
652
653         if (req.getInputStream().available() > 0) {
654             DocumentBuilder JavaDoc documentBuilder = getDocumentBuilder();
655             try {
656                 Document JavaDoc document = documentBuilder.parse
657                     (new InputSource JavaDoc(req.getInputStream()));
658                 // TODO : Process this request body
659
resp.sendError(WebdavStatus.SC_NOT_IMPLEMENTED);
660                 return;
661
662             } catch(SAXException JavaDoc saxe) {
663                 // Parse error - assume invalid content
664
resp.sendError(WebdavStatus.SC_BAD_REQUEST);
665                 return;
666             }
667         }
668
669         boolean result = true;
670         try {
671             resources.createSubcontext(path);
672         } catch (NamingException JavaDoc e) {
673             result = false;
674         }
675
676         if (!result) {
677             resp.sendError(WebdavStatus.SC_CONFLICT,
678                            WebdavStatus.getStatusText
679                            (WebdavStatus.SC_CONFLICT));
680         } else {
681             resp.setStatus(WebdavStatus.SC_CREATED);
682             // Removing any lock-null resource which would be present
683
lockNullResources.remove(path);
684         }
685
686     }
687
688
689     /**
690      * DELETE Method.
691      */

692     protected void doDelete(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc resp)
693         throws ServletException JavaDoc, IOException JavaDoc {
694
695         if (readOnly) {
696             resp.sendError(WebdavStatus.SC_FORBIDDEN);
697             return;
698         }
699
700         if (isLocked(req)) {
701             resp.sendError(WebdavStatus.SC_LOCKED);
702             return;
703         }
704
705         deleteResource(req, resp);
706
707     }
708
709
710     /**
711      * Process a POST request for the specified resource.
712      *
713      * @param req The servlet request we are processing
714      * @param resp The servlet response we are creating
715      *
716      * @exception IOException if an input/output error occurs
717      * @exception ServletException if a servlet-specified error occurs
718      */

719     protected void doPut(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc resp)
720         throws ServletException JavaDoc, IOException JavaDoc {
721
722         if (isLocked(req)) {
723             resp.sendError(WebdavStatus.SC_LOCKED);
724             return;
725         }
726
727         super.doPut(req, resp);
728
729         String JavaDoc path = getRelativePath(req);
730
731         // Removing any lock-null resource which would be present
732
lockNullResources.remove(path);
733
734     }
735
736     /**
737      * COPY Method.
738      */

739     protected void doCopy(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc resp)
740         throws ServletException JavaDoc, IOException JavaDoc {
741
742         if (readOnly) {
743             resp.sendError(WebdavStatus.SC_FORBIDDEN);
744             return;
745         }
746
747         copyResource(req, resp);
748
749     }
750
751
752     /**
753      * MOVE Method.
754      */

755     protected void doMove(HttpServletRequest JavaDoc req, HttpServletResponse JavaDoc resp)
756         throws ServletException JavaDoc, IOException JavaDoc {
757
758         if (readOnly) {
759             resp.sendError(WebdavStatus.SC_FORBIDDEN);
760             return;
761         }
762
763  &nb