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;
23 import java.io.StringReader;
24 import java.io.StringWriter;
25 import java.io.Writer;
26 import java.security.MessageDigest;
27 import java.security.NoSuchAlgorithmException;
28 import java.text.SimpleDateFormat;
29 import java.util.Date;
30 import java.util.Enumeration;
31 import java.util.Hashtable;
32 import java.util.Stack;
33 import java.util.TimeZone;
34 import java.util.Vector;
35
36 import javax.naming.NameClassPair;
37 import javax.naming.NamingEnumeration;
38 import javax.naming.NamingException;
39 import javax.naming.directory.DirContext;
40 import javax.servlet.ServletContext;
41 import javax.servlet.ServletException;
42 import javax.servlet.UnavailableException;
43 import javax.servlet.http.HttpServletRequest;
44 import javax.servlet.http.HttpServletResponse;
45 import javax.xml.parsers.DocumentBuilder;
46 import javax.xml.parsers.DocumentBuilderFactory;
47 import javax.xml.parsers.ParserConfigurationException;
48
49 import org.apache.catalina.Globals;
50 import org.apache.catalina.util.DOMWriter;
51 import org.apache.catalina.util.MD5Encoder;
52 import org.apache.catalina.util.RequestUtil;
53 import org.apache.catalina.util.XMLWriter;
54 import org.apache.naming.resources.CacheEntry;
55 import org.apache.naming.resources.Resource;
56 import org.apache.naming.resources.ResourceAttributes;
57 import org.apache.tomcat.util.http.FastHttpDateFormat;
58 import org.w3c.dom.Document;
59 import org.w3c.dom.Element;
60 import org.w3c.dom.Node;
61 import org.w3c.dom.NodeList;
62 import org.xml.sax.EntityResolver;
63 import org.xml.sax.InputSource;
64 import org.xml.sax.SAXException;
65
66
67
68 /**
69 * Servlet which adds support for WebDAV level 2. All the basic HTTP requests
70 * are handled by the DefaultServlet. The WebDAVServlet must not be used as the
71 * default servlet (ie mapped to '/') as it will not work in this configuration.
72 * To enable WebDAV for a context add the following to web.xml:<br/><code>
73 * <servlet><br/>
74 * <servlet-name>webdav</servlet-name><br/>
75 * <servlet-class>org.apache.catalina.servlets.WebdavServlet</servlet-class><br/>
76 * <init-param><br/>
77 * <param-name>debug</param-name><br/>
78 * <param-value>0</param-value><br/>
79 * </init-param><br/>
80 * <init-param><br/>
81 * <param-name>listings</param-name><br/>
82 * <param-value>true</param-value><br/>
83 * </init-param><br/>
84 * </servlet><br/>
85 * <servlet-mapping><br/>
86 * <servlet-name>webdav</servlet-name><br/>
87 * <url-pattern>/*</url-pattern><br/>
88 * </servlet-mapping>
89 * </code>
90 * <p/>
91 * This will enable read only access. To enable read-write access add:<br/>
92 * <code>
93 * <init-param><br/>
94 * <param-name>readonly</param-name><br/>
95 * <param-value>false</param-value><br/>
96 * </init-param><br/>
97 * </code>
98 * <p/>
99 * To make the content editable via a different URL, using the following
100 * mapping:<br/>
101 * <code>
102 * <servlet-mapping><br/>
103 * <servlet-name>webdav</servlet-name><br/>
104 * <url-pattern>/webdavedit/*</url-pattern><br/>
105 * </servlet-mapping>
106 * </code>
107 * <p/>
108 * Don't forget to secure access appropriately to the editing URLs. With this
109 * configuration the context will be accessible to normal users as before. Those
110 * users with the necessary access will be able to edit content available via
111 * http://host:port/context/content using
112 * http://host:port/context/webdavedit/content
113 *
114 * @author Remy Maucherat
115 * @version $Revision: 600268 $ $Date: 2007-12-02 12:09:55 +0100 (dim., 02 déc. 2007) $
116 */
117
118 public class WebdavServlet
119 extends DefaultServlet {
120
121
122 // -------------------------------------------------------------- Constants
123
124
125 private static final String METHOD_HEAD = "HEAD";
126 private static final String METHOD_PROPFIND = "PROPFIND";
127 private static final String METHOD_PROPPATCH = "PROPPATCH";
128 private static final String METHOD_MKCOL = "MKCOL";
129 private static final String METHOD_COPY = "COPY";
130 private static final String METHOD_MOVE = "MOVE";
131 private static final String METHOD_LOCK = "LOCK";
132 private static final String METHOD_UNLOCK = "UNLOCK";
133
134
135 /**
136 * Default depth is infite.
137 */
138 private static final int INFINITY = 3; // To limit tree browsing a bit
139
140
141 /**
142 * PROPFIND - Specify a property mask.
143 */
144 private static final int FIND_BY_PROPERTY = 0;
145
146
147 /**
148 * PROPFIND - Display all properties.
149 */
150 private static final int FIND_ALL_PROP = 1;
151
152
153 /**
154 * PROPFIND - Return property names.
155 */
156 private static final int FIND_PROPERTY_NAMES = 2;
157
158
159 /**
160 * Create a new lock.
161 */
162 private static final int LOCK_CREATION = 0;
163
164
165 /**
166 * Refresh lock.
167 */
168 private static final int LOCK_REFRESH = 1;
169
170
171 /**
172 * Default lock timeout value.
173 */
174 private static final int DEFAULT_TIMEOUT = 3600;
175
176
177 /**
178 * Maximum lock timeout.
179 */
180 private static final int MAX_TIMEOUT = 604800;
181
182
183 /**
184 * Default namespace.
185 */
186 protected static final String DEFAULT_NAMESPACE = "DAV:";
187
188
189 /**
190 * Simple date format for the creation date ISO representation (partial).
191 */
192 protected static final SimpleDateFormat creationDateFormat =
193 new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
194
195
196 /**
197 * MD5 message digest provider.
198 */
199 protected static MessageDigest md5Helper;
200
201
202 /**
203 * The MD5 helper object for this class.
204 */
205 protected static final MD5Encoder md5Encoder = new MD5Encoder();
206
207
208
209 static {
210 creationDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
211 }
212
213
214 // ----------------------------------------------------- Instance Variables
215
216
217 /**
218 * Repository of the locks put on single resources.
219 * <p>
220 * Key : path <br>
221 * Value : LockInfo
222 */
223 private Hashtable<String,LockInfo> resourceLocks =
224 new Hashtable<String,LockInfo>();
225
226
227 /**
228 * Repository of the lock-null resources.
229 * <p>
230 * Key : path of the collection containing the lock-null resource<br>
231 * Value : Vector of lock-null resource which are members of the
232 * collection. Each element of the Vector is the path associated with
233 * the lock-null resource.
234 */
235 private Hashtable<String,Vector<String>> lockNullResources =
236 new Hashtable<String,Vector<String>>();
237
238
239 /**
240 * Vector of the heritable locks.
241 * <p>
242 * Key : path <br>
243 * Value : LockInfo
244 */
245 private Vector<LockInfo> collectionLocks = new Vector<LockInfo>();
246
247
248 /**
249 * Secret information used to generate reasonably secure lock ids.
250 */
251 private String secret = "catalina";
252
253
254 // --------------------------------------------------------- Public Methods
255
256
257 /**
258 * Initialize this servlet.
259 */
260 public void init()
261 throws ServletException {
262
263 super.init();
264
265 if (getServletConfig().getInitParameter("secret") != null)
266 secret = getServletConfig().getInitParameter("secret");
267
268 // Load the MD5 helper used to calculate signatures.
269 try {
270 md5Helper = MessageDigest.getInstance("MD5");
271 } catch (NoSuchAlgorithmException e) {
272 throw new UnavailableException("No MD5");
273 }
274
275 }
276
277
278 // ------------------------------------------------------ Protected Methods
279
280
281 /**
282 * Return JAXP document builder instance.
283 */
284 protected DocumentBuilder getDocumentBuilder()
285 throws ServletException {
286 DocumentBuilder documentBuilder = null;
287 DocumentBuilderFactory documentBuilderFactory = null;
288 try {
289 documentBuilderFactory = DocumentBuilderFactory.newInstance();
290 documentBuilderFactory.setNamespaceAware(true);
291 documentBuilderFactory.setExpandEntityReferences(false);
292 documentBuilder = documentBuilderFactory.newDocumentBuilder();
293 documentBuilder.setEntityResolver(
294 new WebdavResolver(this.getServletContext()));
295 } catch(ParserConfigurationException e) {
296 throw new ServletException
297 (sm.getString("webdavservlet.jaxpfailed"));
298 }
299 return documentBuilder;
300 }
301
302
303 /**
304 * Handles the special WebDAV methods.
305 */
306 protected void service(HttpServletRequest req, HttpServletResponse resp)
307 throws ServletException, IOException {
308
309 String method = req.getMethod();
310
311 if (debug > 0) {
312 String path = getRelativePath(req);
313 log("[" + method + "] " + path);
314 }
315
316 if (method.equals(METHOD_PROPFIND)) {
317 doPropfind(req, resp);
318 } else if (method.equals(METHOD_PROPPATCH)) {
319 doProppatch(req, resp);
320 } else if (method.equals(METHOD_MKCOL)) {
321 doMkcol(req, resp);
322 } else if (method.equals(METHOD_COPY)) {
323 doCopy(req, resp);
324 } else if (method.equals(METHOD_MOVE)) {
325 doMove(req, resp);
326 } else if (method.equals(METHOD_LOCK)) {
327 doLock(req, resp);
328 } else if (method.equals(METHOD_UNLOCK)) {
329 doUnlock(req, resp);
330 } else {
331 // DefaultServlet processing
332 super.service(req, resp);
333 }
334
335 }
336
337
338 /**
339 * Check if the conditions specified in the optional If headers are
340 * satisfied.
341 *
342 * @param request The servlet request we are processing
343 * @param response The servlet response we are creating
344 * @param resourceAttributes The resource information
345 * @return boolean true if the resource meets all the specified conditions,
346 * and false if any of the conditions is not satisfied, in which case
347 * request processing is stopped
348 */
349 protected boolean checkIfHeaders(HttpServletRequest request,
350 HttpServletResponse response,
351 ResourceAttributes resourceAttributes)
352 throws IOException {
353
354 if (!super.checkIfHeaders(request, response, resourceAttributes))
355 return false;
356
357 // TODO : Checking the WebDAV If header
358 return true;
359
360 }
361
362
363 /**
364 * Override the DefaultServlet implementation and only use the PathInfo. If
365 * the ServletPath is non-null, it will be because the WebDAV servlet has
366 * been mapped to a url other than /* to configure editing at different url
367 * than normal viewing.
368 *
369 * @param request The servlet request we are processing
370 */
371 protected String getRelativePath(HttpServletRequest request) {
372 // Are we being processed by a RequestDispatcher.include()?
373 if (request.getAttribute(Globals.INCLUDE_REQUEST_URI_ATTR) != null) {
374 String result = (String) request.getAttribute(
375 Globals.INCLUDE_PATH_INFO_ATTR);
376 if ((result == null) || (result.equals("")))
377 result = "/";
378 return (result);
379 }
380
381 // No, extract the desired path directly from the request
382 String result = request.getPathInfo();
383 if ((result == null) || (result.equals(""))) {
384 result = "/";
385 }
386 return (result);
387
388 }
389
390
391 /**
392 * OPTIONS Method.
393 *
394 * @param req The request
395 * @param resp The response
396 * @throws ServletException If an error occurs
397 * @throws IOException If an IO error occurs
398 */
399 protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
400 throws ServletException, IOException {
401
402 resp.addHeader("DAV", "1,2");
403
404 StringBuffer methodsAllowed = determineMethodsAllowed(resources,
405 req);
406
407 resp.addHeader("Allow", methodsAllowed.toString());
408 resp.addHeader("MS-Author-Via", "DAV");
409
410 }
411
412
413 /**
414 * PROPFIND Method.
415 */
416 protected void doPropfind(HttpServletRequest req, HttpServletResponse resp)
417 throws ServletException, IOException {
418
419 if (!listings) {
420 // Get allowed methods
421 StringBuffer methodsAllowed = determineMethodsAllowed(resources,
422 req);
423
424 resp.addHeader("Allow", methodsAllowed.toString());
425 resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
426 return;
427 }
428
429 String path = getRelativePath(req);
430 if (path.endsWith("/"))
431 path = path.substring(0, path.length() - 1);
432
433 if ((path.toUpperCase().startsWith("/WEB-INF")) ||
434 (path.toUpperCase().startsWith("/META-INF"))) {
435 resp.sendError(WebdavStatus.SC_FORBIDDEN);
436 return;
437 }
438
439 // Properties which are to be displayed.
440 Vector<String> properties = null;
441 // Propfind depth
442 int depth = INFINITY;
443 // Propfind type
444 int type = FIND_ALL_PROP;
445
446 String depthStr = req.getHeader("Depth");
447
448 if (depthStr == null) {
449 depth = INFINITY;
450 } else {
451 if (depthStr.equals("0")) {
452 depth = 0;
453 } else if (depthStr.equals("1")) {
454 depth = 1;
455 } else if (depthStr.equals("infinity")) {
456 depth = INFINITY;
457 }
458 }
459
460 Node propNode = null;
461
462 if (req.getInputStream().available() > 0) {
463 DocumentBuilder documentBuilder = getDocumentBuilder();
464
465 try {
466 Document document = documentBuilder.parse
467 (new InputSource(req.getInputStream()));
468
469 // Get the root element of the document
470 Element rootElement = document.getDocumentElement();
471 NodeList childList = rootElement.getChildNodes();
472
473 for (int i=0; i < childList.getLength(); i++) {
474 Node currentNode = childList.item(i);
475 switch (currentNode.getNodeType()) {
476 case Node.TEXT_NODE:
477 break;
478 case Node.ELEMENT_NODE:
479 if (currentNode.getNodeName().endsWith("prop")) {
480 type = FIND_BY_PROPERTY;
481 propNode = currentNode;
482 }
483 if (currentNode.getNodeName().endsWith("propname")) {
484 type = FIND_PROPERTY_NAMES;
485 }
486 if (currentNode.getNodeName().endsWith("allprop")) {
487 type = FIND_ALL_PROP;
488 }
489 break;
490 }
491 }
492 } catch (SAXException e) {
493 // Something went wrong - use the defaults.
494 } catch (IOException e) {
495 // Something went wrong - use the defaults.
496 }
497 }
498
499 if (type == FIND_BY_PROPERTY) {
500 properties = new Vector<String>();
501 NodeList childList = propNode.getChildNodes();
502
503 for (int i=0; i < childList.getLength(); i++) {
504 Node currentNode = childList.item(i);
505 switch (currentNode.getNodeType()) {
506 case Node.TEXT_NODE:
507 break;
508 case Node.ELEMENT_NODE:
509 String nodeName = currentNode.getNodeName();
510 String propertyName = null;
511 if (nodeName.indexOf(':') != -1) {
512 propertyName = nodeName.substring
513 (nodeName.indexOf(':') + 1);
514 } else {
515 propertyName = nodeName;
516 }
517 // href is a live property which is handled differently
518 properties.addElement(propertyName);
519 break;
520 }
521 }
522
523 }
524
525 boolean exists = true;
526 Object object = null;
527 try {
528 object = resources.lookup(path);
529 } catch (NamingException e) {
530 exists = false;
531 int slash = path.lastIndexOf('/');
532 if (slash != -1) {
533 String parentPath = path.substring(0, slash);
534 Vector currentLockNullResources =
535 (Vector) lockNullResources.get(parentPath);
536 if (currentLockNullResources != null) {
537 Enumeration lockNullResourcesList =
538 currentLockNullResources.elements();
539 while (lockNullResourcesList.hasMoreElements()) {
540 String lockNullPath = (String)
541 lockNullResourcesList.nextElement();
542 if (lockNullPath.equals(path)) {
543 resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
544 resp.setContentType("text/xml; charset=UTF-8");
545 // Create multistatus object
546 XMLWriter generatedXML =
547 new XMLWriter(resp.getWriter());
548 generatedXML.writeXMLHeader();
549 generatedXML.writeElement
550 (null, "multistatus"
551 + generateNamespaceDeclarations(),
552 XMLWriter.OPENING);
553 parseLockNullProperties
554 (req, generatedXML, lockNullPath, type,
555 properties);
556 generatedXML.writeElement(null, "multistatus",
557 XMLWriter.CLOSING);
558 generatedXML.sendData();
559 return;
560 }
561 }
562 }
563 }
564 }
565
566 if (!exists) {
567 resp.sendError(HttpServletResponse.SC_NOT_FOUND, path);
568 return;
569 }
570
571 resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
572
573 resp.setContentType("text/xml; charset=UTF-8");
574
575 // Create multistatus object
576 XMLWriter generatedXML = new XMLWriter(resp.getWriter());
577 generatedXML.writeXMLHeader();
578
579 generatedXML.writeElement(null, "multistatus"
580 + generateNamespaceDeclarations(),
581 XMLWriter.OPENING);
582
583 if (depth == 0) {
584 parseProperties(req, generatedXML, path, type,
585 properties);
586 } else {
587 // The stack always contains the object of the current level
588 Stack<String> stack = new Stack<String>();
589 stack.push(path);
590
591 // Stack of the objects one level below
592 Stack<String> stackBelow = new Stack<String>();
593
594 while ((!stack.isEmpty()) && (depth >= 0)) {
595
596 String currentPath = (String) stack.pop();
597 parseProperties(req, generatedXML, currentPath,
598 type, properties);
599
600 try {
601 object = resources.lookup(currentPath);
602 } catch (NamingException e) {
603 continue;
604 }
605
606 if ((object instanceof DirContext) && (depth > 0)) {
607
608 try {
609 NamingEnumeration enumeration = resources.list(currentPath);
610 while (enumeration.hasMoreElements()) {
611 NameClassPair ncPair =
612 (NameClassPair) enumeration.nextElement();
613 String newPath = currentPath;
614 if (!(newPath.endsWith("/")))
615 newPath += "/";
616 newPath += ncPair.getName();
617 stackBelow.push(newPath);
618 }
619 } catch (NamingException e) {
620 resp.sendError
621 (HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
622 path);
623 return;
624 }
625
626 // Displaying the lock-null resources present in that
627 // collection
628 String lockPath = currentPath;
629 if (lockPath.endsWith("/"))
630 lockPath =
631 lockPath.substring(0, lockPath.length() - 1);
632 Vector currentLockNullResources =
633 (Vector) lockNullResources.get(lockPath);
634 if (currentLockNullResources != null) {
635 Enumeration lockNullResourcesList =
636 currentLockNullResources.elements();
637 while (lockNullResourcesList.hasMoreElements()) {
638 String lockNullPath = (String)
639 lockNullResourcesList.nextElement();
640 parseLockNullProperties
641 (req, generatedXML, lockNullPath, type,
642 properties);
643 }
644 }
645
646 }
647
648 if (stack.isEmpty()) {
649 depth--;
650 stack = stackBelow;
651 stackBelow = new Stack<String>();
652 }
653
654 generatedXML.sendData();
655
656 }
657 }
658
659 generatedXML.writeElement(null, "multistatus",
660 XMLWriter.CLOSING);
661
662 generatedXML.sendData();
663
664 }
665
666
667 /**
668 * PROPPATCH Method.
669 */
670 protected void doProppatch(HttpServletRequest req,
671 HttpServletResponse resp)
672 throws ServletException, IOException {
673
674 if (readOnly) {
675 resp.sendError(WebdavStatus.SC_FORBIDDEN);
676 return;
677 }
678
679 if (isLocked(req)) {
680 resp.sendError(WebdavStatus.SC_LOCKED);
681 return;
682 }
683
684 resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
685
686 }
687
688
689 /**
690 * MKCOL Method.
691 */
692 protected void doMkcol(HttpServletRequest req, HttpServletResponse resp)
693 throws ServletException, IOException {
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 String path = getRelativePath(req);
706
707 if ((path.toUpperCase().startsWith("/WEB-INF")) ||
708 (path.toUpperCase().startsWith("/META-INF"))) {
709 resp.sendError(WebdavStatus.SC_FORBIDDEN);
710 return;
711 }
712
713 boolean exists = true;
714 Object object = null;
715 try {
716 object = resources.lookup(path);
717 } catch (NamingException e) {
718 exists = false;
719 }
720
721 // Can't create a collection if a resource already exists at the given
722 // path
723 if (exists) {
724 // Get allowed methods
725 StringBuffer methodsAllowed = determineMethodsAllowed(resources,
726 req);
727
728 resp.addHeader("Allow", methodsAllowed.toString());
729
730 resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
731 return;
732 }
733
734 if (req.getInputStream().available() > 0) {
735 DocumentBuilder documentBuilder = getDocumentBuilder();
736 try {
737 Document document = documentBuilder.parse
738 (new InputSource(req.getInputStream()));
739 // TODO : Process this request body
740 resp.sendError(WebdavStatus.SC_NOT_IMPLEMENTED);
741 return;
742
743 } catch(SAXException saxe) {
744 // Parse error - assume invalid content
745 resp.sendError(WebdavStatus.SC_BAD_REQUEST);
746 return;
747 }
748 }
749
750 boolean result = true;
751 try {
752 resources.createSubcontext(path);
753 } catch (NamingException e) {
754 result = false;
755 }
756
757 if (!result) {
758 resp.sendError(WebdavStatus.SC_CONFLICT,
759 WebdavStatus.getStatusText
760 (WebdavStatus.SC_CONFLICT));
761 } else {
762 resp.setStatus(WebdavStatus.SC_CREATED);
763 // Removing any lock-null resource which would be present
764 lockNullResources.remove(path);
765 }
766
767 }
768
769
770 /**
771 * DELETE Method.
772 */
773 protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
774 throws ServletException, IOException {
775
776 if (readOnly) {
777 resp.sendError(WebdavStatus.SC_FORBIDDEN);
778 return;
779 }
780
781 if (isLocked(req)) {
782 resp.sendError(WebdavStatus.SC_LOCKED);
783 return;
784 }
785
786 deleteResource(req, resp);
787
788 }
789
790
791 /**
792 * Process a POST request for the specified resource.
793 *
794 * @param req The servlet request we are processing
795 * @param resp The servlet response we are creating
796 *
797 * @exception IOException if an input/output error occurs
798 * @exception ServletException if a servlet-specified error occurs
799 */
800 protected void doPut(HttpServletRequest req, HttpServletResponse resp)
801 throws ServletException, IOException {
802
803 if (isLocked(req)) {
804 resp.sendError(WebdavStatus.SC_LOCKED);
805 return;
806 }
807
808 super.doPut(req, resp);
809
810 String path = getRelativePath(req);
811
812 // Removing any lock-null resource which would be present
813 lockNullResources.remove(path);
814
815 }
816
817 /**
818 * COPY Method.
819 */
820 protected void doCopy(HttpServletRequest req, HttpServletResponse resp)
821 throws ServletException, IOException {
822
823 if (readOnly) {
824 resp.sendError(WebdavStatus.SC_FORBIDDEN);
825 return;
826 }
827
828 copyResource(req, resp);
829
830 }
831
832
833 /**
834 * MOVE Method.
835 */
836 protected void doMove(HttpServletRequest req, HttpServletResponse resp)
837 throws ServletException, IOException {
838
839 if (readOnly) {
840 resp.sendError(WebdavStatus.SC_FORBIDDEN);
841 return;
842 }
843
844 if (isLocked(req)) {
845 resp.sendError(WebdavStatus.SC_LOCKED);
846 return;
847 }
848
849 String path = getRelativePath(req);
850
851 if (copyResource(req, resp)) {
852 deleteResource(path, req, resp, false);
853 }
854
855 }
856
857
858 /**
859 * LOCK Method.
860 */
861 protected void doLock(HttpServletRequest req, HttpServletResponse resp)
862 throws ServletException, IOException {
863
864 if (readOnly) {
865 resp.sendError(WebdavStatus.SC_FORBIDDEN);
866 return;
867 }
868
869 if (isLocked(req)) {
870 resp.sendError(WebdavStatus.SC_LOCKED);
871 return;
872 }
873
874 LockInfo lock = new LockInfo();
875
876 // Parsing lock request
877
878 // Parsing depth header
879
880 String depthStr = req.getHeader("Depth");
881
882 if (depthStr == null) {
883 lock.depth = INFINITY;
884 } else {
885 if (depthStr.equals("0")) {
886 lock.depth = 0;
887 } else {
888 lock.depth = INFINITY;
889 }
890 }
891
892 // Parsing timeout header
893
894 int lockDuration = DEFAULT_TIMEOUT;
895 String lockDurationStr = req.getHeader("Timeout");
896 if (lockDurationStr == null) {
897 lockDuration = DEFAULT_TIMEOUT;
898 } else {
899 int commaPos = lockDurationStr.indexOf(",");
900 // If multiple timeouts, just use the first
901 if (commaPos != -1) {
902 lockDurationStr = lockDurationStr.substring(0,commaPos);
903 }
904 if (lockDurationStr.startsWith("Second-")) {
905 lockDuration =
906 (new Integer(lockDurationStr.substring(7))).intValue();
907 } else {
908 if (lockDurationStr.equalsIgnoreCase("infinity")) {
909 lockDuration = MAX_TIMEOUT;
910 } else {
911 try {
912 lockDuration =
913 (new Integer(lockDurationStr)).intValue();
914 } catch (NumberFormatException e) {
915 lockDuration = MAX_TIMEOUT;
916 }
917 }
918 }
919 if (lockDuration == 0) {
920 lockDuration = DEFAULT_TIMEOUT;
921 }
922 if (lockDuration > MAX_TIMEOUT) {
923 lockDuration = MAX_TIMEOUT;
924 }
925 }
926 lock.expiresAt = System.currentTimeMillis() + (lockDuration * 1000);
927
928 int lockRequestType = LOCK_CREATION;
929
930 Node lockInfoNode = null;
931
932 DocumentBuilder documentBuilder = getDocumentBuilder();
933
934 try {
935 Document document = documentBuilder.parse(new InputSource
936 (req.getInputStream()));
937
938 // Get the root element of the document
939 Element rootElement = document.getDocumentElement();
940 lockInfoNode = rootElement;
941 } catch (IOException e) {
942 lockRequestType = LOCK_REFRESH;
943 } catch (SAXException e) {
944 lockRequestType = LOCK_REFRESH;
945 }
946
947 if (lockInfoNode != null) {
948
949 // Reading lock information
950
951 NodeList childList = lockInfoNode.getChildNodes();
952 StringWriter strWriter = null;
953 DOMWriter domWriter = null;
954
955 Node lockScopeNode = null;
956 Node lockTypeNode = null;
957 Node lockOwnerNode = null;
958
959 for (int i=0; i < childList.getLength(); i++) {
960 Node currentNode = childList.item(i);
961 switch (currentNode.getNodeType()) {
962 case Node.TEXT_NODE:
963 break;
964 case Node.ELEMENT_NODE:
965 String nodeName = currentNode.getNodeName();
966 if (nodeName.endsWith("lockscope")) {
967 lockScopeNode = currentNode;
968 }
969 if (nodeName.endsWith("locktype")) {
970 lockTypeNode = currentNode;
971 }
972 if (nodeName.endsWith("owner")) {
973 lockOwnerNode = currentNode;
974 }
975 break;
976 }
977 }
978
979 if (lockScopeNode != null) {
980
981 childList = lockScopeNode.getChildNodes();
982 for (int i=0; i < childList.getLength(); i++) {
983 Node currentNode = childList.item(i);
984 switch (currentNode.getNodeType()) {
985 case Node.TEXT_NODE:
986 break;
987 case Node.ELEMENT_NODE:
988 String tempScope = currentNode.getNodeName();
989 if (tempScope.indexOf(':') != -1) {
990 lock.scope = tempScope.substring
991 (tempScope.indexOf(':') + 1);
992 } else {
993 lock.scope = tempScope;
994 }
995 break;
996 }
997 }
998
999 if (lock.scope == null) {
1000 // Bad request
1001 resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1002 }
1003
1004 } else {
1005 // Bad request
1006 resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1007 }
1008
1009 if (lockTypeNode != null) {
1010
1011 childList = lockTypeNode.getChildNodes();
1012 for (int i=0; i < childList.getLength(); i++) {
1013 Node currentNode = childList.item(i);
1014 switch (currentNode.getNodeType()) {
1015 case Node.TEXT_NODE:
1016 break;
1017 case Node.ELEMENT_NODE:
1018 String tempType = currentNode.getNodeName();
1019 if (tempType.indexOf(':') != -1) {
1020 lock.type =
1021 tempType.substring(tempType.indexOf(':') + 1);
1022 } else {
1023 lock.type = tempType;
1024 }
1025 break;
1026 }
1027 }
1028
1029 if (lock.type == null) {
1030 // Bad request
1031 resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1032 }
1033
1034 } else {
1035 // Bad request
1036 resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1037 }
1038
1039 if (lockOwnerNode != null) {
1040
1041 childList = lockOwnerNode.getChildNodes();
1042 for (int i=0; i < childList.getLength(); i++) {
1043 Node currentNode = childList.item(i);
1044 switch (currentNode.getNodeType()) {
1045 case Node.TEXT_NODE:
1046 lock.owner += currentNode.getNodeValue();
1047 break;
1048 case Node.ELEMENT_NODE:
1049 strWriter = new StringWriter();
1050 domWriter = new DOMWriter(strWriter, true);
1051 domWriter.setQualifiedNames(false);
1052 domWriter.print(currentNode);
1053 lock.owner += strWriter.toString();
1054 break;
1055 }
1056 }
1057
1058 if (lock.owner == null) {
1059 // Bad request
1060 resp.setStatus(WebdavStatus.SC_BAD_REQUEST);
1061 }
1062
1063 } else {
1064 lock.owner = new String();
1065 }
1066
1067 }
1068
1069 String path = getRelativePath(req);
1070
1071 lock.path = path;
1072
1073 boolean exists = true;
1074 Object object = null;
1075 try {
1076 object = resources.lookup(path);
1077 } catch (NamingException e) {
1078 exists = false;
1079 }
1080
1081 Enumeration locksList = null;
1082
1083 if (lockRequestType == LOCK_CREATION) {
1084
1085 // Generating lock id
1086 String lockTokenStr = req.getServletPath() + "-" + lock.type + "-"
1087 + lock.scope + "-" + req.getUserPrincipal() + "-"
1088 + lock.depth + "-" + lock.owner + "-" + lock.tokens + "-"
1089 + lock.expiresAt + "-" + System.currentTimeMillis() + "-"
1090 + secret;
1091 String lockToken =
1092 md5Encoder.encode(md5Helper.digest(lockTokenStr.getBytes()));
1093
1094 if ( (exists) && (object instanceof DirContext) &&
1095 (lock.depth == INFINITY) ) {
1096
1097 // Locking a collection (and all its member resources)
1098
1099 // Checking if a child resource of this collection is
1100 // already locked
1101 Vector<String> lockPaths = new Vector<String>();
1102 locksList = collectionLocks.elements();
1103 while (locksList.hasMoreElements()) {
1104 LockInfo currentLock = (LockInfo) locksList.nextElement();
1105 if (currentLock.hasExpired()) {
1106 resourceLocks.remove(currentLock.path);
1107 continue;
1108 }
1109 if ( (currentLock.path.startsWith(lock.path)) &&
1110 ((currentLock.isExclusive()) ||
1111 (lock.isExclusive())) ) {
1112 // A child collection of this collection is locked
1113 lockPaths.addElement(currentLock.path);
1114 }
1115 }
1116 locksList = resourceLocks.elements();
1117 while (locksList.hasMoreElements()) {
1118 LockInfo currentLock = (LockInfo) locksList.nextElement();
1119 if (currentLock.hasExpired()) {
1120 resourceLocks.remove(currentLock.path);
1121 continue;
1122 }
1123 if ( (currentLock.path.startsWith(lock.path)) &&
1124 ((currentLock.isExclusive()) ||
1125 (lock.isExclusive())) ) {
1126 // A child resource of this collection is locked
1127 lockPaths.addElement(currentLock.path);
1128 }
1129 }
1130
1131 if (!lockPaths.isEmpty()) {
1132
1133 // One of the child paths was locked
1134 // We generate a multistatus error report
1135
1136 Enumeration lockPathsList = lockPaths.elements();
1137
1138 resp.setStatus(WebdavStatus.SC_CONFLICT);
1139
1140 XMLWriter generatedXML = new XMLWriter();
1141 generatedXML.writeXMLHeader();
1142
1143 generatedXML.writeElement
1144 (null, "multistatus" + generateNamespaceDeclarations(),
1145 XMLWriter.OPENING);
1146
1147 while (lockPathsList.hasMoreElements()) {
1148 generatedXML.writeElement(null, "response",
1149 XMLWriter.OPENING);
1150 generatedXML.writeElement(null, "href",
1151 XMLWriter.OPENING);
1152 generatedXML
1153 .writeText((String) lockPathsList.nextElement());
1154 generatedXML.writeElement(null, "href",
1155 XMLWriter.CLOSING);
1156 generatedXML.writeElement(null, "status",
1157 XMLWriter.OPENING);
1158 generatedXML
1159 .writeText("HTTP/1.1 " + WebdavStatus.SC_LOCKED
1160 + " " + WebdavStatus
1161 .getStatusText(WebdavStatus.SC_LOCKED));
1162 generatedXML.writeElement(null, "status",
1163 XMLWriter.CLOSING);
1164
1165 generatedXML.writeElement(null, "response",
1166 XMLWriter.CLOSING);
1167 }
1168
1169 generatedXML.writeElement(null, "multistatus",
1170 XMLWriter.CLOSING);
1171
1172 Writer writer = resp.getWriter();
1173 writer.write(generatedXML.toString());
1174 writer.close();
1175
1176 return;
1177
1178 }
1179
1180 boolean addLock = true;
1181
1182 // Checking if there is already a shared lock on this path
1183 locksList = collectionLocks.elements();
1184 while (locksList.hasMoreElements()) {
1185
1186 LockInfo currentLock = (LockInfo) locksList.nextElement();
1187 if (currentLock.path.equals(lock.path)) {
1188
1189 if (currentLock.isExclusive()) {
1190 resp.sendError(WebdavStatus.SC_LOCKED);
1191 return;
1192 } else {
1193 if (lock.isExclusive()) {
1194 resp.sendError(WebdavStatus.SC_LOCKED);
1195 return;
1196 }
1197 }
1198
1199 currentLock.tokens.addElement(lockToken);
1200 lock = currentLock;
1201 addLock = false;
1202
1203 }
1204
1205 }
1206
1207 if (addLock) {
1208 lock.tokens.addElement(lockToken);
1209 collectionLocks.addElement(lock);
1210 }
1211
1212 } else {
1213
1214 // Locking a single resource
1215
1216 // Retrieving an already existing lock on that resource
1217 LockInfo presentLock = (LockInfo) resourceLocks.get(lock.path);
1218 if (presentLock != null) {
1219
1220 if ((presentLock.isExclusive()) || (lock.isExclusive())) {
1221 // If either lock is exclusive, the lock can't be
1222 // granted
1223 resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
1224 return;
1225 } else {
1226 presentLock.tokens.addElement(lockToken);
1227 lock = presentLock;
1228 }
1229
1230 } else {
1231
1232 lock.tokens.addElement(lockToken);
1233 resourceLocks.put(lock.path, lock);
1234
1235 // Checking if a resource exists at this path
1236 exists = true;
1237 try {
1238 object = resources.lookup(path);
1239 } catch (NamingException e) {
1240 exists = false;
1241 }
1242 if (!exists) {
1243
1244 // "Creating" a lock-null resource
1245 int slash = lock.path.lastIndexOf('/');
1246 String parentPath = lock.path.substring(0, slash);
1247
1248 Vector<String> lockNulls =
1249 lockNullResources.get(parentPath);
1250 if (lockNulls == null) {
1251 lockNulls = new Vector<String>();
1252 lockNullResources.put(parentPath, lockNulls);
1253 }
1254
1255 lockNulls.addElement(lock.path);
1256
1257 }
1258 // Add the Lock-Token header as by RFC 2518 8.10.1
1259 // - only do this for newly created locks
1260 resp.addHeader("Lock-Token", "<opaquelocktoken:"
1261 + lockToken + ">");
1262 }
1263
1264 }
1265
1266 }
1267
1268 if (lockRequestType == LOCK_REFRESH) {
1269
1270 String ifHeader = req.getHeader("If");
1271 if (ifHeader == null)
1272 ifHeader = "";
1273
1274 // Checking resource locks
1275
1276 LockInfo toRenew = (LockInfo) resourceLocks.get(path);
1277 Enumeration tokenList = null;
1278 if (lock != null) {
1279
1280 // At least one of the tokens of the locks must have been given
1281
1282 tokenList = toRenew.tokens.elements();
1283 while (tokenList.hasMoreElements()) {
1284 String token = (String) tokenList.nextElement();
1285 if (ifHeader.indexOf(token) != -1) {
1286 toRenew.expiresAt = lock.expiresAt;
1287 lock = toRenew;
1288 }
1289 }
1290
1291 }
1292
1293 // Checking inheritable collection locks
1294
1295 Enumeration collectionLocksList = collectionLocks.elements();
1296 while (collectionLocksList.hasMoreElements()) {
1297 toRenew = (LockInfo) collectionLocksList.nextElement();
1298 if (path.equals(toRenew.path)) {
1299
1300 tokenList = toRenew.tokens.elements();
1301 while (tokenList.hasMoreElements()) {
1302 String token = (String) tokenList.nextElement();
1303 if (ifHeader.indexOf(token) != -1) {
1304 toRenew.expiresAt = lock.expiresAt;
1305 lock = toRenew;
1306 }
1307 }
1308
1309 }
1310 }
1311
1312 }
1313
1314 // Set the status, then generate the XML response containing
1315 // the lock information
1316 XMLWriter generatedXML = new XMLWriter();
1317 generatedXML.writeXMLHeader();
1318 generatedXML.writeElement(null, "prop"
1319 + generateNamespaceDeclarations(),
1320 XMLWriter.OPENING);
1321
1322 generatedXML.writeElement(null, "lockdiscovery",
1323 XMLWriter.OPENING);
1324
1325 lock.toXML(generatedXML);
1326
1327 generatedXML.writeElement(null, "lockdiscovery",
1328 XMLWriter.CLOSING);
1329
1330 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
1331
1332 resp.setStatus(WebdavStatus.SC_OK);
1333 resp.setContentType("text/xml; charset=UTF-8");
1334 Writer writer = resp.getWriter();
1335 writer.write(generatedXML.toString());
1336 writer.close();
1337
1338 }
1339
1340
1341 /**
1342 * UNLOCK Method.
1343 */
1344 protected void doUnlock(HttpServletRequest req, HttpServletResponse resp)
1345 throws ServletException, IOException {
1346
1347 if (readOnly) {
1348 resp.sendError(WebdavStatus.SC_FORBIDDEN);
1349 return;
1350 }
1351
1352 if (isLocked(req)) {
1353 resp.sendError(WebdavStatus.SC_LOCKED);
1354 return;
1355 }
1356
1357 String path = getRelativePath(req);
1358
1359 String lockTokenHeader = req.getHeader("Lock-Token");
1360 if (lockTokenHeader == null)
1361 lockTokenHeader = "";
1362
1363 // Checking resource locks
1364
1365 LockInfo lock = (LockInfo) resourceLocks.get(path);
1366 Enumeration tokenList = null;
1367 if (lock != null) {
1368
1369 // At least one of the tokens of the locks must have been given
1370
1371 tokenList = lock.tokens.elements();
1372 while (tokenList.hasMoreElements()) {
1373 String token = (String) tokenList.nextElement();
1374 if (lockTokenHeader.indexOf(token) != -1) {
1375 lock.tokens.removeElement(token);
1376 }
1377 }
1378
1379 if (lock.tokens.isEmpty()) {
1380 resourceLocks.remove(path);
1381 // Removing any lock-null resource which would be present
1382 lockNullResources.remove(path);
1383 }
1384
1385 }
1386
1387 // Checking inheritable collection locks
1388
1389 Enumeration collectionLocksList = collectionLocks.elements();
1390 while (collectionLocksList.hasMoreElements()) {
1391 lock = (LockInfo) collectionLocksList.nextElement();
1392 if (path.equals(lock.path)) {
1393
1394 tokenList = lock.tokens.elements();
1395 while (tokenList.hasMoreElements()) {
1396 String token = (String) tokenList.nextElement();
1397 if (lockTokenHeader.indexOf(token) != -1) {
1398 lock.tokens.removeElement(token);
1399 break;
1400 }
1401 }
1402
1403 if (lock.tokens.isEmpty()) {
1404 collectionLocks.removeElement(lock);
1405 // Removing any lock-null resource which would be present
1406 lockNullResources.remove(path);
1407 }
1408
1409 }
1410 }
1411
1412 resp.setStatus(WebdavStatus.SC_NO_CONTENT);
1413
1414 }
1415
1416 /**
1417 * Return a context-relative path, beginning with a "/", that represents
1418 * the canonical version of the specified path after ".." and "." elements
1419 * are resolved out. If the specified path attempts to go outside the
1420 * boundaries of the current context (i.e. too many ".." path elements
1421 * are present), return <code>null</code> instead.
1422 *
1423 * @param path Path to be normalized
1424 */
1425 protected String normalize(String path) {
1426
1427 if (path == null)
1428 return null;
1429
1430 // Create a place for the normalized path
1431 String normalized = path;
1432
1433 if (normalized == null)
1434 return (null);
1435
1436 if (normalized.equals("/."))
1437 return "/";
1438
1439 // Normalize the slashes and add leading slash if necessary
1440 if (normalized.indexOf('\\') >= 0)
1441 normalized = normalized.replace('\\', '/');
1442 if (!normalized.startsWith("/"))
1443 normalized = "/" + normalized;
1444
1445 // Resolve occurrences of "//" in the normalized path
1446 while (true) {
1447 int index = normalized.indexOf("//");
1448 if (index < 0)
1449 break;
1450 normalized = normalized.substring(0, index) +
1451 normalized.substring(index + 1);
1452 }
1453
1454 // Resolve occurrences of "/./" in the normalized path
1455 while (true) {
1456 int index = normalized.indexOf("/./");
1457 if (index < 0)
1458 break;
1459 normalized = normalized.substring(0, index) +
1460 normalized.substring(index + 2);
1461 }
1462
1463 // Resolve occurrences of "/../" in the normalized path
1464 while (true) {
1465 int index = normalized.indexOf("/../");
1466 if (index < 0)
1467 break;
1468 if (index == 0)
1469 return (null); // Trying to go outside our context
1470 int index2 = normalized.lastIndexOf('/', index - 1);
1471 normalized = normalized.substring(0, index2) +
1472 normalized.substring(index + 3);
1473 }
1474
1475 // Return the normalized path that we have completed
1476 return (normalized);
1477
1478 }
1479
1480
1481 // -------------------------------------------------------- Private Methods
1482
1483 /**
1484 * Generate the namespace declarations.
1485 */
1486 private String generateNamespaceDeclarations() {
1487 return " xmlns=\"" + DEFAULT_NAMESPACE + "\"";
1488 }
1489
1490
1491 /**
1492 * Check to see if a resource is currently write locked. The method
1493 * will look at the "If" header to make sure the client
1494 * has give the appropriate lock tokens.
1495 *
1496 * @param req Servlet request
1497 * @return boolean true if the resource is locked (and no appropriate
1498 * lock token has been found for at least one of the non-shared locks which
1499 * are present on the resource).
1500 */
1501 private boolean isLocked(HttpServletRequest req) {
1502
1503 String path = getRelativePath(req);
1504
1505 String ifHeader = req.getHeader("If");
1506 if (ifHeader == null)
1507 ifHeader = "";
1508
1509 String lockTokenHeader = req.getHeader("Lock-Token");
1510 if (lockTokenHeader == null)
1511 lockTokenHeader = "";
1512
1513 return isLocked(path, ifHeader + lockTokenHeader);
1514
1515 }
1516
1517
1518 /**
1519 * Check to see if a resource is currently write locked.
1520 *
1521 * @param path Path of the resource
1522 * @param ifHeader "If" HTTP header which was included in the request
1523 * @return boolean true if the resource is locked (and no appropriate
1524 * lock token has been found for at least one of the non-shared locks which
1525 * are present on the resource).
1526 */
1527 private boolean isLocked(String path, String ifHeader) {
1528
1529 // Checking resource locks
1530
1531 LockInfo lock = (LockInfo) resourceLocks.get(path);
1532 Enumeration tokenList = null;
1533 if ((lock != null) && (lock.hasExpired())) {
1534 resourceLocks.remove(path);
1535 } else if (lock != null) {
1536
1537 // At least one of the tokens of the locks must have been given
1538
1539 tokenList = lock.tokens.elements();
1540 boolean tokenMatch = false;
1541 while (tokenList.hasMoreElements()) {
1542 String token = (String) tokenList.nextElement();
1543 if (ifHeader.indexOf(token) != -1)
1544 tokenMatch = true;
1545 }
1546 if (!tokenMatch)
1547 return true;
1548
1549 }
1550
1551 // Checking inheritable collection locks
1552
1553 Enumeration collectionLocksList = collectionLocks.elements();
1554 while (collectionLocksList.hasMoreElements()) {
1555 lock = (LockInfo) collectionLocksList.nextElement();
1556 if (lock.hasExpired()) {
1557 collectionLocks.removeElement(lock);
1558 } else if (path.startsWith(lock.path)) {
1559
1560 tokenList = lock.tokens.elements();
1561 boolean tokenMatch = false;
1562 while (tokenList.hasMoreElements()) {
1563 String token = (String) tokenList.nextElement();
1564 if (ifHeader.indexOf(token) != -1)
1565 tokenMatch = true;
1566 }
1567 if (!tokenMatch)
1568 return true;
1569
1570 }
1571 }
1572
1573 return false;
1574
1575 }
1576
1577
1578 /**
1579 * Copy a resource.
1580 *
1581 * @param req Servlet request
1582 * @param resp Servlet response
1583 * @return boolean true if the copy is successful
1584 */
1585 private boolean copyResource(HttpServletRequest req,
1586 HttpServletResponse resp)
1587 throws ServletException, IOException {
1588
1589 // Parsing destination header
1590
1591 String destinationPath = req.getHeader("Destination");
1592
1593 if (destinationPath == null) {
1594 resp.sendError(WebdavStatus.SC_BAD_REQUEST);
1595 return false;
1596 }
1597
1598 // Remove url encoding from destination
1599 destinationPath = RequestUtil.URLDecode(destinationPath, "UTF8");
1600
1601 int protocolIndex = destinationPath.indexOf("://");
1602 if (protocolIndex >= 0) {
1603 // if the Destination URL contains the protocol, we can safely
1604 // trim everything upto the first "/" character after "://"
1605 int firstSeparator =
1606 destinationPath.indexOf("/", protocolIndex + 4);
1607 if (firstSeparator < 0) {
1608 destinationPath = "/";
1609 } else {
1610 destinationPath = destinationPath.substring(firstSeparator);
1611 }
1612 } else {
1613 String hostName = req.getServerName();
1614 if ((hostName != null) && (destinationPath.startsWith(hostName))) {
1615 destinationPath = destinationPath.substring(hostName.length());
1616 }
1617
1618 int portIndex = destinationPath.indexOf(":");
1619 if (portIndex >= 0) {
1620 destinationPath = destinationPath.substring(portIndex);
1621 }
1622
1623 if (destinationPath.startsWith(":")) {
1624 int firstSeparator = destinationPath.indexOf("/");
1625 if (firstSeparator < 0) {
1626 destinationPath = "/";
1627 } else {
1628 destinationPath =
1629 destinationPath.substring(firstSeparator);
1630 }
1631 }
1632 }
1633
1634 // Normalise destination path (remove '.' and '..')
1635 destinationPath = normalize(destinationPath);
1636
1637 String contextPath = req.getContextPath();
1638 if ((contextPath != null) &&
1639 (destinationPath.startsWith(contextPath))) {
1640 destinationPath = destinationPath.substring(contextPath.length());
1641 }
1642
1643 String pathInfo = req.getPathInfo();
1644 if (pathInfo != null) {
1645 String servletPath = req.getServletPath();
1646 if ((servletPath != null) &&
1647 (destinationPath.startsWith(servletPath))) {
1648 destinationPath = destinationPath
1649 .substring(servletPath.length());
1650 }
1651 }
1652
1653 if (debug > 0)
1654 log("Dest path :" + destinationPath);
1655
1656 if ((destinationPath.toUpperCase().startsWith("/WEB-INF")) ||
1657 (destinationPath.toUpperCase().startsWith("/META-INF"))) {
1658 resp.sendError(WebdavStatus.SC_FORBIDDEN);
1659 return false;
1660 }
1661
1662 String path = getRelativePath(req);
1663
1664 if ((path.toUpperCase().startsWith("/WEB-INF")) ||
1665 (path.toUpperCase().startsWith("/META-INF"))) {
1666 resp.sendError(WebdavStatus.SC_FORBIDDEN);
1667 return false;
1668 }
1669
1670 if (destinationPath.equals(path)) {
1671 resp.sendError(WebdavStatus.SC_FORBIDDEN);
1672 return false;
1673 }
1674
1675 // Parsing overwrite header
1676
1677 boolean overwrite = true;
1678 String overwriteHeader = req.getHeader("Overwrite");
1679
1680 if (overwriteHeader != null) {
1681 if (overwriteHeader.equalsIgnoreCase("T")) {
1682 overwrite = true;
1683 } else {
1684 overwrite = false;
1685 }
1686 }
1687
1688 // Overwriting the destination
1689
1690 boolean exists = true;
1691 try {
1692 resources.lookup(destinationPath);
1693 } catch (NamingException e) {
1694 exists = false;
1695 }
1696
1697 if (overwrite) {
1698
1699 // Delete destination resource, if it exists
1700 if (exists) {
1701 if (!deleteResource(destinationPath, req, resp, true)) {
1702 return false;
1703 }
1704 } else {
1705 resp.setStatus(WebdavStatus.SC_CREATED);
1706 }
1707
1708 } else {
1709
1710 // If the destination exists, then it's a conflict
1711 if (exists) {
1712 resp.sendError(WebdavStatus.SC_PRECONDITION_FAILED);
1713 return false;
1714 }
1715
1716 }
1717
1718 // Copying source to destination
1719
1720 Hashtable<String,Integer> errorList = new Hashtable<String,Integer>();
1721
1722 boolean result = copyResource(resources, errorList,
1723 path, destinationPath);
1724
1725 if ((!result) || (!errorList.isEmpty())) {
1726
1727 sendReport(req, resp, errorList);
1728 return false;
1729
1730 }
1731
1732 // Copy was successful
1733 resp.setStatus(WebdavStatus.SC_CREATED);
1734
1735 // Removing any lock-null resource which would be present at
1736 // the destination path
1737 lockNullResources.remove(destinationPath);
1738
1739 return true;
1740
1741 }
1742
1743
1744 /**
1745 * Copy a collection.
1746 *
1747 * @param resources Resources implementation to be used
1748 * @param errorList Hashtable containing the list of errors which occurred
1749 * during the copy operation
1750 * @param source Path of the resource to be copied
1751 * @param dest Destination path
1752 */
1753 private boolean copyResource(DirContext resources,
1754 Hashtable<String,Integer> errorList, String source, String dest) {
1755
1756 if (debug > 1)
1757 log("Copy: " + source + " To: " + dest);
1758
1759 Object object = null;
1760 try {
1761 object = resources.lookup(source);
1762 } catch (NamingException e) {
1763 }
1764
1765 if (object instanceof DirContext) {
1766
1767 try {
1768 resources.createSubcontext(dest);
1769 } catch (NamingException e) {
1770 errorList.put
1771 (dest, new Integer(WebdavStatus.SC_CONFLICT));
1772 return false;
1773 }
1774
1775 try {
1776 NamingEnumeration enumeration = resources.list(source);
1777 while (enumeration.hasMoreElements()) {
1778 NameClassPair ncPair = (NameClassPair) enumeration.nextElement();
1779 String childDest = dest;
1780 if (!childDest.equals("/"))
1781 childDest += "/";
1782 childDest += ncPair.getName();
1783 String childSrc = source;
1784 if (!childSrc.equals("/"))
1785 childSrc += "/";
1786 childSrc += ncPair.getName();
1787 copyResource(resources, errorList, childSrc, childDest);
1788 }
1789 } catch (NamingException e) {
1790 errorList.put
1791 (dest, new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
1792 return false;
1793 }
1794
1795 } else {
1796
1797 if (object instanceof Resource) {
1798 try {
1799 resources.bind(dest, object);
1800 } catch (NamingException e) {
1801 errorList.put
1802 (source,
1803 new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
1804 return false;
1805 }
1806 } else {
1807 errorList.put
1808 (source,
1809 new Integer(WebdavStatus.SC_INTERNAL_SERVER_ERROR));
1810 return false;
1811 }
1812
1813 }
1814
1815 return true;
1816
1817 }
1818
1819
1820 /**
1821 * Delete a resource.
1822 *
1823 * @param req Servlet request
1824 * @param resp Servlet response
1825 * @return boolean true if the copy is successful
1826 */
1827 private boolean deleteResource(HttpServletRequest req,
1828 HttpServletResponse resp)
1829 throws ServletException, IOException {
1830
1831 String path = getRelativePath(req);
1832
1833 return deleteResource(path, req, resp, true);
1834
1835 }
1836
1837
1838 /**
1839 * Delete a resource.
1840 *
1841 * @param path Path of the resource which is to be deleted
1842 * @param req Servlet request
1843 * @param resp Servlet response
1844 * @param setStatus Should the response status be set on successful
1845 * completion
1846 */
1847 private boolean deleteResource(String path, HttpServletRequest req,
1848 HttpServletResponse resp, boolean setStatus)
1849 throws ServletException, IOException {
1850
1851 if ((path.toUpperCase().startsWith("/WEB-INF")) ||
1852 (path.toUpperCase().startsWith("/META-INF"))) {
1853 resp.sendError(WebdavStatus.SC_FORBIDDEN);
1854 return false;
1855 }
1856
1857 String ifHeader = req.getHeader("If");
1858 if (ifHeader == null)
1859 ifHeader = "";
1860
1861 String lockTokenHeader = req.getHeader("Lock-Token");
1862 if (lockTokenHeader == null)
1863 lockTokenHeader = "";
1864
1865 if (isLocked(path, ifHeader + lockTokenHeader)) {
1866 resp.sendError(WebdavStatus.SC_LOCKED);
1867 return false;
1868 }
1869
1870 boolean exists = true;
1871 Object object = null;
1872 try {
1873 object = resources.lookup(path);
1874 } catch (NamingException e) {
1875 exists = false;
1876 }
1877
1878 if (!exists) {
1879 resp.sendError(WebdavStatus.SC_NOT_FOUND);
1880 return false;
1881 }
1882
1883 boolean collection = (object instanceof DirContext);
1884
1885 if (!collection) {
1886 try {
1887 resources.unbind(path);
1888 } catch (NamingException e) {
1889 resp.sendError(WebdavStatus.SC_INTERNAL_SERVER_ERROR);
1890 return false;
1891 }
1892 } else {
1893
1894 Hashtable<String,Integer> errorList =
1895 new Hashtable<String,Integer>();
1896
1897 deleteCollection(req, resources, path, errorList);
1898 try {
1899 resources.unbind(path);
1900 } catch (NamingException e) {
1901 errorList.put(path, new Integer
1902 (WebdavStatus.SC_INTERNAL_SERVER_ERROR));
1903 }
1904
1905 if (!errorList.isEmpty()) {
1906
1907 sendReport(req, resp, errorList);
1908 return false;
1909
1910 }
1911
1912 }
1913 if (setStatus) {
1914 resp.setStatus(WebdavStatus.SC_NO_CONTENT);
1915 }
1916 return true;
1917
1918 }
1919
1920
1921 /**
1922 * Deletes a collection.
1923 *
1924 * @param resources Resources implementation associated with the context
1925 * @param path Path to the collection to be deleted
1926 * @param errorList Contains the list of the errors which occurred
1927 */
1928 private void deleteCollection(HttpServletRequest req,
1929 DirContext resources,
1930 String path,
1931 Hashtable<String,Integer> errorList) {
1932
1933 if (debug > 1)
1934 log("Delete:" + path);
1935
1936 if ((path.toUpperCase().startsWith("/WEB-INF")) ||
1937 (path.toUpperCase().startsWith("/META-INF"))) {
1938 errorList.put(path, new Integer(WebdavStatus.SC_FORBIDDEN));
1939 return;
1940 }
1941
1942 String ifHeader = req.getHeader("If");
1943 if (ifHeader == null)
1944 ifHeader = "";
1945
1946 String lockTokenHeader = req.getHeader("Lock-Token");
1947 if (lockTokenHeader == null)
1948 lockTokenHeader = "";
1949
1950 Enumeration enumeration = null;
1951 try {
1952 enumeration = resources.list(path);
1953 } catch (NamingException e) {
1954 errorList.put(path, new Integer
1955 (WebdavStatus.SC_INTERNAL_SERVER_ERROR));
1956 return;
1957 }
1958
1959 while (enumeration.hasMoreElements()) {
1960 NameClassPair ncPair = (NameClassPair) enumeration.nextElement();
1961 String childName = path;
1962 if (!childName.equals("/"))
1963 childName += "/";
1964 childName += ncPair.getName();
1965
1966 if (isLocked(childName, ifHeader + lockTokenHeader)) {
1967
1968 errorList.put(childName, new Integer(WebdavStatus.SC_LOCKED));
1969
1970 } else {
1971
1972 try {
1973 Object object = resources.lookup(childName);
1974 if (object instanceof DirContext) {
1975 deleteCollection(req, resources, childName, errorList);
1976 }
1977
1978 try {
1979 resources.unbind(childName);
1980 } catch (NamingException e) {
1981 if (!(object instanceof DirContext)) {
1982 // If it's not a collection, then it's an unknown
1983 // error
1984 errorList.put
1985 (childName, new Integer
1986 (WebdavStatus.SC_INTERNAL_SERVER_ERROR));
1987 }
1988 }
1989 } catch (NamingException e) {
1990 errorList.put
1991 (childName, new Integer
1992 (WebdavStatus.SC_INTERNAL_SERVER_ERROR));
1993 }
1994 }
1995
1996 }
1997
1998 }
1999
2000
2001 /**
2002 * Send a multistatus element containing a complete error report to the
2003 * client.
2004 *
2005 * @param req Servlet request
2006 * @param resp Servlet response
2007 * @param errorList List of error to be displayed
2008 */
2009 private void sendReport(HttpServletRequest req, HttpServletResponse resp,
2010 Hashtable errorList)
2011 throws ServletException, IOException {
2012
2013 resp.setStatus(WebdavStatus.SC_MULTI_STATUS);
2014
2015 String absoluteUri = req.getRequestURI();
2016 String relativePath = getRelativePath(req);
2017
2018 XMLWriter generatedXML = new XMLWriter();
2019 generatedXML.writeXMLHeader();
2020
2021 generatedXML.writeElement(null, "multistatus"
2022 + generateNamespaceDeclarations(),
2023 XMLWriter.OPENING);
2024
2025 Enumeration pathList = errorList.keys();
2026 while (pathList.hasMoreElements()) {
2027
2028 String errorPath = (String) pathList.nextElement();
2029 int errorCode = ((Integer) errorList.get(errorPath)).intValue();
2030
2031 generatedXML.writeElement(null, "response", XMLWriter.OPENING);
2032
2033 generatedXML.writeElement(null, "href", XMLWriter.OPENING);
2034 String toAppend = errorPath.substring(relativePath.length());
2035 if (!toAppend.startsWith("/"))
2036 toAppend = "/" + toAppend;
2037 generatedXML.writeText(absoluteUri + toAppend);
2038 generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
2039 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
2040 generatedXML
2041 .writeText("HTTP/1.1 " + errorCode + " "
2042 + WebdavStatus.getStatusText(errorCode));
2043 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
2044
2045 generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
2046
2047 }
2048
2049 generatedXML.writeElement(null, "multistatus", XMLWriter.CLOSING);
2050
2051 Writer writer = resp.getWriter();
2052 writer.write(generatedXML.toString());
2053 writer.close();
2054
2055 }
2056
2057
2058 /**
2059 * Propfind helper method.
2060 *
2061 * @param req The servlet request
2062 * @param resources Resources object associated with this context
2063 * @param generatedXML XML response to the Propfind request
2064 * @param path Path of the current resource
2065 * @param type Propfind type
2066 * @param propertiesVector If the propfind type is find properties by
2067 * name, then this Vector contains those properties
2068 */
2069 private void parseProperties(HttpServletRequest req,
2070 XMLWriter generatedXML,
2071 String path, int type,
2072 Vector<String> propertiesVector) {
2073
2074 // Exclude any resource in the /WEB-INF and /META-INF subdirectories
2075 // (the "toUpperCase()" avoids problems on Windows systems)
2076 if (path.toUpperCase().startsWith("/WEB-INF") ||
2077 path.toUpperCase().startsWith("/META-INF"))
2078 return;
2079
2080 CacheEntry cacheEntry = resources.lookupCache(path);
2081
2082 generatedXML.writeElement(null, "response", XMLWriter.OPENING);
2083 String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK + " "
2084 + WebdavStatus.getStatusText
2085 (WebdavStatus.SC_OK));
2086
2087 // Generating href element
2088 generatedXML.writeElement(null, "href", XMLWriter.OPENING);
2089
2090 String href = req.getContextPath() + req.getServletPath();
2091 if ((href.endsWith("/")) && (path.startsWith("/")))
2092 href += path.substring(1);
2093 else
2094 href += path;
2095 if ((cacheEntry.context != null) && (!href.endsWith("/")))
2096 href += "/";
2097
2098 generatedXML.writeText(rewriteUrl(href));
2099
2100 generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
2101
2102 String resourceName = path;
2103 int lastSlash = path.lastIndexOf('/');
2104 if (lastSlash != -1)
2105 resourceName = resourceName.substring(lastSlash + 1);
2106
2107 switch (type) {
2108
2109 case FIND_ALL_PROP :
2110
2111 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
2112 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
2113
2114 generatedXML.writeProperty
2115 (null, "creationdate",
2116 getISOCreationDate(cacheEntry.attributes.getCreation()));
2117 generatedXML.writeElement(null, "displayname", XMLWriter.OPENING);
2118 generatedXML.writeData(resourceName);
2119 generatedXML.writeElement(null, "displayname", XMLWriter.CLOSING);
2120 if (cacheEntry.resource != null) {
2121 generatedXML.writeProperty
2122 (null, "getlastmodified", FastHttpDateFormat.formatDate
2123 (cacheEntry.attributes.getLastModified(), null));
2124 generatedXML.writeProperty
2125 (null, "getcontentlength",
2126 String.valueOf(cacheEntry.attributes.getContentLength()));
2127 String contentType = getServletContext().getMimeType
2128 (cacheEntry.name);
2129 if (contentType != null) {
2130 generatedXML.writeProperty(null, "getcontenttype",
2131 contentType);
2132 }
2133 generatedXML.writeProperty(null, "getetag",
2134 getETag(cacheEntry.attributes));
2135 generatedXML.writeElement(null, "resourcetype",
2136 XMLWriter.NO_CONTENT);
2137 } else {
2138 generatedXML.writeElement(null, "resourcetype",
2139 XMLWriter.OPENING);
2140 generatedXML.writeElement(null, "collection",
2141 XMLWriter.NO_CONTENT);
2142 generatedXML.writeElement(null, "resourcetype",
2143 XMLWriter.CLOSING);
2144 }
2145
2146 generatedXML.writeProperty(null, "source", "");
2147
2148 String supportedLocks = "<lockentry>"
2149 + "<lockscope><exclusive/></lockscope>"
2150 + "<locktype><write/></locktype>"
2151 + "</lockentry>" + "<lockentry>"
2152 + "<lockscope><shared/></lockscope>"
2153 + "<locktype><write/></locktype>"
2154 + "</lockentry>";
2155 generatedXML.writeElement(null, "supportedlock",
2156 XMLWriter.OPENING);
2157 generatedXML.writeText(supportedLocks);
2158 generatedXML.writeElement(null, "supportedlock",
2159 XMLWriter.CLOSING);
2160
2161 generateLockDiscovery(path, generatedXML);
2162
2163 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
2164 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
2165 generatedXML.writeText(status);
2166 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
2167 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
2168
2169 break;
2170
2171 case FIND_PROPERTY_NAMES :
2172
2173 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
2174 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
2175
2176 generatedXML.writeElement(null, "creationdate",
2177 XMLWriter.NO_CONTENT);
2178 generatedXML.writeElement(null, "displayname",
2179 XMLWriter.NO_CONTENT);
2180 if (cacheEntry.resource != null) {
2181 generatedXML.writeElement(null, "getcontentlanguage",
2182 XMLWriter.NO_CONTENT);
2183 generatedXML.writeElement(null, "getcontentlength",
2184 XMLWriter.NO_CONTENT);
2185 generatedXML.writeElement(null, "getcontenttype",
2186 XMLWriter.NO_CONTENT);
2187 generatedXML.writeElement(null, "getetag",
2188 XMLWriter.NO_CONTENT);
2189 generatedXML.writeElement(null, "getlastmodified",
2190 XMLWriter.NO_CONTENT);
2191 }
2192 generatedXML.writeElement(null, "resourcetype",
2193 XMLWriter.NO_CONTENT);
2194 generatedXML.writeElement(null, "source", XMLWriter.NO_CONTENT);
2195 generatedXML.writeElement(null, "lockdiscovery",
2196 XMLWriter.NO_CONTENT);
2197
2198 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
2199 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
2200 generatedXML.writeText(status);
2201 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
2202 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
2203
2204 break;
2205
2206 case FIND_BY_PROPERTY :
2207
2208 Vector<String> propertiesNotFound = new Vector<String>();
2209
2210 // Parse the list of properties
2211
2212 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
2213 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
2214
2215 Enumeration<String> properties = propertiesVector.elements();
2216
2217 while (properties.hasMoreElements()) {
2218
2219 String property = (String) properties.nextElement();
2220
2221 if (property.equals("creationdate")) {
2222 generatedXML.writeProperty
2223 (null, "creationdate",
2224 getISOCreationDate(cacheEntry.attributes.getCreation()));
2225 } else if (property.equals("displayname")) {
2226 generatedXML.writeElement
2227 (null, "displayname", XMLWriter.OPENING);
2228 generatedXML.writeData(resourceName);
2229 generatedXML.writeElement
2230 (null, "displayname", XMLWriter.CLOSING);
2231 } else if (property.equals("getcontentlanguage")) {
2232 if (cacheEntry.context != null) {
2233 propertiesNotFound.addElement(property);
2234 } else {
2235 generatedXML.writeElement(null, "getcontentlanguage",
2236 XMLWriter.NO_CONTENT);
2237 }
2238 } else if (property.equals("getcontentlength")) {
2239 if (cacheEntry.context != null) {
2240 propertiesNotFound.addElement(property);
2241 } else {
2242 generatedXML.writeProperty
2243 (null, "getcontentlength",
2244 (String.valueOf(cacheEntry.attributes.getContentLength())));
2245 }
2246 } else if (property.equals("getcontenttype")) {
2247 if (cacheEntry.context != null) {
2248 propertiesNotFound.addElement(property);
2249 } else {
2250 generatedXML.writeProperty
2251 (null, "getcontenttype",
2252 getServletContext().getMimeType
2253 (cacheEntry.name));
2254 }
2255 } else if (property.equals("getetag")) {
2256 if (cacheEntry.context != null) {
2257 propertiesNotFound.addElement(property);
2258 } else {
2259 generatedXML.writeProperty
2260 (null, "getetag", getETag(cacheEntry.attributes));
2261 }
2262 } else if (property.equals("getlastmodified")) {
2263 if (cacheEntry.context != null) {
2264 propertiesNotFound.addElement(property);
2265 } else {
2266 generatedXML.writeProperty
2267 (null, "getlastmodified", FastHttpDateFormat.formatDate
2268 (cacheEntry.attributes.getLastModified(), null));
2269 }
2270 } else if (property.equals("resourcetype")) {
2271 if (cacheEntry.context != null) {
2272 generatedXML.writeElement(null, "resourcetype",
2273 XMLWriter.OPENING);
2274 generatedXML.writeElement(null, "collection",
2275 XMLWriter.NO_CONTENT);
2276 generatedXML.writeElement(null, "resourcetype",
2277 XMLWriter.CLOSING);
2278 } else {
2279 generatedXML.writeElement(null, "resourcetype",
2280 XMLWriter.NO_CONTENT);
2281 }
2282 } else if (property.equals("source")) {
2283 generatedXML.writeProperty(null, "source", "");
2284 } else if (property.equals("supportedlock")) {
2285 supportedLocks = "<lockentry>"
2286 + "<lockscope><exclusive/></lockscope>"
2287 + "<locktype><write/></locktype>"
2288 + "</lockentry>" + "<lockentry>"
2289 + "<lockscope><shared/></lockscope>"
2290 + "<locktype><write/></locktype>"
2291 + "</lockentry>";
2292 generatedXML.writeElement(null, "supportedlock",
2293 XMLWriter.OPENING);
2294 generatedXML.writeText(supportedLocks);
2295 generatedXML.writeElement(null, "supportedlock",
2296 XMLWriter.CLOSING);
2297 } else if (property.equals("lockdiscovery")) {
2298 if (!generateLockDiscovery(path, generatedXML))
2299 propertiesNotFound.addElement(property);
2300 } else {
2301 propertiesNotFound.addElement(property);
2302 }
2303
2304 }
2305
2306 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
2307 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
2308 generatedXML.writeText(status);
2309 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
2310 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
2311
2312 Enumeration propertiesNotFoundList = propertiesNotFound.elements();
2313
2314 if (propertiesNotFoundList.hasMoreElements()) {
2315
2316 status = new String("HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND
2317 + " " + WebdavStatus.getStatusText
2318 (WebdavStatus.SC_NOT_FOUND));
2319
2320 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
2321 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
2322
2323 while (propertiesNotFoundList.hasMoreElements()) {
2324 generatedXML.writeElement
2325 (null, (String) propertiesNotFoundList.nextElement(),
2326 XMLWriter.NO_CONTENT);
2327 }
2328
2329 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
2330 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
2331 generatedXML.writeText(status);
2332 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
2333 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
2334
2335 }
2336
2337 break;
2338
2339 }
2340
2341 generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
2342
2343 }
2344
2345
2346 /**
2347 * Propfind helper method. Dispays the properties of a lock-null resource.
2348 *
2349 * @param resources Resources object associated with this context
2350 * @param generatedXML XML response to the Propfind request
2351 * @param path Path of the current resource
2352 * @param type Propfind type
2353 * @param propertiesVector If the propfind type is find properties by
2354 * name, then this Vector contains those properties
2355 */
2356 private void parseLockNullProperties(HttpServletRequest req,
2357 XMLWriter generatedXML,
2358 String path, int type,
2359 Vector propertiesVector) {
2360
2361 // Exclude any resource in the /WEB-INF and /META-INF subdirectories
2362 // (the "toUpperCase()" avoids problems on Windows systems)
2363 if (path.toUpperCase().startsWith("/WEB-INF") ||
2364 path.toUpperCase().startsWith("/META-INF"))
2365 return;
2366
2367 // Retrieving the lock associated with the lock-null resource
2368 LockInfo lock = (LockInfo) resourceLocks.get(path);
2369
2370 if (lock == null)
2371 return;
2372
2373 generatedXML.writeElement(null, "response", XMLWriter.OPENING);
2374 String status = new String("HTTP/1.1 " + WebdavStatus.SC_OK + " "
2375 + WebdavStatus.getStatusText
2376 (WebdavStatus.SC_OK));
2377
2378 // Generating href element
2379 generatedXML.writeElement(null, "href", XMLWriter.OPENING);
2380
2381 String absoluteUri = req.getRequestURI();
2382 String relativePath = getRelativePath(req);
2383 String toAppend = path.substring(relativePath.length());
2384 if (!toAppend.startsWith("/"))
2385 toAppend = "/" + toAppend;
2386
2387 generatedXML.writeText(rewriteUrl(normalize(absoluteUri + toAppend)));
2388
2389 generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
2390
2391 String resourceName = path;
2392 int lastSlash = path.lastIndexOf('/');
2393 if (lastSlash != -1)
2394 resourceName = resourceName.substring(lastSlash + 1);
2395
2396 switch (type) {
2397
2398 case FIND_ALL_PROP :
2399
2400 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
2401 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
2402
2403 generatedXML.writeProperty
2404 (null, "creationdate",
2405 getISOCreationDate(lock.creationDate.getTime()));
2406 generatedXML.writeElement
2407 (null, "displayname", XMLWriter.OPENING);
2408 generatedXML.writeData(resourceName);
2409 generatedXML.writeElement
2410 (null, "displayname", XMLWriter.CLOSING);
2411 generatedXML.writeProperty(null, "getlastmodified",
2412 FastHttpDateFormat.formatDate
2413 (lock.creationDate.getTime(), null));
2414 generatedXML.writeProperty
2415 (null, "getcontentlength", String.valueOf(0));
2416 generatedXML.writeProperty(null, "getcontenttype", "");
2417 generatedXML.writeProperty(null, "getetag", "");
2418 generatedXML.writeElement(null, "resourcetype",
2419 XMLWriter.OPENING);
2420 generatedXML.writeElement(null, "lock-null", XMLWriter.NO_CONTENT);
2421 generatedXML.writeElement(null, "resourcetype",
2422 XMLWriter.CLOSING);
2423
2424 generatedXML.writeProperty(null, "source", "");
2425
2426 String supportedLocks = "<lockentry>"
2427 + "<lockscope><exclusive/></lockscope>"
2428 + "<locktype><write/></locktype>"
2429 + "</lockentry>" + "<lockentry>"
2430 + "<lockscope><shared/></lockscope>"
2431 + "<locktype><write/></locktype>"
2432 + "</lockentry>";
2433 generatedXML.writeElement(null, "supportedlock",
2434 XMLWriter.OPENING);
2435 generatedXML.writeText(supportedLocks);
2436 generatedXML.writeElement(null, "supportedlock",
2437 XMLWriter.CLOSING);
2438
2439 generateLockDiscovery(path, generatedXML);
2440
2441 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
2442 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
2443 generatedXML.writeText(status);
2444 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
2445 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
2446
2447 break;
2448
2449 case FIND_PROPERTY_NAMES :
2450
2451 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
2452 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
2453
2454 generatedXML.writeElement(null, "creationdate",
2455 XMLWriter.NO_CONTENT);
2456 generatedXML.writeElement(null, "displayname",
2457 XMLWriter.NO_CONTENT);
2458 generatedXML.writeElement(null, "getcontentlanguage",
2459 XMLWriter.NO_CONTENT);
2460 generatedXML.writeElement(null, "getcontentlength",
2461 XMLWriter.NO_CONTENT);
2462 generatedXML.writeElement(null, "getcontenttype",
2463 XMLWriter.NO_CONTENT);
2464 generatedXML.writeElement(null, "getetag",
2465 XMLWriter.NO_CONTENT);
2466 generatedXML.writeElement(null, "getlastmodified",
2467 XMLWriter.NO_CONTENT);
2468 generatedXML.writeElement(null, "resourcetype",
2469 XMLWriter.NO_CONTENT);
2470 generatedXML.writeElement(null, "source",
2471 XMLWriter.NO_CONTENT);
2472 generatedXML.writeElement(null, "lockdiscovery",
2473 XMLWriter.NO_CONTENT);
2474
2475 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
2476 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
2477 generatedXML.writeText(status);
2478 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
2479 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
2480
2481 break;
2482
2483 case FIND_BY_PROPERTY :
2484
2485 Vector<String> propertiesNotFound = new Vector<String>();
2486
2487 // Parse the list of properties
2488
2489 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
2490 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
2491
2492 Enumeration properties = propertiesVector.elements();
2493
2494 while (properties.hasMoreElements()) {
2495
2496 String property = (String) properties.nextElement();
2497
2498 if (property.equals("creationdate")) {
2499 generatedXML.writeProperty
2500 (null, "creationdate",
2501 getISOCreationDate(lock.creationDate.getTime()));
2502 } else if (property.equals("displayname")) {
2503 generatedXML.writeElement
2504 (null, "displayname", XMLWriter.OPENING);
2505 generatedXML.writeData(resourceName);
2506 generatedXML.writeElement
2507 (null, "displayname", XMLWriter.CLOSING);
2508 } else if (property.equals("getcontentlanguage")) {
2509 generatedXML.writeElement(null, "getcontentlanguage",
2510 XMLWriter.NO_CONTENT);
2511 } else if (property.equals("getcontentlength")) {
2512 generatedXML.writeProperty
2513 (null, "getcontentlength", (String.valueOf(0)));
2514 } else if (property.equals("getcontenttype")) {
2515 generatedXML.writeProperty
2516 (null, "getcontenttype", "");
2517 } else if (property.equals("getetag")) {
2518 generatedXML.writeProperty(null, "getetag", "");
2519 } else if (property.equals("getlastmodified")) {
2520 generatedXML.writeProperty
2521 (null, "getlastmodified",
2522 FastHttpDateFormat.formatDate
2523 (lock.creationDate.getTime(), null));
2524 } else if (property.equals("resourcetype")) {
2525 generatedXML.writeElement(null, "resourcetype",
2526 XMLWriter.OPENING);
2527 generatedXML.writeElement(null, "lock-null",
2528 XMLWriter.NO_CONTENT);
2529 generatedXML.writeElement(null, "resourcetype",
2530 XMLWriter.CLOSING);
2531 } else if (property.equals("source")) {
2532 generatedXML.writeProperty(null, "source", "");
2533 } else if (property.equals("supportedlock")) {
2534 supportedLocks = "<lockentry>"
2535 + "<lockscope><exclusive/></lockscope>"
2536 + "<locktype><write/></locktype>"
2537 + "</lockentry>" + "<lockentry>"
2538 + "<lockscope><shared/></lockscope>"
2539 + "<locktype><write/></locktype>"
2540 + "</lockentry>";
2541 generatedXML.writeElement(null, "supportedlock",
2542 XMLWriter.OPENING);
2543 generatedXML.writeText(supportedLocks);
2544 generatedXML.writeElement(null, "supportedlock",
2545 XMLWriter.CLOSING);
2546 } else if (property.equals("lockdiscovery")) {
2547 if (!generateLockDiscovery(path, generatedXML))
2548 propertiesNotFound.addElement(property);
2549 } else {
2550 propertiesNotFound.addElement(property);
2551 }
2552
2553 }
2554
2555 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
2556 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
2557 generatedXML.writeText(status);
2558 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
2559 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
2560
2561 Enumeration propertiesNotFoundList = propertiesNotFound.elements();
2562
2563 if (propertiesNotFoundList.hasMoreElements()) {
2564
2565 status = new String("HTTP/1.1 " + WebdavStatus.SC_NOT_FOUND
2566 + " " + WebdavStatus.getStatusText
2567 (WebdavStatus.SC_NOT_FOUND));
2568
2569 generatedXML.writeElement(null, "propstat", XMLWriter.OPENING);
2570 generatedXML.writeElement(null, "prop", XMLWriter.OPENING);
2571
2572 while (propertiesNotFoundList.hasMoreElements()) {
2573 generatedXML.writeElement
2574 (null, (String) propertiesNotFoundList.nextElement(),
2575 XMLWriter.NO_CONTENT);
2576 }
2577
2578 generatedXML.writeElement(null, "prop", XMLWriter.CLOSING);
2579 generatedXML.writeElement(null, "status", XMLWriter.OPENING);
2580 generatedXML.writeText(status);
2581 generatedXML.writeElement(null, "status", XMLWriter.CLOSING);
2582 generatedXML.writeElement(null, "propstat", XMLWriter.CLOSING);
2583
2584 }
2585
2586 break;
2587
2588 }
2589
2590 generatedXML.writeElement(null, "response", XMLWriter.CLOSING);
2591
2592 }
2593
2594
2595 /**
2596 * Print the lock discovery information associated with a path.
2597 *
2598 * @param path Path
2599 * @param generatedXML XML data to which the locks info will be appended
2600 * @return true if at least one lock was displayed
2601 */
2602 private boolean generateLockDiscovery
2603 (String path, XMLWriter generatedXML) {
2604
2605 LockInfo resourceLock = (LockInfo) resourceLocks.get(path);
2606 Enumeration collectionLocksList = collectionLocks.elements();
2607
2608 boolean wroteStart = false;
2609
2610 if (resourceLock != null) {
2611 wroteStart = true;
2612 generatedXML.writeElement(null, "lockdiscovery",
2613 XMLWriter.OPENING);
2614 resourceLock.toXML(generatedXML);
2615 }
2616
2617 while (collectionLocksList.hasMoreElements()) {
2618 LockInfo currentLock =
2619 (LockInfo) collectionLocksList.nextElement();
2620 if (path.startsWith(currentLock.path)) {
2621 if (!wroteStart) {
2622 wroteStart = true;
2623 generatedXML.writeElement(null, "lockdiscovery",
2624 XMLWriter.OPENING);
2625 }
2626 currentLock.toXML(generatedXML);
2627 }
2628 }
2629
2630 if (wroteStart) {
2631 generatedXML.writeElement(null, "lockdiscovery",
2632 XMLWriter.CLOSING);
2633 } else {
2634 return false;
2635 }
2636
2637 return true;
2638
2639 }
2640
2641
2642 /**
2643 * Get creation date in ISO format.
2644 */
2645 private String getISOCreationDate(long creationDate) {
2646 StringBuffer creationDateValue = new StringBuffer
2647 (creationDateFormat.format
2648 (new Date(creationDate)));
2649 /*
2650 int offset = Calendar.getInstance().getTimeZone().getRawOffset()
2651 / 3600000; // FIXME ?
2652 if (offset < 0) {
2653 creationDateValue.append("-");
2654 offset = -offset;
2655 } else if (offset > 0) {
2656 creationDateValue.append("+");
2657 }
2658 if (offset != 0) {
2659 if (offset < 10)
2660 creationDateValue.append("0");
2661 creationDateValue.append(offset + ":00");
2662 } else {
2663 creationDateValue.append("Z");
2664 }
2665 */
2666 return creationDateValue.toString();
2667 }
2668
2669 /**
2670 * Determines the methods normally allowed for the resource.
2671 *
2672 */
2673 private StringBuffer determineMethodsAllowed(DirContext resources,
2674 HttpServletRequest req) {
2675
2676 StringBuffer methodsAllowed = new StringBuffer();
2677 boolean exists = true;
2678 Object object = null;
2679 try {
2680 String path = getRelativePath(req);
2681
2682 object = resources.lookup(path);
2683 } catch (NamingException e) {
2684 exists = false;
2685 }
2686
2687 if (!exists) {
2688 methodsAllowed.append("OPTIONS, MKCOL, PUT, LOCK");
2689 return methodsAllowed;
2690 }
2691
2692 methodsAllowed.append("OPTIONS, GET, HEAD, POST, DELETE, TRACE");
2693 methodsAllowed.append(", PROPPATCH, COPY, MOVE, LOCK, UNLOCK");
2694
2695 if (listings) {
2696 methodsAllowed.append(", PROPFIND");
2697 }
2698
2699 if (!(object instanceof DirContext)) {
2700 methodsAllowed.append(", PUT");
2701 }
2702
2703 return methodsAllowed;
2704 }
2705
2706 // -------------------------------------------------- LockInfo Inner Class
2707
2708
2709 /**
2710 * Holds a lock information.
2711 */
2712 private class LockInfo {
2713
2714
2715 // -------------------------------------------------------- Constructor
2716
2717
2718 /**
2719 * Constructor.
2720 */
2721 public LockInfo() {
2722
2723 }
2724
2725
2726 // ------------------------------------------------- Instance Variables
2727
2728
2729 String path = "/";
2730 String type = "write";
2731 String scope = "exclusive";
2732 int depth = 0;
2733 String owner = "";
2734 Vector<String> tokens = new Vector<String>();
2735 long expiresAt = 0;
2736 Date creationDate = new Date();
2737
2738
2739 // ----------------------------------------------------- Public Methods
2740
2741
2742 /**
2743 * Get a String representation of this lock token.
2744 */
2745 public String toString() {
2746
2747 String result = "Type:" + type + "\n";
2748 result += "Scope:" + scope + "\n";
2749 result += "Depth:" + depth + "\n";
2750 result += "Owner:" + owner + "\n";
2751 result += "Expiration:"
2752 + FastHttpDateFormat.formatDate(expiresAt, null) + "\n";
2753 Enumeration tokensList = tokens.elements();
2754 while (tokensList.hasMoreElements()) {
2755 result += "Token:" + tokensList.nextElement() + "\n";
2756 }
2757 return result;
2758
2759 }
2760
2761
2762 /**
2763 * Return true if the lock has expired.
2764 */
2765 public boolean hasExpired() {
2766 return (System.currentTimeMillis() > expiresAt);
2767 }
2768
2769
2770 /**
2771 * Return true if the lock is exclusive.
2772 */
2773 public boolean isExclusive() {
2774
2775 return (scope.equals("exclusive"));
2776
2777 }
2778
2779
2780 /**
2781 * Get an XML representation of this lock token. This method will
2782 * append an XML fragment to the given XML writer.
2783 */
2784 public void toXML(XMLWriter generatedXML) {
2785
2786 generatedXML.writeElement(null, "activelock", XMLWriter.OPENING);
2787
2788 generatedXML.writeElement(null, "locktype", XMLWriter.OPENING);
2789 generatedXML.writeElement(null, type, XMLWriter.NO_CONTENT);
2790 generatedXML.writeElement(null, "locktype", XMLWriter.CLOSING);
2791
2792 generatedXML.writeElement(null, "lockscope", XMLWriter.OPENING);
2793 generatedXML.writeElement(null, scope, XMLWriter.NO_CONTENT);
2794 generatedXML.writeElement(null, "lockscope", XMLWriter.CLOSING);
2795
2796 generatedXML.writeElement(null, "depth", XMLWriter.OPENING);
2797 if (depth == INFINITY) {
2798 generatedXML.writeText("Infinity");
2799 } else {
2800 generatedXML.writeText("0");
2801 }
2802 generatedXML.writeElement(null, "depth", XMLWriter.CLOSING);
2803
2804 generatedXML.writeElement(null, "owner", XMLWriter.OPENING);
2805 generatedXML.writeText(owner);
2806 generatedXML.writeElement(null, "owner", XMLWriter.CLOSING);
2807
2808 generatedXML.writeElement(null, "timeout", XMLWriter.OPENING);
2809 long timeout = (expiresAt - System.currentTimeMillis()) / 1000;
2810 generatedXML.writeText("Second-" + timeout);
2811 generatedXML.writeElement(null, "timeout", XMLWriter.CLOSING);
2812
2813 generatedXML.writeElement(null, "locktoken", XMLWriter.OPENING);
2814 Enumeration tokensList = tokens.elements();
2815 while (tokensList.hasMoreElements()) {
2816 generatedXML.writeElement(null, "href", XMLWriter.OPENING);
2817 generatedXML.writeText("opaquelocktoken:"
2818 + tokensList.nextElement());
2819 generatedXML.writeElement(null, "href", XMLWriter.CLOSING);
2820 }
2821 generatedXML.writeElement(null, "locktoken", XMLWriter.CLOSING);
2822
2823 generatedXML.writeElement(null, "activelock", XMLWriter.CLOSING);
2824
2825 }
2826
2827
2828 }
2829
2830
2831 // --------------------------------------------- WebdavResolver Inner Class
2832 /**
2833 * Work around for XML parsers that don't fully respect
2834 * {@link DocumentBuilderFactory#setExpandEntityReferences(false)}. External
2835 * references are filtered out for security reasons. See CVE-2007-5461.
2836 */
2837 private class WebdavResolver implements EntityResolver {
2838 private ServletContext context;
2839
2840 public WebdavResolver(ServletContext theContext) {
2841 context = theContext;
2842 }
2843
2844 public InputSource resolveEntity (String publicId, String systemId) {
2845 context.log(sm.getString("webdavservlet.enternalEntityIgnored",
2846 publicId, systemId));
2847 return new InputSource(
2848 new StringReader("Ignored external entity"));
2849 }
2850 }
2851 };
2852
2853
2854 // -------------------------------------------------------- WebdavStatus Class
2855
2856
2857 /**
2858 * Wraps the HttpServletResponse class to abstract the
2859 * specific protocol used. To support other protocols
2860 * we would only need to modify this class and the
2861 * WebDavRetCode classes.
2862 *
2863 * @author Marc Eaddy
2864 * @version 1.0, 16 Nov 1997
2865 */
2866 class WebdavStatus {
2867
2868
2869 // ----------------------------------------------------- Instance Variables
2870
2871
2872 /**
2873 * This Hashtable contains the mapping of HTTP and WebDAV
2874 * status codes to descriptive text. This is a static
2875 * variable.
2876 */
2877 private static Hashtable<Integer,String> mapStatusCodes =
2878 new Hashtable<Integer,String>();
2879
2880
2881 // ------------------------------------------------------ HTTP Status Codes
2882
2883
2884 /**
2885 * Status code (200) indicating the request succeeded normally.
2886 */
2887 public static final int SC_OK = HttpServletResponse.SC_OK;
2888
2889
2890 /**
2891 * Status code (201) indicating the request succeeded and created
2892 * a new resource on the server.
2893 */
2894 public static final int SC_CREATED = HttpServletResponse.SC_CREATED;
2895
2896
2897 /**
2898 * Status code (202) indicating that a request was accepted for
2899 * processing, but was not completed.
2900 */
2901 public static final int SC_ACCEPTED = HttpServletResponse.SC_ACCEPTED;
2902
2903
2904 /**
2905 * Status code (204) indicating that the request succeeded but that
2906 * there was no new information to return.
2907 */
2908 public static final int SC_NO_CONTENT = HttpServletResponse.SC_NO_CONTENT;
2909
2910
2911 /**
2912 * Status code (301) indicating that the resource has permanently
2913 * moved to a new location, and that future references should use a
2914 * new URI with their requests.
2915 */
2916 public static final int SC_MOVED_PERMANENTLY =
2917 HttpServletResponse.SC_MOVED_PERMANENTLY;
2918
2919
2920 /**
2921 * Status code (302) indicating that the resource has temporarily
2922 * moved to another location, but that future references should
2923 * still use the original URI to access the resource.
2924 */
2925 public static final int SC_MOVED_TEMPORARILY =
2926 HttpServletResponse.SC_MOVED_TEMPORARILY;
2927
2928
2929 /**
2930 * Status code (304) indicating that a conditional GET operation
2931 * found that the resource was available and not modified.
2932 */
2933 public static final int SC_NOT_MODIFIED =
2934 HttpServletResponse.SC_NOT_MODIFIED;
2935
2936
2937 /**
2938 * Status code (400) indicating the request sent by the client was
2939 * syntactically incorrect.
2940 */
2941 public static final int SC_BAD_REQUEST =
2942 HttpServletResponse.SC_BAD_REQUEST;
2943
2944
2945 /**
2946 * Status code (401) indicating that the request requires HTTP
2947 * authentication.
2948 */
2949 public static final int SC_UNAUTHORIZED =
2950 HttpServletResponse.SC_UNAUTHORIZED;
2951
2952
2953 /**
2954 * Status code (403) indicating the server understood the request
2955 * but refused to fulfill it.
2956 */
2957 public static final int SC_FORBIDDEN = HttpServletResponse.SC_FORBIDDEN;
2958
2959
2960 /**
2961 * Status code (404) indicating that the requested resource is not
2962 * available.
2963 */
2964 public static final int SC_NOT_FOUND = HttpServletResponse.SC_NOT_FOUND;
2965
2966
2967 /**
2968 * Status code (500) indicating an error inside the HTTP service
2969 * which prevented it from fulfilling the request.
2970 */
2971 public static final int SC_INTERNAL_SERVER_ERROR =
2972 HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
2973
2974
2975 /**
2976 * Status code (501) indicating the HTTP service does not support
2977 * the functionality needed to fulfill the request.
2978 */
2979 public static final int SC_NOT_IMPLEMENTED =
2980 HttpServletResponse.SC_NOT_IMPLEMENTED;
2981
2982
2983 /**
2984 * Status code (502) indicating that the HTTP server received an
2985 * invalid response from a server it consulted when acting as a
2986 * proxy or gateway.
2987 */
2988 public static final int SC_BAD_GATEWAY =
2989 HttpServletResponse.SC_BAD_GATEWAY;
2990
2991
2992 /**
2993 * Status code (503) indicating that the HTTP service is
2994 * temporarily overloaded, and unable to handle the request.
2995 */
2996 public static final int SC_SERVICE_UNAVAILABLE =
2997 HttpServletResponse.SC_SERVICE_UNAVAILABLE;
2998
2999
3000 /**
3001 * Status code (100) indicating the client may continue with
3002 * its request. This interim response is used to inform the
3003 * client that the initial part of the request has been
3004 * received and has not yet been rejected by the server.
3005 */
3006 public static final int SC_CONTINUE = 100;
3007
3008
3009 /**
3010 * Status code (405) indicating the method specified is not
3011 * allowed for the resource.
3012 */
3013 public static final int SC_METHOD_NOT_ALLOWED = 405;
3014
3015
3016 /**
3017 * Status code (409) indicating that the request could not be
3018 * completed due to a conflict with the current state of the
3019 * resource.
3020 */
3021 public static final int SC_CONFLICT = 409;
3022
3023
3024 /**
3025 * Status code (412) indicating the precondition given in one
3026 * or more of the request-header fields evaluated to false
3027 * when it was tested on the server.
3028 */
3029 public static final int SC_PRECONDITION_FAILED = 412;
3030
3031
3032 /**
3033 * Status code (413) indicating the server is refusing to
3034 * process a request because the request entity is larger
3035 * than the server is willing or able to process.
3036 */
3037 public static final int SC_REQUEST_TOO_LONG = 413;
3038
3039
3040 /**
3041 * Status code (415) indicating the server is refusing to service
3042 * the request because the entity of the request is in a format
3043 * not supported by the requested resource for the requested
3044 * method.
3045 */
3046 public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415;
3047
3048
3049 // -------------------------------------------- Extended WebDav status code
3050
3051
3052 /**
3053 * Status code (207) indicating that the response requires
3054 * providing status for multiple independent operations.
3055 */
3056 public static final int SC_MULTI_STATUS = 207;
3057 // This one colides with HTTP 1.1
3058 // "207 Parital Update OK"
3059
3060
3061 /**
3062 * Status code (418) indicating the entity body submitted with
3063 * the PATCH method was not understood by the resource.
3064 */
3065 public static final int SC_UNPROCESSABLE_ENTITY = 418;
3066 // This one colides with HTTP 1.1
3067 // "418 Reauthentication Required"
3068
3069
3070 /**
3071 * Status code (419) indicating that the resource does not have
3072 * sufficient space to record the state of the resource after the
3073 * execution of this method.
3074 */
3075 public static final int SC_INSUFFICIENT_SPACE_ON_RESOURCE = 419;
3076 // This one colides with HTTP 1.1
3077 // "419 Proxy Reauthentication Required"
3078
3079
3080 /**
3081 * Status code (420) indicating the method was not executed on
3082 * a particular resource within its scope because some part of
3083 * the method's execution failed causing the entire method to be
3084 * aborted.
3085 */
3086 public static final int SC_METHOD_FAILURE = 420;
3087
3088
3089 /**
3090 * Status code (423) indicating the destination resource of a
3091 * method is locked, and either the request did not contain a
3092 * valid Lock-Info header, or the Lock-Info header identifies
3093 * a lock held by another principal.
3094 */
3095 public static final int SC_LOCKED = 423;
3096
3097
3098 // ------------------------------------------------------------ Initializer
3099
3100
3101 static {
3102 // HTTP 1.0 tatus Code
3103 addStatusCodeMap(SC_OK, "OK");
3104 addStatusCodeMap(SC_CREATED, "Created");
3105 addStatusCodeMap(SC_ACCEPTED, "Accepted");
3106 addStatusCodeMap(SC_NO_CONTENT, "No Content");
3107 addStatusCodeMap(SC_MOVED_PERMANENTLY, "Moved Permanently");
3108 addStatusCodeMap(SC_MOVED_TEMPORARILY, "Moved Temporarily");
3109 addStatusCodeMap(SC_NOT_MODIFIED, "Not Modified");
3110 addStatusCodeMap(SC_BAD_REQUEST, "Bad Request");
3111 addStatusCodeMap(SC_UNAUTHORIZED, "Unauthorized");
3112 addStatusCodeMap(SC_FORBIDDEN, "Forbidden");
3113 addStatusCodeMap(SC_NOT_FOUND, "Not Found");
3114 addStatusCodeMap(SC_INTERNAL_SERVER_ERROR, "Internal Server Error");
3115 addStatusCodeMap(SC_NOT_IMPLEMENTED, "Not Implemented");
3116 addStatusCodeMap(SC_BAD_GATEWAY, "Bad Gateway");
3117 addStatusCodeMap(SC_SERVICE_UNAVAILABLE, "Service Unavailable");
3118 addStatusCodeMap(SC_CONTINUE, "Continue");
3119 addStatusCodeMap(SC_METHOD_NOT_ALLOWED, "Method Not Allowed");
3120 addStatusCodeMap(SC_CONFLICT, "Conflict");
3121 addStatusCodeMap(SC_PRECONDITION_FAILED, "Precondition Failed");
3122 addStatusCodeMap(SC_REQUEST_TOO_LONG, "Request Too Long");
3123 addStatusCodeMap(SC_UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type");
3124 // WebDav Status Codes
3125 addStatusCodeMap(SC_MULTI_STATUS, "Multi-Status");
3126 addStatusCodeMap(SC_UNPROCESSABLE_ENTITY, "Unprocessable Entity");
3127 addStatusCodeMap(SC_INSUFFICIENT_SPACE_ON_RESOURCE,
3128 "Insufficient Space On Resource");
3129 addStatusCodeMap(SC_METHOD_FAILURE, "Method Failure");
3130 addStatusCodeMap(SC_LOCKED, "Locked");
3131 }
3132
3133
3134 // --------------------------------------------------------- Public Methods
3135
3136
3137 /**
3138 * Returns the HTTP status text for the HTTP or WebDav status code
3139 * specified by looking it up in the static mapping. This is a
3140 * static function.
3141 *
3142 * @param nHttpStatusCode [IN] HTTP or WebDAV status code
3143 * @return A string with a short descriptive phrase for the
3144 * HTTP status code (e.g., "OK").
3145 */
3146 public static String getStatusText(int nHttpStatusCode) {
3147 Integer intKey = new Integer(nHttpStatusCode);
3148
3149 if (!mapStatusCodes.containsKey(intKey)) {
3150 return "";
3151 } else {
3152 return (String) mapStatusCodes.get(intKey);
3153 }
3154 }
3155
3156
3157 // -------------------------------------------------------- Private Methods
3158
3159
3160 /**
3161 * Adds a new status code -> status text mapping. This is a static
3162 * method because the mapping is a static variable.
3163 *
3164 * @param nKey [IN] HTTP or WebDAV status code
3165 * @param strVal [IN] HTTP status text
3166 */
3167 private static void addStatusCodeMap(int nKey, String strVal) {
3168 mapStatusCodes.put(new Integer(nKey), strVal);
3169 }
3170
3171 };
3172
3173