0001: /*
0002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
0003: *
0004: * This file is part of Resin(R) Open Source
0005: *
0006: * Each copy or derived work must preserve the copyright notice and this
0007: * notice unmodified.
0008: *
0009: * Resin Open Source is free software; you can redistribute it and/or modify
0010: * it under the terms of the GNU General Public License as published by
0011: * the Free Software Foundation; either version 2 of the License, or
0012: * (at your option) any later version.
0013: *
0014: * Resin Open Source is distributed in the hope that it will be useful,
0015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
0017: * of NON-INFRINGEMENT. See the GNU General Public License for more
0018: * details.
0019: *
0020: * You should have received a copy of the GNU General Public License
0021: * along with Resin Open Source; if not, write to the
0022: * Free SoftwareFoundation, Inc.
0023: * 59 Temple Place, Suite 330
0024: * Boston, MA 02111-1307 USA
0025: *
0026: * @author Scott Ferguson
0027: */
0028:
0029: package com.caucho.servlets.webdav;
0030:
0031: import com.caucho.log.Log;
0032: import com.caucho.server.webapp.Application;
0033: import com.caucho.util.CharBuffer;
0034: import com.caucho.util.HTTPUtil;
0035: import com.caucho.util.QDate;
0036: import com.caucho.vfs.Path;
0037: import com.caucho.vfs.ReadStream;
0038: import com.caucho.vfs.Vfs;
0039: import com.caucho.vfs.WriteStream;
0040: import com.caucho.xml.XmlParser;
0041:
0042: import org.xml.sax.Attributes;
0043: import org.xml.sax.SAXException;
0044:
0045: import javax.naming.Context;
0046: import javax.naming.InitialContext;
0047: import javax.servlet.GenericServlet;
0048: import javax.servlet.ServletContext;
0049: import javax.servlet.ServletException;
0050: import javax.servlet.ServletRequest;
0051: import javax.servlet.ServletResponse;
0052: import javax.servlet.http.HttpServletRequest;
0053: import javax.servlet.http.HttpServletResponse;
0054: import java.io.IOException;
0055: import java.io.InputStream;
0056: import java.io.OutputStream;
0057: import java.util.ArrayList;
0058: import java.util.Collections;
0059: import java.util.HashMap;
0060: import java.util.Iterator;
0061: import java.util.logging.Level;
0062: import java.util.logging.Logger;
0063:
0064: /**
0065: * Serves the WebDAV protocol. The underlying AbstractPath controls
0066: * the actual files served and modified. The default AbstractPath
0067: * just uses getRealPath from the current ServletContext.
0068: *
0069: * <p>More sophisticated users can customize AbstractPath to provide their
0070: * own WebDAV view for their objects, much like the Linux /proc
0071: * filesystem provides a view to Linux kernel modules.
0072: *
0073: * <pre>
0074: * <resource-ref res-ref-name='resin/webdav'>
0075: * <class-name>test.foo.MyDataSource</class-name>
0076: * <init-param my-foo='bar'/>
0077: * </resource-ref>
0078: *
0079: * <servlet-mapping url-pattern='/webdav/*'
0080: * servlet-name='com.caucho.http.webdav.WebDavServlet'>
0081: * <init-param enable='write'/>
0082: * <init-param path-source='resin/webdav'/>
0083: * </servlet-mapping>
0084: * </pre>
0085: */
0086: public class WebDavServlet extends GenericServlet {
0087: private static final Logger log = Log.open(WebDavServlet.class);
0088:
0089: private QDate _calendar = new QDate();
0090:
0091: private boolean _enable = false;
0092: private boolean _enableWrite = false;
0093:
0094: private boolean _addCrLf = false;
0095:
0096: private String _user;
0097: private String _role;
0098: private boolean _needsSecure;
0099: private AbstractPath _path;
0100: private String _root;
0101:
0102: /**
0103: * Sets the enable value.
0104: */
0105: public void setEnable(String enable) {
0106:
0107: if (enable == null || enable.equals(""))
0108: return;
0109: else if (enable.equals("read"))
0110: _enable = true;
0111: else if (enable.equals("write") || enable.equals("all")
0112: || enable.equals("yes") || enable.equals("true")) {
0113: _enable = true;
0114: _enableWrite = true;
0115: }
0116: }
0117:
0118: /**
0119: * Sets the allowed role.
0120: */
0121: public void setRole(String role) {
0122: _role = role;
0123: }
0124:
0125: /**
0126: * Sets the allowed user.
0127: */
0128: public void setUser(String user) {
0129: _user = user;
0130: }
0131:
0132: /**
0133: * Set true for securted.
0134: */
0135: public void setSecure(boolean needsSecure) {
0136: _needsSecure = needsSecure;
0137: }
0138:
0139: /**
0140: * Sets the path.
0141: */
0142: public void setPathSource(AbstractPath path) {
0143: _path = path;
0144: }
0145:
0146: /**
0147: * Sets the root.
0148: */
0149: public void setRoot(String root) {
0150: _root = root;
0151: }
0152:
0153: /**
0154: * Sets true if should add cr/lf
0155: */
0156: public void setCrLf(boolean addCrLf) {
0157: _addCrLf = addCrLf;
0158: }
0159:
0160: public void init() throws ServletException {
0161: String enable = getInitParameter("enable");
0162:
0163: if (enable != null)
0164: setEnable(enable);
0165:
0166: String role = getInitParameter("role");
0167: if (role != null)
0168: setRole(role);
0169:
0170: if (_role == null)
0171: _role = "webdav";
0172: else if (_role.equals("*"))
0173: _role = null;
0174:
0175: String user = getInitParameter("user");
0176: if (user != null)
0177: setUser(user);
0178:
0179: String secure = getInitParameter("secure");
0180: if (secure == null) {
0181: } else if ("false".equalsIgnoreCase(secure)
0182: || "no".equalsIgnoreCase(secure))
0183: _needsSecure = false;
0184: else
0185: _needsSecure = true;
0186:
0187: String pathSource = getInitParameter("path-source");
0188: try {
0189: if (pathSource != null) {
0190: Context env = (Context) new InitialContext()
0191: .lookup("java:comp/env");
0192: _path = (AbstractPath) env.lookup(pathSource);
0193: }
0194: } catch (Exception e) {
0195: log.log(Level.FINE, e.toString(), e);
0196: }
0197:
0198: try {
0199: if (pathSource != null && _path == null) {
0200: _path = (AbstractPath) new InitialContext()
0201: .lookup(pathSource);
0202: }
0203: } catch (Exception e) {
0204: throw new ServletException(e);
0205: }
0206:
0207: String root = getInitParameter("root");
0208:
0209: if (_path != null) {
0210: } else if (_root != null) {
0211: Path pwd = ((Application) getServletContext()).getAppDir();
0212:
0213: _path = new FilePath(pwd.lookup(_root));
0214: } else if (root != null) {
0215: Path pwd = ((Application) getServletContext()).getAppDir();
0216:
0217: _path = new FilePath(pwd.lookup(root));
0218: } else
0219: _path = new ApplicationPath();
0220: }
0221:
0222: /**
0223: * Service the webdav request.
0224: */
0225: public void service(ServletRequest request, ServletResponse response)
0226: throws ServletException, IOException {
0227: HttpServletRequest req = (HttpServletRequest) request;
0228: HttpServletResponse res = (HttpServletResponse) response;
0229:
0230: if (!_enable) {
0231: res.sendError(res.SC_FORBIDDEN);
0232: return;
0233: }
0234:
0235: if (_needsSecure && !req.isSecure()) {
0236: res.sendError(res.SC_FORBIDDEN);
0237: return;
0238: }
0239:
0240: if (_role != null && !req.isUserInRole(_role)) {
0241: res.sendError(res.SC_FORBIDDEN);
0242: return;
0243: }
0244:
0245: if (_user != null) {
0246: java.security.Principal principal = req.getUserPrincipal();
0247: if (principal == null) {
0248: res.sendError(res.SC_FORBIDDEN);
0249: return;
0250: }
0251: if (!principal.getName().equals(_user)) {
0252: res.sendError(res.SC_FORBIDDEN);
0253: return;
0254: }
0255: }
0256:
0257: ServletContext app = getServletContext();
0258: String requestURI = req.getRequestURI();
0259: String pathInfo = req.getPathInfo();
0260:
0261: String depthString = req.getHeader("Depth");
0262: int depth = Integer.MAX_VALUE;
0263:
0264: OutputStream os = res.getOutputStream();
0265: WriteStream out = Vfs.openWrite(os);
0266: out.setEncoding("UTF-8");
0267:
0268: if (_addCrLf)
0269: out.setNewlineString("\r\n");
0270:
0271: try {
0272: if ("0".equals(depthString))
0273: depth = 0;
0274: else if ("1".equals(depthString))
0275: depth = 1;
0276:
0277: if (req.getMethod().equals("OPTIONS")) {
0278: res.setHeader("DAV", "1");
0279:
0280: res.setHeader("MS-Author-Via", "DAV");
0281: if (_enableWrite)
0282: res
0283: .setHeader("Allow",
0284: "OPTIONS, PROPFIND, GET, HEAD, PUT, MKCOL, DELETE, COPY, MOVE, PROPPATCH");
0285: else if (_enable)
0286: res.setHeader("Allow",
0287: "OPTIONS, PROPFIND, GET, HEAD");
0288: } else if (req.getMethod().equals("PROPFIND")) {
0289: handlePropfind(req, res, out, depth);
0290: } else if (req.getMethod().equals("GET")
0291: || req.getMethod().equals("HEAD")) {
0292: handleGet(req, res, out);
0293: } else if (req.getMethod().equals("PUT") && _enableWrite) {
0294: handlePut(req, res, out);
0295: } else if (req.getMethod().equals("MKCOL") && _enableWrite) {
0296: handleMkcol(req, res, out);
0297: } else if (req.getMethod().equals("DELETE") && _enableWrite) {
0298: handleDelete(req, res, out);
0299: } else if (req.getMethod().equals("COPY") && _enableWrite) {
0300: handleCopy(req, res, out, depth);
0301: } else if (req.getMethod().equals("MOVE") && _enableWrite) {
0302: handleMove(req, res, out);
0303: } else if (req.getMethod().equals("PROPPATCH")
0304: && _enableWrite) {
0305: handleProppatch(req, res, out, depth);
0306: } else if (!_enableWrite && "PUT".equals(req.getMethod())
0307: || "MKCOL".equals(req.getMethod())
0308: || "DELETE".equals(req.getMethod())
0309: || "COPY".equals(req.getMethod())
0310: || "MOVE".equals(req.getMethod())
0311: || "PROPPATCH".equals(req.getMethod())) {
0312: res.sendError(res.SC_FORBIDDEN);
0313: } else {
0314: res.sendError(res.SC_NOT_IMPLEMENTED,
0315: "Method not implemented");
0316: }
0317: } finally {
0318: out.close();
0319: }
0320: }
0321:
0322: private void handlePropfind(HttpServletRequest req,
0323: HttpServletResponse res, WriteStream out, int depth)
0324: throws ServletException, IOException {
0325: InputStream is = req.getInputStream();
0326: PropfindHandler handler = new PropfindHandler();
0327: XmlParser parser = new XmlParser();
0328: parser.setContentHandler(handler);
0329:
0330: try {
0331: parser.parse(is);
0332: } catch (SAXException e) {
0333: sendError(res, out, res.SC_BAD_REQUEST,
0334: "Bad Request for PROPFIND", String.valueOf(e));
0335: return;
0336: }
0337:
0338: Application app = (Application) getServletContext();
0339: Path appDir = app.getAppDir();
0340:
0341: String pathInfo = req.getPathInfo();
0342: String uriPwd = app.getContextPath() + req.getServletPath();
0343:
0344: if (pathInfo == null)
0345: pathInfo = "/";
0346: else
0347: uriPwd = uriPwd + pathInfo;
0348:
0349: if (_path.isDirectory(pathInfo, req, app)
0350: && !uriPwd.endsWith("/"))
0351: uriPwd = uriPwd + "/";
0352:
0353: ServletContext rootApp = app.getContext("/");
0354:
0355: ArrayList<AttributeName> properties = handler.getProperties();
0356: boolean isPropname = handler.isPropname();
0357:
0358: if (properties.size() == 0)
0359: addAllProperties(properties, pathInfo, req, app);
0360:
0361: startMultistatus(res, out);
0362:
0363: printPathProperties(out, req, app, uriPwd, pathInfo,
0364: properties, isPropname, depth);
0365:
0366: out.println("</D:multistatus>");
0367: }
0368:
0369: /**
0370: * Proppatch sets properties. This implementation does not allow
0371: * any property setting.
0372: */
0373: private void handleProppatch(HttpServletRequest req,
0374: HttpServletResponse res, WriteStream out, int depth)
0375: throws ServletException, IOException {
0376: InputStream is = req.getInputStream();
0377: ProppatchHandler handler = new ProppatchHandler();
0378: XmlParser parser = new XmlParser();
0379: parser.setContentHandler(handler);
0380:
0381: try {
0382: parser.parse(is);
0383: } catch (SAXException e) {
0384: sendError(res, out, res.SC_BAD_REQUEST,
0385: "Bad Request for PROPPATCH", "Bad Request: " + e);
0386: return;
0387: }
0388:
0389: Application app = (Application) getServletContext();
0390: Path appDir = app.getAppDir();
0391:
0392: String pathInfo = req.getPathInfo();
0393: String uriPwd = app.getContextPath() + req.getServletPath();
0394:
0395: if (pathInfo == null)
0396: pathInfo = "/";
0397: else
0398: uriPwd = uriPwd + pathInfo;
0399:
0400: if (_path.isDirectory(pathInfo, req, app)
0401: && !uriPwd.endsWith("/"))
0402: uriPwd = uriPwd + "/";
0403:
0404: ArrayList forbidden = new ArrayList();
0405:
0406: startMultistatus(res, out);
0407:
0408: out.println("<D:response>");
0409: out.println("<D:href>" + escapeXml(uriPwd) + "</D:href>");
0410:
0411: ArrayList properties = new ArrayList();
0412: ArrayList<ProppatchCommand> commands = handler.getCommands();
0413:
0414: for (int i = 0; i < commands.size(); i++) {
0415: ProppatchCommand command = commands.get(i);
0416: int code = command.getCode();
0417: AttributeName name = command.getName();
0418: String value = command.getValue();
0419: int status;
0420:
0421: out.println("<D:propstat><D:prop><" + name.getName()
0422: + " xmlns:" + name.getPrefix() + "=\""
0423: + name.getNamespace() + "\"/>");
0424:
0425: if (code == ProppatchCommand.SET) {
0426: _path.setAttribute(name, value, pathInfo, req, app);
0427:
0428: out.println("<D:status>HTTP/1.1 200 OK</D:status>");
0429: } else if (code == ProppatchCommand.REMOVE) {
0430: _path.removeAttribute(name, pathInfo, req, app);
0431:
0432: out.println("<D:status>HTTP/1.1 200 OK</D:status>");
0433: } else
0434: out.println("<D:status>HTTP/1.1 424 Failed</D:status>");
0435:
0436: out.println("</D:prop></D:propstat>");
0437: }
0438:
0439: out.println("</D:response>");
0440:
0441: out.println("</D:multistatus>");
0442: }
0443:
0444: private void handlePut(HttpServletRequest req,
0445: HttpServletResponse res, WriteStream out)
0446: throws ServletException, IOException {
0447: ServletContext app = getServletContext();
0448:
0449: String pathInfo = req.getPathInfo();
0450: if (pathInfo == null)
0451: pathInfo = "/";
0452:
0453: if (!_path.isDirectory(getParent(pathInfo), req, app)) {
0454: sendError(res, out, 409, "Conflict",
0455: "PUT requires a parent collection");
0456: return;
0457: } else if (!_path.exists(pathInfo, req, app))
0458: res.setStatus(201, "Created");
0459: else
0460: res.setStatus(204, "No Content");
0461:
0462: OutputStream os;
0463:
0464: try {
0465: os = _path.openWrite(pathInfo, req, app);
0466: } catch (IOException e) {
0467: log.log(Level.FINE, e.toString(), e);
0468:
0469: sendError(res, out, 403, "Forbidden", "PUT forbidden");
0470: return;
0471: }
0472:
0473: WriteStream ws = Vfs.openWrite(os);
0474: Path path = ws.getPath();
0475: try {
0476: InputStream is = req.getInputStream();
0477: ws.writeStream(is);
0478: } finally {
0479: ws.close();
0480: }
0481: }
0482:
0483: /**
0484: * Creates a directory.
0485: */
0486: private void handleMkcol(HttpServletRequest req,
0487: HttpServletResponse res, WriteStream out)
0488: throws ServletException, IOException {
0489: res.setContentType("text/xml; charset=\"utf-8\"");
0490:
0491: ServletContext app = getServletContext();
0492:
0493: String pathInfo = req.getPathInfo();
0494: if (pathInfo == null)
0495: pathInfo = "/";
0496:
0497: if (_path.exists(pathInfo, req, app)) {
0498: res.sendError(res.SC_METHOD_NOT_ALLOWED,
0499: "Collection already exists");
0500: return;
0501: }
0502:
0503: if (!_path.isDirectory(getParent(pathInfo), req, app)) {
0504: res.sendError(res.SC_CONFLICT,
0505: "MKCOL needs parent collection");
0506: return;
0507: }
0508:
0509: InputStream is = req.getInputStream();
0510: int ch = is.read();
0511:
0512: if (ch >= 0) {
0513: res.sendError(res.SC_UNSUPPORTED_MEDIA_TYPE,
0514: "MKCOL doesn't understand content-type");
0515: return;
0516: }
0517:
0518: if (!_path.mkdir(pathInfo, req, app)) {
0519: res.sendError(res.SC_FORBIDDEN, "MKCOL forbidden");
0520: return;
0521: }
0522:
0523: res.setHeader("Location", req.getRequestURI());
0524: sendError(res, out, res.SC_CREATED, null, "Created collection "
0525: + HTTPUtil.encodeString(req.getRequestURI()));
0526: }
0527:
0528: private void addAllProperties(ArrayList<AttributeName> properties,
0529: String pathInfo, HttpServletRequest req, Application app)
0530: throws IOException, ServletException {
0531: properties.add(new AttributeName("DAV:", "resourcetype",
0532: "D:resourcetype"));
0533: properties.add(new AttributeName("DAV:", "getcontenttype",
0534: "D:getcontenttype"));
0535: properties.add(new AttributeName("DAV:", "getcontentlength",
0536: "D:getcontentlength"));
0537: properties.add(new AttributeName("DAV:", "creationdate",
0538: "D:creationdate"));
0539: properties.add(new AttributeName("DAV:", "getlastmodified",
0540: "D:getlastmodified"));
0541:
0542: Iterator<AttributeName> iter = _path.getAttributeNames(
0543: pathInfo, req, app);
0544: while (iter.hasNext()) {
0545: AttributeName name = iter.next();
0546:
0547: if (!properties.contains(name))
0548: properties.add(name);
0549: }
0550: }
0551:
0552: private void printPathProperties(WriteStream out,
0553: HttpServletRequest req, ServletContext app, String uri,
0554: String pathInfo, ArrayList<AttributeName> properties,
0555: boolean isPropname, int depth) throws IOException,
0556: ServletException {
0557: out.println("<D:response>");
0558: out.print("<D:href>");
0559: out.print(escapeXml(uri));
0560: out.println("</D:href>");
0561:
0562: if (!_path.exists(pathInfo, req, app)) {
0563: out.println("<D:propstat>");
0564: out.println("<D:status>HTTP/1.1 404 Not Found</D:status>");
0565: out.println("</D:propstat>");
0566: out.println("</D:response>");
0567: return;
0568: }
0569:
0570: ArrayList<AttributeName> unknownProperties = new ArrayList<AttributeName>();
0571:
0572: out.println("<D:propstat>");
0573:
0574: out.println("<D:prop>");
0575:
0576: boolean isDirectory = _path.isDirectory(pathInfo, req, app);
0577:
0578: for (int j = 0; j < properties.size(); j++) {
0579: AttributeName prop = properties.get(j);
0580: String localName = prop.getLocal();
0581: String propUri = prop.getNamespace();
0582: String qName = prop.getName();
0583: String prefix = prop.getPrefix();
0584:
0585: if (isPropname) {
0586: if (propUri.equals("DAV:"))
0587: out.println("<D:" + localName + "/>");
0588: else {
0589: String nsPrefix;
0590:
0591: if (prefix.equals("D")) {
0592: prefix = "caucho-D";
0593: qName = "caucho-D:" + localName;
0594: }
0595:
0596: if (prefix.equals(""))
0597: nsPrefix = "xmlns";
0598: else
0599: nsPrefix = "xmlns:" + prefix;
0600:
0601: out.println("<" + qName + " " + nsPrefix + "=\""
0602: + propUri + "\"/>");
0603: }
0604: continue;
0605: }
0606:
0607: String value = _path.getAttribute(prop, pathInfo, req, app);
0608: if (value != null) {
0609: String nsPrefix;
0610:
0611: if (prefix.equals("D")) {
0612: prefix = "caucho-D";
0613: qName = "caucho-D:" + localName;
0614: }
0615:
0616: if (prefix.equals(""))
0617: nsPrefix = "xmlns";
0618: else
0619: nsPrefix = "xmlns:" + prefix;
0620:
0621: out.print("<" + qName + " " + nsPrefix + "=\""
0622: + propUri + "\">");
0623: out.print(value);
0624: out.println("</" + prop.getName() + ">");
0625: continue;
0626: }
0627:
0628: if (!propUri.equals("DAV:")) {
0629: unknownProperties.add(prop);
0630: } else if (localName.equals("resourcetype")) {
0631: if (isDirectory) {
0632: out.print("<D:resourcetype>");
0633: out.print("<D:collection/>");
0634: out.println("</D:resourcetype>");
0635: } else {
0636: out.println("<D:resourcetype/>");
0637: }
0638: } else if (localName.equals("getcontentlength")) {
0639: out.print("<D:getcontentlength>");
0640: out.print(_path.getLength(pathInfo, req, app));
0641: out.println("</D:getcontentlength>");
0642: } else if (localName.equals("getlastmodified")) {
0643: out.print("<D:getlastmodified>");
0644: out.print(_calendar.formatGMT(_path.getLastModified(
0645: pathInfo, req, app)));
0646: out.println("</D:getlastmodified>");
0647: } else if (localName.equals("creationdate")) {
0648: out.print("<D:creationdate>");
0649:
0650: long time = _path.getLastModified(pathInfo, req, app);
0651:
0652: out.print(_calendar.formatGMT(time,
0653: "%Y-%m-%dT%H:%M:%SZ"));
0654:
0655: out.println("</D:creationdate>");
0656: } else if (localName.equals("displayname")) {
0657: out.print("<D:displayname>");
0658:
0659: String name = pathInfo;
0660: if (name.endsWith("/"))
0661: name = name.substring(0, name.length() - 1);
0662: int p = pathInfo.lastIndexOf('/');
0663: if (p > 0 && p < pathInfo.length())
0664: name = pathInfo.substring(p + 1);
0665:
0666: out.print(escapeXml(name));
0667:
0668: out.println("</D:displayname>");
0669: } else if (localName.equals("getcontenttype")) {
0670: String mimeType = app.getMimeType(uri);
0671:
0672: if (mimeType != null) {
0673: out.print("<D:getcontenttype>");
0674: out.print(mimeType);
0675: out.println("</D:getcontenttype>");
0676: } else {
0677: out.println("<D:getcontenttype/>");
0678: }
0679: } else
0680: unknownProperties.add(prop);
0681: }
0682:
0683: out.println("</D:prop>");
0684: out.println("<D:status>HTTP/1.1 200 OK</D:status>");
0685: out.println("</D:propstat>");
0686:
0687: if (unknownProperties.size() != 0) {
0688: out.println("<D:propstat>");
0689: out.println("<D:prop>");
0690:
0691: for (int j = 0; j < unknownProperties.size(); j++) {
0692: AttributeName prop = (AttributeName) unknownProperties
0693: .get(j);
0694:
0695: if (prop.getNamespace().equals("DAV:"))
0696: out.println("<D:" + prop.getLocal() + "/>");
0697: else {
0698: String nsPrefix;
0699: String prefix = prop.getPrefix();
0700: String qName = prop.getName();
0701:
0702: if (prefix.equals("D")) {
0703: prefix = "caucho-D";
0704: qName = "caucho-D:" + prop.getLocal();
0705: }
0706:
0707: if (prefix.equals(""))
0708: nsPrefix = "xmlns";
0709: else
0710: nsPrefix = "xmlns:" + prefix;
0711:
0712: out.println("<" + qName + " " + nsPrefix + "=\""
0713: + prop.getNamespace() + "\"/>");
0714: }
0715: }
0716: out.println("</D:prop>");
0717: out.println("<D:status>HTTP/1.1 404 Not Found</D:status>");
0718: out.println("</D:propstat>");
0719: }
0720:
0721: out.println("</D:response>");
0722:
0723: if (depth > 0 && _path.isDirectory(pathInfo, req, app)) {
0724: String[] list = _path.list(pathInfo, req, app);
0725: ArrayList<String> sortedList = new ArrayList<String>();
0726:
0727: for (int i = 0; i < list.length; i++)
0728: sortedList.add(list[i]);
0729: Collections.sort(sortedList);
0730:
0731: for (int i = 0; i < sortedList.size(); i++) {
0732: String filename = sortedList.get(i);
0733:
0734: String suburi;
0735: if (uri.endsWith("/"))
0736: suburi = uri + filename;
0737: else
0738: suburi = uri + "/" + filename;
0739:
0740: String subpath;
0741: if (pathInfo.endsWith("/"))
0742: subpath = pathInfo + filename;
0743: else
0744: subpath = pathInfo + "/" + filename;
0745:
0746: if (_path.isDirectory(subpath, req, app))
0747: suburi = suburi + '/';
0748:
0749: if (!_path.canRead(subpath, req, app)
0750: || filename.startsWith(".")
0751: || filename.equals("CVS")
0752: || filename.endsWith("~"))
0753: continue;
0754:
0755: printPathProperties(out, req, app, suburi, subpath,
0756: properties, isPropname, depth - 1);
0757: }
0758: }
0759: }
0760:
0761: private void handleDelete(HttpServletRequest req,
0762: HttpServletResponse res, WriteStream out)
0763: throws ServletException, IOException {
0764: ServletContext app = getServletContext();
0765:
0766: String pathInfo = req.getPathInfo();
0767: if (pathInfo == null)
0768: pathInfo = "/";
0769:
0770: String uri = req.getContextPath() + pathInfo;
0771:
0772: if (_path.isFile(pathInfo, req, app)) {
0773: if (!_path.remove(pathInfo, req, app))
0774: res.sendError(403, "Forbidden");
0775: else
0776: res.setStatus(204, "No Content");
0777: } else if (_path.isDirectory(pathInfo, req, app)) {
0778: if (deleteRecursive(req, res, out, uri, pathInfo, false)) {
0779: out
0780: .println("<D:status>HTTP/1.0 403 Forbidden</D:status>");
0781: out.println("</D:response>");
0782: out.println("</D:multistatus>");
0783: } else
0784: res.setStatus(204, "No Content");
0785: } else {
0786: res.sendError(res.SC_NOT_FOUND);
0787: }
0788: }
0789:
0790: private boolean deleteRecursive(HttpServletRequest req,
0791: HttpServletResponse res, WriteStream out, String uri,
0792: String pathInfo, boolean hasError) throws IOException {
0793: ServletContext app = getServletContext();
0794: boolean newError = false;
0795:
0796: if (_path.isDirectory(pathInfo, req, app)) {
0797: String[] list = _path.list(pathInfo, req, app);
0798: for (int i = 0; i < list.length; i++) {
0799: try {
0800: String suburi = lookup(uri, list[i]);
0801: String subpath = lookup(pathInfo, list[i]);
0802:
0803: hasError = deleteRecursive(req, res, out, suburi,
0804: subpath, hasError);
0805: } catch (IOException e) {
0806: log.log(Level.WARNING, e.toString(), e);
0807: }
0808: }
0809:
0810: if (!_path.rmdir(pathInfo, req, app))
0811: newError = true;
0812: } else if (!_path.remove(pathInfo, req, app))
0813: newError = true;
0814:
0815: if (newError) {
0816: if (!hasError) {
0817: startMultistatus(res, out);
0818: out.println("<D:response>");
0819: }
0820:
0821: out.println("<D:href>" + escapeXml(uri) + "</D:href>");
0822:
0823: hasError = true;
0824: }
0825:
0826: return hasError;
0827: }
0828:
0829: private void handleCopy(HttpServletRequest req,
0830: HttpServletResponse res, WriteStream out, int depth)
0831: throws ServletException, IOException {
0832: ServletContext app = getServletContext();
0833: String pathInfo = req.getPathInfo();
0834: if (pathInfo == null)
0835: pathInfo = "/";
0836:
0837: if (depth == 1)
0838: depth = Integer.MAX_VALUE;
0839:
0840: if (!_path.exists(pathInfo, req, app)) {
0841: res.sendError(res.SC_NOT_FOUND);
0842: return;
0843: }
0844:
0845: String destURI = getDestination(req);
0846: if (destURI == null) {
0847: res.sendError(403, "Forbidden");
0848: return;
0849: }
0850:
0851: String prefix = req.getContextPath();
0852: if (req.getServletPath() != null)
0853: prefix += req.getServletPath();
0854: if (!destURI.startsWith(prefix)) {
0855: res.sendError(403, "Forbidden");
0856: return;
0857: }
0858:
0859: String destPath = destURI.substring(prefix.length());
0860:
0861: if (destPath.equals(pathInfo)) {
0862: res.sendError(403, "Forbidden");
0863: return;
0864: } else if (destPath.startsWith(pathInfo)
0865: && (pathInfo.endsWith("/") || destPath
0866: .startsWith(pathInfo + '/'))) {
0867: res.sendError(403, "Forbidden");
0868: return;
0869: } else if (pathInfo.startsWith(destPath)
0870: && (destPath.endsWith("/") || pathInfo
0871: .startsWith(destPath + '/'))) {
0872: res.sendError(403, "Forbidden");
0873: return;
0874: }
0875:
0876: String overwrite = req.getHeader("Overwrite");
0877: if (overwrite == null)
0878: overwrite = "T";
0879:
0880: if (!_path.exists(destPath, req, app)) {
0881: res.setStatus(res.SC_CREATED);
0882: } else if (!overwrite.equals("F")) {
0883: removeRecursive(destPath, req);
0884: res.setStatus(204, "No Content");
0885: } else {
0886: res.sendError(412, "Overwrite not allowed for COPY");
0887: return;
0888: }
0889:
0890: if (!_path.exists(getParent(destPath), req, app)) {
0891: res.sendError(409, "COPY needs parent of destination");
0892: return;
0893: }
0894:
0895: if (_path.isFile(pathInfo, req, app)) {
0896: OutputStream os = _path.openWrite(destPath, req, app);
0897: WriteStream ws = Vfs.openWrite(os);
0898: try {
0899: InputStream is = _path.openRead(pathInfo, req, app);
0900: try {
0901: ws.writeStream(is);
0902: } finally {
0903: is.close();
0904: }
0905: } finally {
0906: ws.close();
0907: }
0908: return;
0909: } else {
0910: copyRecursive(pathInfo, destPath, depth, req);
0911: return;
0912: }
0913: }
0914:
0915: private void removeRecursive(String pathInfo, HttpServletRequest req)
0916: throws IOException {
0917: ServletContext app = getServletContext();
0918:
0919: if (_path.isDirectory(pathInfo, req, app)) {
0920: String[] list = _path.list(pathInfo, req, app);
0921:
0922: for (int i = 0; i < list.length; i++) {
0923: try {
0924: removeRecursive(lookup(pathInfo, list[i]), req);
0925: } catch (IOException e) {
0926: log.log(Level.WARNING, e.toString(), e);
0927: }
0928: }
0929: }
0930:
0931: _path.remove(pathInfo, req, app);
0932: }
0933:
0934: private void copyRecursive(String srcPath, String destPath,
0935: int depth, HttpServletRequest req) throws IOException {
0936: ServletContext app = getServletContext();
0937:
0938: if (_path.isDirectory(srcPath, req, app)) {
0939: _path.mkdir(destPath, req, app);
0940:
0941: if (depth == 0)
0942: return;
0943:
0944: String[] list = _path.list(srcPath, req, app);
0945: for (int i = 0; i < list.length; i++) {
0946: try {
0947: copyRecursive(lookup(srcPath, list[i]), lookup(
0948: destPath, list[i]), depth - 1, req);
0949: } catch (IOException e) {
0950: log.log(Level.WARNING, e.toString(), e);
0951: }
0952: }
0953: } else {
0954: OutputStream os = _path.openWrite(destPath, req, app);
0955: WriteStream ws = Vfs.openWrite(os);
0956: try {
0957: InputStream is = _path.openRead(srcPath, req, app);
0958: try {
0959: ws.writeStream(is);
0960: } finally {
0961: is.close();
0962: }
0963: } finally {
0964: ws.close();
0965: }
0966: }
0967: }
0968:
0969: private void handleMove(HttpServletRequest req,
0970: HttpServletResponse res, WriteStream out)
0971: throws ServletException, IOException {
0972: ServletContext app = getServletContext();
0973:
0974: String pathInfo = req.getPathInfo();
0975: if (pathInfo == null)
0976: pathInfo = "/";
0977:
0978: int depth = Integer.MAX_VALUE;
0979:
0980: if (!_path.exists(pathInfo, req, app)) {
0981: res.sendError(res.SC_NOT_FOUND);
0982: return;
0983: }
0984:
0985: String destURI = getDestination(req);
0986: if (destURI == null) {
0987: res.sendError(403, "Forbidden");
0988: return;
0989: }
0990:
0991: String prefix = req.getContextPath();
0992: if (req.getServletPath() != null)
0993: prefix += req.getServletPath();
0994: if (!destURI.startsWith(prefix)) {
0995: res.sendError(403, "Forbidden");
0996: return;
0997: }
0998:
0999: String destPath = destURI.substring(prefix.length());
1000:
1001: if (destPath.equals(pathInfo)) {
1002: res.sendError(403, "Forbidden");
1003: return;
1004: } else if (destPath.startsWith(pathInfo)
1005: && (pathInfo.endsWith("/") || destPath
1006: .startsWith(pathInfo + '/'))) {
1007: res.sendError(403, "Forbidden");
1008: return;
1009: } else if (pathInfo.startsWith(destPath)
1010: && (destPath.endsWith("/") || pathInfo
1011: .startsWith(destPath + '/'))) {
1012: res.sendError(403, "Forbidden");
1013: return;
1014: }
1015:
1016: String overwrite = req.getHeader("Overwrite");
1017: if (overwrite == null)
1018: overwrite = "T";
1019:
1020: if (!_path.exists(destPath, req, app)) {
1021: res.setStatus(res.SC_CREATED);
1022: } else if (!overwrite.equals("F")) {
1023: removeRecursive(destPath, req);
1024: res.setStatus(204, "No Content");
1025: } else {
1026: res.sendError(412, "Overwrite not allowed for MOVE");
1027: return;
1028: }
1029:
1030: if (!_path.exists(getParent(destPath), req, app)) {
1031: res.sendError(409, "MOVE needs parent of destination");
1032: return;
1033: }
1034:
1035: if (_path.rename(pathInfo, destPath, req, app)) {
1036: // try renaming directly
1037: res.setStatus(204, "No Content");
1038: } else if (_path.isFile(pathInfo, req, app)) {
1039: HashMap<AttributeName, String> props = getProperties(
1040: pathInfo, req, app);
1041: OutputStream os = _path.openWrite(destPath, req, app);
1042: WriteStream ws = Vfs.openWrite(os);
1043:
1044: try {
1045: InputStream is = _path.openRead(pathInfo, req, app);
1046: try {
1047: ws.writeStream(is);
1048: } finally {
1049: is.close();
1050: }
1051: } finally {
1052: ws.close();
1053: }
1054: setProperties(props, destPath, req, app);
1055:
1056: _path.remove(pathInfo, req, app);
1057: } else {
1058: moveRecursive(pathInfo, destPath, req);
1059: res.setStatus(204, "No Content");
1060: }
1061: }
1062:
1063: private String getDestination(HttpServletRequest request) {
1064: String dest = request.getHeader("Destination");
1065:
1066: dest = java.net.URLDecoder.decode(dest);
1067:
1068: if (dest.startsWith("/"))
1069: return dest;
1070:
1071: String prefix = request.getScheme() + "://";
1072: String host = request.getHeader("Host");
1073: if (host != null)
1074: prefix = prefix + host.toLowerCase();
1075:
1076: if (dest.startsWith(prefix))
1077: return dest.substring(prefix.length());
1078: else
1079: return null;
1080: }
1081:
1082: private void moveRecursive(String srcPath, String destPath,
1083: HttpServletRequest req) throws IOException {
1084: ServletContext app = getServletContext();
1085:
1086: if (_path.isDirectory(srcPath, req, app)) {
1087: _path.mkdir(destPath, req, app);
1088:
1089: String[] list = _path.list(srcPath, req, app);
1090: for (int i = 0; i < list.length; i++) {
1091: try {
1092: moveRecursive(lookup(srcPath, list[i]), lookup(
1093: destPath, list[i]), req);
1094: } catch (IOException e) {
1095: log.log(Level.WARNING, e.toString(), e);
1096: }
1097: }
1098:
1099: _path.remove(srcPath, req, app);
1100: } else {
1101: HashMap<AttributeName, String> props = getProperties(
1102: srcPath, req, app);
1103: OutputStream os = _path.openWrite(destPath, req, app);
1104: WriteStream rs = Vfs.openWrite(os);
1105:
1106: try {
1107: InputStream is = _path.openRead(srcPath, req, app);
1108: try {
1109: rs.writeStream(is);
1110: } finally {
1111: is.close();
1112: }
1113: } finally {
1114: rs.close();
1115: os.close();
1116: }
1117:
1118: setProperties(props, destPath, req, app);
1119: _path.remove(srcPath, req, app);
1120: }
1121: }
1122:
1123: /**
1124: * Grabs all the properties from a path.
1125: */
1126: private HashMap<AttributeName, String> getProperties(
1127: String pathInfo, HttpServletRequest req, ServletContext app)
1128: throws IOException {
1129: HashMap<AttributeName, String> properties = null;
1130:
1131: Iterator<AttributeName> iter = _path.getAttributeNames(
1132: pathInfo, req, app);
1133: while (iter.hasNext()) {
1134: AttributeName name = iter.next();
1135: String value = _path.getAttribute(name, pathInfo, req, app);
1136:
1137: if (properties == null)
1138: properties = new HashMap<AttributeName, String>();
1139:
1140: properties.put(name, value);
1141: }
1142:
1143: return properties;
1144: }
1145:
1146: /**
1147: * Sets all the properties for a path.
1148: */
1149: private void setProperties(HashMap<AttributeName, String> map,
1150: String pathInfo, HttpServletRequest req, ServletContext app)
1151: throws IOException {
1152: if (map == null)
1153: return;
1154:
1155: Iterator<AttributeName> iter = map.keySet().iterator();
1156: while (iter.hasNext()) {
1157: AttributeName name = iter.next();
1158:
1159: String value = map.get(name);
1160:
1161: _path.setAttribute(name, value, pathInfo, req, app);
1162: }
1163: }
1164:
1165: private void handleGet(HttpServletRequest req,
1166: HttpServletResponse res, WriteStream out)
1167: throws ServletException, IOException {
1168: ServletContext app = getServletContext();
1169:
1170: String pathInfo = req.getPathInfo();
1171: if (pathInfo == null)
1172: pathInfo = "/";
1173:
1174: String mimeType = app.getMimeType(pathInfo);
1175: res.setContentType(mimeType);
1176:
1177: if (!_path.isFile(pathInfo, req, app)
1178: || !_path.canRead(pathInfo, req, app)) {
1179: res.sendError(res.SC_NOT_FOUND);
1180: return;
1181: }
1182:
1183: long length = _path.getLength(pathInfo, req, app);
1184: res.setContentLength((int) length);
1185:
1186: if ("HTTP/1.1".equals(req.getProtocol())) {
1187: res.setDateHeader("Last-Modified", _path.getLastModified(
1188: pathInfo, req, app));
1189: res.setHeader("Cache-Control", "private");
1190: }
1191:
1192: if (req.getMethod().equals("HEAD"))
1193: return;
1194:
1195: OutputStream os = res.getOutputStream();
1196: InputStream is = _path.openRead(pathInfo, req, app);
1197: ReadStream rs = Vfs.openRead(is);
1198: try {
1199: rs.writeToStream(os);
1200: } finally {
1201: rs.close();
1202: }
1203: }
1204:
1205: protected void startMultistatus(HttpServletResponse res,
1206: WriteStream out) throws IOException {
1207: res.setStatus(207, "Multistatus");
1208: res.setContentType("text/xml; charset=\"utf-8\"");
1209:
1210: out.println("<?xml version=\"1.0\"?>");
1211: out.println("<D:multistatus xmlns:D=\"DAV:\">");
1212: }
1213:
1214: protected void sendError(HttpServletResponse res, WriteStream out,
1215: int status, String statusText, String message)
1216: throws IOException {
1217: if (statusText == null)
1218: res.setStatus(status);
1219: else
1220: res.setStatus(status, statusText);
1221:
1222: res.setContentType("text/html");
1223:
1224: if (statusText != null) {
1225: out.print("<title>");
1226: out.print(statusText);
1227: out.println("</title>");
1228: out.print("<h1>");
1229: out.print(statusText);
1230: out.println("</h1>");
1231: out.println(message);
1232: } else {
1233: out.print("<title>");
1234: out.print(message);
1235: out.println("</title>");
1236: out.print("<h1>");
1237: out.print(message);
1238: out.println("</h1>");
1239: }
1240: }
1241:
1242: private void handleDirectory(HttpServletRequest req,
1243: HttpServletResponse res, WriteStream out, String pathInfo)
1244: throws IOException, ServletException {
1245: ServletContext app = getServletContext();
1246:
1247: res.setContentType("text/html");
1248:
1249: out.println("<title>Directory of " + pathInfo + "</title>");
1250: out.println("<h1>Directory of " + pathInfo + "</h1>");
1251:
1252: String[] list = _path.list(pathInfo, req, app);
1253: for (int i = 0; i < list.length; i++) {
1254: out.println("<a href=\"" + list[i] + "\">" + list[i]
1255: + "</a><br>");
1256: }
1257: }
1258:
1259: private String escapeXml(String data) {
1260: CharBuffer cb = CharBuffer.allocate();
1261: for (int i = 0; i < data.length(); i++) {
1262: char ch = data.charAt(i);
1263:
1264: switch (ch) {
1265: case '<':
1266: cb.append("<");
1267: break;
1268: case '>':
1269: cb.append(">");
1270: break;
1271: case '&':
1272: cb.append("&");
1273: break;
1274: default:
1275: cb.append(ch);
1276: break;
1277: }
1278: }
1279:
1280: return cb.close();
1281: }
1282:
1283: protected String getParent(String pathInfo) {
1284: int p = pathInfo.lastIndexOf('/', pathInfo.length() - 2);
1285:
1286: if (p < 0)
1287: return "/";
1288: else
1289: return pathInfo.substring(0, p);
1290: }
1291:
1292: protected String lookup(String parent, String child) {
1293: if (parent.endsWith("/"))
1294: return parent + child;
1295: else
1296: return parent + '/' + child;
1297: }
1298:
1299: public void destroy() {
1300: _path.destroy();
1301: }
1302:
1303: static class PropfindHandler extends
1304: org.xml.sax.helpers.DefaultHandler {
1305: ArrayList<AttributeName> properties = new ArrayList<AttributeName>();
1306: boolean inProp;
1307: boolean isPropname;
1308:
1309: ArrayList<AttributeName> getProperties() {
1310: return properties;
1311: }
1312:
1313: boolean isPropname() {
1314: return isPropname;
1315: }
1316:
1317: public void startElement(String uri, String localName,
1318: String qName, Attributes attributes)
1319: throws SAXException {
1320: if (localName.equals("prop"))
1321: inProp = true;
1322: else if (localName.equals("propname"))
1323: isPropname = true;
1324: else if (inProp) {
1325: if (qName.indexOf(':') > 0 && uri.equals(""))
1326: throw new SAXException("illegal empty namespace");
1327:
1328: properties
1329: .add(new AttributeName(uri, localName, qName));
1330: }
1331: }
1332:
1333: public void endElement(String uri, String localName,
1334: String qName) throws SAXException {
1335: if (localName.equals("prop"))
1336: inProp = false;
1337: }
1338: }
1339:
1340: static class ProppatchHandler extends
1341: org.xml.sax.helpers.DefaultHandler {
1342: ArrayList<ProppatchCommand> _commands = new ArrayList<ProppatchCommand>();
1343: boolean _inProp;
1344: boolean _inSet;
1345: boolean _inRemove;
1346: boolean _isPropname;
1347: AttributeName _attributeName;
1348: CharBuffer _value;
1349:
1350: boolean isPropname() {
1351: return _isPropname;
1352: }
1353:
1354: ArrayList<ProppatchCommand> getCommands() {
1355: return _commands;
1356: }
1357:
1358: public void startElement(String uri, String localName,
1359: String qName, Attributes attributes)
1360: throws SAXException {
1361: if (localName.equals("set"))
1362: _inSet = true;
1363: else if (localName.equals("remove"))
1364: _inRemove = true;
1365: else if (localName.equals("prop"))
1366: _inProp = true;
1367: else if (localName.equals("propname"))
1368: _isPropname = true;
1369: else if (!_inProp) {
1370: } else if (_attributeName == null) {
1371: _attributeName = new AttributeName(uri, localName,
1372: qName);
1373: _value = CharBuffer.allocate();
1374: } else {
1375: int p = qName.indexOf(':');
1376:
1377: if (p > 0)
1378: _value.append("<" + qName + " xmlns:"
1379: + qName.substring(p + 1) + "=\"" + uri
1380: + "\">");
1381: else
1382: _value.append("<" + qName + " xmlns=\"" + uri
1383: + "\">");
1384: }
1385: }
1386:
1387: public void characters(char[] buffer, int offset, int length) {
1388: if (_value != null)
1389: _value.append(buffer, offset, length);
1390: }
1391:
1392: public void endElement(String uri, String localName,
1393: String qName) throws SAXException {
1394: if (localName.equals("prop"))
1395: _inProp = false;
1396: else if (localName.equals("set"))
1397: _inSet = false;
1398: else if (localName.equals("remove"))
1399: _inRemove = false;
1400: else if (_attributeName == null) {
1401: } else if (localName.equals(_attributeName.getLocal())
1402: && uri.equals(_attributeName.getNamespace())) {
1403: if (_inSet) {
1404: _commands.add(new ProppatchCommand(
1405: ProppatchCommand.SET, _attributeName,
1406: _value.close()));
1407: } else if (_inRemove) {
1408: _commands.add(new ProppatchCommand(
1409: ProppatchCommand.REMOVE, _attributeName,
1410: _value.close()));
1411: }
1412:
1413: _value = null;
1414: _attributeName = null;
1415: } else {
1416: _value.append("</" + qName + ">");
1417: }
1418: }
1419: }
1420:
1421: static class ProppatchCommand {
1422: public static int SET = 0;
1423: public static int REMOVE = 1;
1424: public static int CHANGE = 2;
1425:
1426: private int _code;
1427: private AttributeName _name;
1428: private String _value;
1429:
1430: ProppatchCommand(int code, AttributeName name, String value) {
1431: _code = code;
1432: _name = name;
1433: _value = value;
1434: }
1435:
1436: int getCode() {
1437: return _code;
1438: }
1439:
1440: AttributeName getName() {
1441: return _name;
1442: }
1443:
1444: String getValue() {
1445: return _value;
1446: }
1447: }
1448: }
|