1 28 29 package com.caucho.servlets.webdav; 30 31 import com.caucho.log.Log; 32 import com.caucho.server.webapp.Application; 33 import com.caucho.util.CharBuffer; 34 import com.caucho.util.HTTPUtil; 35 import com.caucho.util.QDate; 36 import com.caucho.vfs.Path; 37 import com.caucho.vfs.ReadStream; 38 import com.caucho.vfs.Vfs; 39 import com.caucho.vfs.WriteStream; 40 import com.caucho.xml.XmlParser; 41 42 import org.xml.sax.Attributes ; 43 import org.xml.sax.SAXException ; 44 45 import javax.naming.Context ; 46 import javax.naming.InitialContext ; 47 import javax.servlet.GenericServlet ; 48 import javax.servlet.ServletContext ; 49 import javax.servlet.ServletException ; 50 import javax.servlet.ServletRequest ; 51 import javax.servlet.ServletResponse ; 52 import javax.servlet.http.HttpServletRequest ; 53 import javax.servlet.http.HttpServletResponse ; 54 import java.io.IOException ; 55 import java.io.InputStream ; 56 import java.io.OutputStream ; 57 import java.util.ArrayList ; 58 import java.util.Collections ; 59 import java.util.HashMap ; 60 import java.util.Iterator ; 61 import java.util.logging.Level ; 62 import java.util.logging.Logger ; 63 64 86 public class WebDavServlet extends GenericServlet { 87 private static final Logger log = Log.open(WebDavServlet.class); 88 89 private QDate _calendar = new QDate(); 90 91 private boolean _enable = false; 92 private boolean _enableWrite = false; 93 94 private boolean _addCrLf = false; 95 96 private String _user; 97 private String _role; 98 private boolean _needsSecure; 99 private AbstractPath _path; 100 private String _root; 101 102 105 public void setEnable(String enable) 106 { 107 108 if (enable == null || enable.equals("")) 109 return; 110 else if (enable.equals("read")) 111 _enable = true; 112 else if (enable.equals("write") || 113 enable.equals("all") || 114 enable.equals("yes") || 115 enable.equals("true")) { 116 _enable = true; 117 _enableWrite = true; 118 } 119 } 120 121 124 public void setRole(String role) 125 { 126 _role = role; 127 } 128 129 132 public void setUser(String user) 133 { 134 _user = user; 135 } 136 137 140 public void setSecure(boolean needsSecure) 141 { 142 _needsSecure = needsSecure; 143 } 144 145 148 public void setPathSource(AbstractPath path) 149 { 150 _path = path; 151 } 152 153 156 public void setRoot(String root) 157 { 158 _root = root; 159 } 160 161 164 public void setCrLf(boolean addCrLf) 165 { 166 _addCrLf = addCrLf; 167 } 168 169 public void init() 170 throws ServletException 171 { 172 String enable = getInitParameter("enable"); 173 174 if (enable != null) 175 setEnable(enable); 176 177 String role = getInitParameter("role"); 178 if (role != null) 179 setRole(role); 180 181 if (_role == null) 182 _role = "webdav"; 183 else if (_role.equals("*")) 184 _role = null; 185 186 String user = getInitParameter("user"); 187 if (user != null) 188 setUser(user); 189 190 String secure = getInitParameter("secure"); 191 if (secure == null) { 192 } 193 else if ("false".equalsIgnoreCase(secure) || 194 "no".equalsIgnoreCase(secure)) 195 _needsSecure = false; 196 else 197 _needsSecure = true; 198 199 String pathSource = getInitParameter("path-source"); 200 try { 201 if (pathSource != null) { 202 Context env = (Context ) new InitialContext ().lookup("java:comp/env"); 203 _path = (AbstractPath) env.lookup(pathSource); 204 } 205 } catch (Exception e) { 206 log.log(Level.FINE, e.toString(), e); 207 } 208 209 try { 210 if (pathSource != null && _path == null) { 211 _path = (AbstractPath) new InitialContext ().lookup(pathSource); 212 } 213 } catch (Exception e) { 214 throw new ServletException (e); 215 } 216 217 String root = getInitParameter("root"); 218 219 if (_path != null) { 220 } 221 else if (_root != null) { 222 Path pwd = ((Application) getServletContext()).getAppDir(); 223 224 _path = new FilePath(pwd.lookup(_root)); 225 } 226 else if (root != null) { 227 Path pwd = ((Application) getServletContext()).getAppDir(); 228 229 _path = new FilePath(pwd.lookup(root)); 230 } 231 else 232 _path = new ApplicationPath(); 233 } 234 235 238 public void service(ServletRequest request, ServletResponse response) 239 throws ServletException , IOException 240 { 241 HttpServletRequest req = (HttpServletRequest ) request; 242 HttpServletResponse res = (HttpServletResponse ) response; 243 244 if (! _enable) { 245 res.sendError(res.SC_FORBIDDEN); 246 return; 247 } 248 249 if (_needsSecure && ! req.isSecure()) { 250 res.sendError(res.SC_FORBIDDEN); 251 return; 252 } 253 254 if (_role != null && ! req.isUserInRole(_role)) { 255 res.sendError(res.SC_FORBIDDEN); 256 return; 257 } 258 259 if (_user != null) { 260 java.security.Principal principal = req.getUserPrincipal(); 261 if (principal == null) { 262 res.sendError(res.SC_FORBIDDEN); 263 return; 264 } 265 if (! principal.getName().equals(_user)) { 266 res.sendError(res.SC_FORBIDDEN); 267 return; 268 } 269 } 270 271 ServletContext app = getServletContext(); 272 String requestURI = req.getRequestURI(); 273 String pathInfo = req.getPathInfo(); 274 275 String depthString = req.getHeader("Depth"); 276 int depth = Integer.MAX_VALUE; 277 278 OutputStream os = res.getOutputStream(); 279 WriteStream out = Vfs.openWrite(os); 280 out.setEncoding("UTF-8"); 281 282 if (_addCrLf) 283 out.setNewlineString("\r\n"); 284 285 try { 286 if ("0".equals(depthString)) 287 depth = 0; 288 else if ("1".equals(depthString)) 289 depth = 1; 290 291 if (req.getMethod().equals("OPTIONS")) { 292 res.setHeader("DAV", "1"); 293 294 res.setHeader("MS-Author-Via", "DAV"); 295 if (_enableWrite) 296 res.setHeader("Allow", "OPTIONS, PROPFIND, GET, HEAD, PUT, MKCOL, DELETE, COPY, MOVE, PROPPATCH"); 297 else if (_enable) 298 res.setHeader("Allow", "OPTIONS, PROPFIND, GET, HEAD"); 299 } 300 else if (req.getMethod().equals("PROPFIND")) { 301 handlePropfind(req, res, out, depth); 302 } 303 else if (req.getMethod().equals("GET") || 304 req.getMethod().equals("HEAD")) { 305 handleGet(req, res, out); 306 } 307 else if (req.getMethod().equals("PUT") && _enableWrite) { 308 handlePut(req, res, out); 309 } 310 else if (req.getMethod().equals("MKCOL") && _enableWrite) { 311 handleMkcol(req, res, out); 312 } 313 else if (req.getMethod().equals("DELETE") && _enableWrite) { 314 handleDelete(req, res, out); 315 } 316 else if (req.getMethod().equals("COPY") && _enableWrite) { 317 handleCopy(req, res, out, depth); 318 } 319 else if (req.getMethod().equals("MOVE") && _enableWrite) { 320 handleMove(req, res, out); 321 } 322 else if (req.getMethod().equals("PROPPATCH") && _enableWrite) { 323 handleProppatch(req, res, out, depth); 324 } 325 else if (! _enableWrite && 326 "PUT".equals(req.getMethod()) || 327 "MKCOL".equals(req.getMethod()) || 328 "DELETE".equals(req.getMethod()) || 329 "COPY".equals(req.getMethod()) || 330 "MOVE".equals(req.getMethod()) || 331 "PROPPATCH".equals(req.getMethod())) { 332 res.sendError(res.SC_FORBIDDEN); 333 } 334 else { 335 res.sendError(res.SC_NOT_IMPLEMENTED, "Method not implemented"); 336 } 337 } finally { 338 out.close(); 339 } 340 } 341 342 private void handlePropfind(HttpServletRequest req, 343 HttpServletResponse res, 344 WriteStream out, 345 int depth) 346 throws ServletException , IOException 347 { 348 InputStream is = req.getInputStream(); 349 PropfindHandler handler = new PropfindHandler(); 350 XmlParser parser = new XmlParser(); 351 parser.setContentHandler(handler); 352 353 try { 354 parser.parse(is); 355 } catch (SAXException e) { 356 sendError(res, out, res.SC_BAD_REQUEST, "Bad Request for PROPFIND", 357 String.valueOf(e)); 358 return; 359 } 360 361 Application app = (Application) getServletContext(); 362 Path appDir = app.getAppDir(); 363 364 String pathInfo = req.getPathInfo(); 365 String uriPwd = app.getContextPath() + req.getServletPath(); 366 367 if (pathInfo == null) 368 pathInfo = "/"; 369 else 370 uriPwd = uriPwd + pathInfo; 371 372 if (_path.isDirectory(pathInfo, req, app) && ! uriPwd.endsWith("/")) 373 uriPwd = uriPwd + "/"; 374 375 ServletContext rootApp = app.getContext("/"); 376 377 ArrayList <AttributeName> properties = handler.getProperties(); 378 boolean isPropname = handler.isPropname(); 379 380 if (properties.size() == 0) 381 addAllProperties(properties, pathInfo, req, app); 382 383 startMultistatus(res, out); 384 385 printPathProperties(out, req, app, uriPwd, pathInfo, 386 properties, isPropname, depth); 387 388 out.println("</D:multistatus>"); 389 } 390 391 395 private void handleProppatch(HttpServletRequest req, 396 HttpServletResponse res, 397 WriteStream out, 398 int depth) 399 throws ServletException , IOException 400 { 401 InputStream is = req.getInputStream(); 402 ProppatchHandler handler = new ProppatchHandler(); 403 XmlParser parser = new XmlParser(); 404 parser.setContentHandler(handler); 405 406 try { 407 parser.parse(is); 408 } catch (SAXException e) { 409 sendError(res, out, res.SC_BAD_REQUEST, "Bad Request for PROPPATCH", 410 "Bad Request: " + e); 411 return; 412 } 413 414 Application app = (Application) getServletContext(); 415 Path appDir = app.getAppDir(); 416 417 String pathInfo = req.getPathInfo(); 418 String uriPwd = app.getContextPath() + req.getServletPath(); 419 420 if (pathInfo == null) 421 pathInfo = "/"; 422 else 423 uriPwd = uriPwd + pathInfo; 424 425 if (_path.isDirectory(pathInfo, req, app) && ! uriPwd.endsWith("/")) 426 uriPwd = uriPwd + "/"; 427 428 ArrayList forbidden = new ArrayList (); 429 430 startMultistatus(res, out); 431 432 out.println("<D:response>"); 433 out.println("<D:href>" + escapeXml(uriPwd) + "</D:href>"); 434 435 ArrayList properties = new ArrayList (); 436 ArrayList <ProppatchCommand> commands = handler.getCommands(); 437 438 for (int i = 0; i < commands.size(); i++) { 439 ProppatchCommand command = commands.get(i); 440 int code = command.getCode(); 441 AttributeName name = command.getName(); 442 String value = command.getValue(); 443 int status; 444 445 out.println("<D:propstat><D:prop><" + name.getName() + " xmlns:" + 446 name.getPrefix() + "=\"" + name.getNamespace() + "\"/>"); 447 448 if (code == ProppatchCommand.SET) { 449 _path.setAttribute(name, value, pathInfo, req, app); 450 451 out.println("<D:status>HTTP/1.1 200 OK</D:status>"); 452 } 453 else if (code == ProppatchCommand.REMOVE) { 454 _path.removeAttribute(name, pathInfo, req, app); 455 456 out.println("<D:status>HTTP/1.1 200 OK</D:status>"); 457 } 458 else 459 out.println("<D:status>HTTP/1.1 424 Failed</D:status>"); 460 461 out.println("</D:prop></D:propstat>"); 462 } 463 464 out.println("</D:response>"); 465 466 out.println("</D:multistatus>"); 467 } 468 469 private void handlePut(HttpServletRequest req, 470 HttpServletResponse res, 471 WriteStream out) 472 throws ServletException , IOException 473 { 474 ServletContext app = getServletContext(); 475 476 String pathInfo = req.getPathInfo(); 477 if (pathInfo == null) 478 pathInfo = "/"; 479 480 if (! _path.isDirectory(getParent(pathInfo), req, app)) { 481 sendError(res, out, 409, "Conflict", "PUT requires a parent collection"); 482 return; 483 } 484 else if (! _path.exists(pathInfo, req, app)) 485 res.setStatus(201, "Created"); 486 else 487 res.setStatus(204, "No Content"); 488 489 OutputStream os; 490 491 try { 492 os = _path.openWrite(pathInfo, req, app); 493 } catch (IOException e) { 494 log.log(Level.FINE, e.toString(), e); 495 496 sendError(res, out, 403, "Forbidden", "PUT forbidden"); 497 return; 498 } 499 500 WriteStream ws = Vfs.openWrite(os); 501 Path path =ws.getPath(); 502 try { 503 InputStream is = req.getInputStream(); 504 ws.writeStream(is); 505 } finally { 506 ws.close(); 507 } 508 } 509 510 513 private void handleMkcol(HttpServletRequest req, 514 HttpServletResponse res, 515 WriteStream out) 516 throws ServletException , IOException 517 { 518 res.setContentType("text/xml; charset=\"utf-8\""); 519 520 ServletContext app = getServletContext(); 521 522 String pathInfo = req.getPathInfo(); 523 if (pathInfo == null) 524 pathInfo = "/"; 525 526 if (_path.exists(pathInfo, req, app)) { 527 res.sendError(res.SC_METHOD_NOT_ALLOWED, "Collection already exists"); 528 return; 529 } 530 531 if (! _path.isDirectory(getParent(pathInfo), req, app)) { 532 res.sendError(res.SC_CONFLICT, "MKCOL needs parent collection"); 533 return; 534 } 535 536 InputStream is = req.getInputStream(); 537 int ch = is.read(); 538 539 if (ch >= 0) { 540 res.sendError(res.SC_UNSUPPORTED_MEDIA_TYPE, "MKCOL doesn't understand content-type"); 541 return; 542 } 543 544 if (! _path.mkdir(pathInfo, req, app)) { 545 res.sendError(res.SC_FORBIDDEN, "MKCOL forbidden"); 546 return; 547 } 548 549 res.setHeader("Location", req.getRequestURI()); 550 sendError(res, out, res.SC_CREATED, null, 551 "Created collection " + 552 HTTPUtil.encodeString(req.getRequestURI())); 553 } 554 555 private void addAllProperties(ArrayList <AttributeName> properties, 556 String pathInfo, 557 HttpServletRequest req, 558 Application app) 559 throws IOException , ServletException 560 { 561 properties.add(new AttributeName("DAV:", "resourcetype", "D:resourcetype")); 562 properties.add(new AttributeName("DAV:", "getcontenttype", "D:getcontenttype")); 563 properties.add(new AttributeName("DAV:", "getcontentlength", "D:getcontentlength")); 564 properties.add(new AttributeName("DAV:", "creationdate", "D:creationdate")); 565 properties.add(new AttributeName("DAV:", "getlastmodified", "D:getlastmodified")); 566 567 Iterator <AttributeName> iter = _path.getAttributeNames(pathInfo, req, app); 568 while (iter.hasNext()) { 569 AttributeName name = iter.next(); 570 571 if (! properties.contains(name)) 572 properties.add(name); 573 } 574 } 575 576 private void printPathProperties(WriteStream out, 577 HttpServletRequest req, 578 ServletContext app, 579 String uri, String pathInfo, 580 ArrayList <AttributeName> properties, 581 boolean isPropname, int depth) 582 throws IOException , ServletException 583 { 584 out.println("<D:response>"); 585 out.print("<D:href>"); 586 out.print(escapeXml(uri)); 587 out.println("</D:href>"); 588 589 if (! _path.exists(pathInfo, req, app)) { 590 out.println("<D:propstat>"); 591 out.println("<D:status>HTTP/1.1 404 Not Found</D:status>"); 592 out.println("</D:propstat>"); 593 out.println("</D:response>"); 594 return; 595 } 596 597 ArrayList <AttributeName> unknownProperties = new ArrayList <AttributeName>(); 598 599 out.println("<D:propstat>"); 600 601 out.println("<D:prop>"); 602 603 boolean isDirectory = _path.isDirectory(pathInfo, req, app); 604 605 for (int j = 0; j < properties.size(); j++) { 606 AttributeName prop = properties.get(j); 607 String localName = prop.getLocal(); 608 String propUri = prop.getNamespace(); 609 String qName = prop.getName(); 610 String prefix = prop.getPrefix(); 611 612 if (isPropname) { 613 if (propUri.equals("DAV:")) 614 out.println("<D:" + localName + "/>"); 615 else { 616 String nsPrefix; 617 618 if (prefix.equals("D")) { 619 prefix = "caucho-D"; 620 qName = "caucho-D:" + localName; 621 } 622 623 if (prefix.equals("")) 624 nsPrefix = "xmlns"; 625 else 626 nsPrefix = "xmlns:" + prefix; 627 628 out.println("<" + qName + " " + nsPrefix + "=\"" + 629 propUri + "\"/>"); 630 } 631 continue; 632 } 633 634 String value = _path.getAttribute(prop, pathInfo, req, app); 635 if (value != null) { 636 String nsPrefix; 637 638 if (prefix.equals("D")) { 639 prefix = "caucho-D"; 640 qName = "caucho-D:" + localName; 641 } 642 643 if (prefix.equals("")) 644 nsPrefix = "xmlns"; 645 else 646 nsPrefix = "xmlns:" + prefix; 647 648 out.print("<" + qName + " " + nsPrefix + "=\"" + 649 propUri + "\">"); 650 out.print(value); 651 out.println("</" + prop.getName() + ">"); 652 continue; 653 } 654 655 if (! propUri.equals("DAV:")) { 656 unknownProperties.add(prop); 657 } 658 else if (localName.equals("resourcetype")) { 659 if (isDirectory) { 660 out.print("<D:resourcetype>"); 661 out.print("<D:collection/>"); 662 out.println("</D:resourcetype>"); 663 } 664 else { 665 out.println("<D:resourcetype/>"); 666 } 667 } 668 else if (localName.equals("getcontentlength")) { 669 out.print("<D:getcontentlength>"); 670 out.print(_path.getLength(pathInfo, req, app)); 671 out.println("</D:getcontentlength>"); 672 } 673 else if (localName.equals("getlastmodified")) { 674 out.print("<D:getlastmodified>"); 675 out.print(_calendar.formatGMT(_path.getLastModified(pathInfo, req, app))); 676 out.println("</D:getlastmodified>"); 677 } 678 else if (localName.equals("creationdate")) { 679 out.print("<D:creationdate>"); 680 681 long time = _path.getLastModified(pathInfo, req, app); 682 683 out.print(_calendar.formatGMT(time, "%Y-%m-%dT%H:%M:%SZ")); 684 685 out.println("</D:creationdate>"); 686 } 687 else if (localName.equals("displayname")) { 688 out.print("<D:displayname>"); 689 690 String name = pathInfo; 691 if (name.endsWith("/")) 692 name = name.substring(0, name.length() - 1); 693 int p = pathInfo.lastIndexOf('/'); 694 if (p > 0 && p < pathInfo.length()) 695 name = pathInfo.substring(p + 1); 696 697 out.print(escapeXml(name)); 698 699 out.println("</D:displayname>"); 700 } 701 else if (localName.equals("getcontenttype")) { 702 String mimeType = app.getMimeType(uri); 703 704 if (mimeType != null) { 705 out.print("<D:getcontenttype>"); 706 out.print(mimeType); 707 out.println("</D:getcontenttype>"); 708 } 709 else { 710 out.println("<D:getcontenttype/>"); 711 } 712 } 713 else 714 unknownProperties.add(prop); 715 } 716 717 out.println("</D:prop>"); 718 out.println("<D:status>HTTP/1.1 200 OK</D:status>"); 719 out.println("</D:propstat>"); 720 721 if (unknownProperties.size() != 0) { 722 out.println("<D:propstat>"); 723 out.println("<D:prop>"); 724 725 for (int j = 0; j < unknownProperties.size(); j++) { 726 AttributeName prop = (AttributeName) unknownProperties.get(j); 727 728 if (prop.getNamespace().equals("DAV:")) 729 out.println("<D:" + prop.getLocal() + "/>"); 730 else { 731 String nsPrefix; 732 String prefix = prop.getPrefix(); 733 String qName = prop.getName(); 734 735 if (prefix.equals("D")) { 736 prefix = "caucho-D"; 737 qName = "caucho-D:" + prop.getLocal(); 738 } 739 740 if (prefix.equals("")) 741 nsPrefix = "xmlns"; 742 else 743 nsPrefix = "xmlns:" + prefix; 744 745 out.println("<" + qName + " " + nsPrefix + "=\"" + 746 prop.getNamespace() + "\"/>"); 747 } 748 } 749 out.println("</D:prop>"); 750 out.println("<D:status>HTTP/1.1 404 Not Found</D:status>"); 751 out.println("</D:propstat>"); 752 } 753 754 out.println("</D:response>"); 755 756 if (depth > 0 && _path.isDirectory(pathInfo, req, app)) { 757 String []list = _path.list(pathInfo, req, app); 758 ArrayList <String > sortedList = new ArrayList <String >(); 759 760 for (int i = 0; i < list.length; i++) 761 sortedList.add(list[i]); 762 Collections.sort(sortedList); 763 764 for (int i = 0; i < sortedList.size(); i++) { 765 String filename = sortedList.get(i); 766 767 String suburi; 768 if (uri.endsWith("/")) 769 suburi = uri + filename; 770 else 771 suburi = uri + "/" + filename; 772 773 String subpath; 774 if (pathInfo.endsWith("/")) 775 subpath = pathInfo + filename; 776 else 777 subpath = pathInfo + "/" + filename; 778 779 if (_path.isDirectory(subpath, req, app)) 780 suburi = suburi + '/'; 781 782 if (! _path.canRead(subpath, req, app) || filename.startsWith(".") || 783 filename.equals("CVS") || filename.endsWith("~")) 784 continue; 785 786 printPathProperties(out, req, app, suburi, subpath, 787 properties, isPropname, depth - 1); 788 } 789 } 790 } 791 792 private void handleDelete(HttpServletRequest req, 793 HttpServletResponse res, 794 WriteStream out) 795 throws ServletException , IOException 796 { 797 ServletContext app = getServletContext(); 798 799 String pathInfo = req.getPathInfo(); 800 if (pathInfo == null) 801 pathInfo = "/"; 802 803 String uri = req.getContextPath() + pathInfo; 804 805 if (_path.isFile(pathInfo, req, app)) { 806 if (! _path.remove(pathInfo, req, app)) 807 res.sendError(403, "Forbidden"); 808 else 809 res.setStatus(204, "No Content"); 810 } 811 else if (_path.isDirectory(pathInfo, req, app)) { 812 if (deleteRecursive(req, res, out, uri, pathInfo, false)) { 813 out.println("<D:status>HTTP/1.0 403 Forbidden</D:status>"); 814 out.println("</D:response>"); 815 out.println("</D:multistatus>"); 816 } 817 else 818 res.setStatus(204, "No Content"); 819 } 820 else { 821 res.sendError(res.SC_NOT_FOUND); 822 } 823 } 824 825 private boolean deleteRecursive(HttpServletRequest req, 826 HttpServletResponse res, 827 WriteStream out, 828 String uri, String pathInfo, 829 boolean hasError) 830 throws IOException 831 { 832 ServletContext app = getServletContext(); 833 boolean newError = false; 834 835 if (_path.isDirectory(pathInfo, req, app)) { 836 String []list = _path.list(pathInfo, req, app); 837 for (int i = 0; i < list.length; i++) { 838 try { 839 String suburi = lookup(uri, list[i]); 840 String subpath = lookup(pathInfo, list[i]); 841 842 hasError = deleteRecursive(req, res, out, suburi, subpath, hasError); 843 } catch (IOException e) { 844 log.log(Level.WARNING, e.toString(), e); 845 } 846 } 847 848 if (! _path.rmdir(pathInfo, req, app)) 849 newError = true; 850 } 851 else if (! _path.remove(pathInfo, req, app)) 852 newError = true; 853 854 if (newError) { 855 if (! hasError) { 856 startMultistatus(res, out); 857 out.println("<D:response>"); 858 } 859 860 out.println("<D:href>" + escapeXml(uri) + "</D:href>"); 861 862 hasError = true; 863 } 864 865 return hasError; 866 } 867 868 private void handleCopy(HttpServletRequest req, 869 HttpServletResponse res, 870 WriteStream out, 871 int depth) 872 throws ServletException , IOException 873 { 874 ServletContext app = getServletContext(); 875 String pathInfo = req.getPathInfo(); 876 if (pathInfo == null) 877 pathInfo = "/"; 878 879 if (depth == 1) 880 depth = Integer.MAX_VALUE; 881 882 if (! _path.exists(pathInfo, req, app)) { 883 res.sendError(res.SC_NOT_FOUND); 884 return; 885 } 886 887 String destURI = getDestination(req); 888 if (destURI == null) { 889 res.sendError(403, "Forbidden"); 890 return; 891 } 892 893 String prefix = req.getContextPath(); 894 if (req.getServletPath() != null) 895 prefix += req.getServletPath(); 896 if (! destURI.startsWith(prefix)) { 897 res.sendError(403, "Forbidden"); 898 return; 899 } 900 901 String destPath = destURI.substring(prefix.length()); 902 903 if (destPath.equals(pathInfo)) { 904 res.sendError(403, "Forbidden"); 905 return; 906 } 907 else if (destPath.startsWith(pathInfo) && 908 (pathInfo.endsWith("/") || destPath.startsWith(pathInfo + '/'))) { 909 res.sendError(403, "Forbidden"); 910 return; 911 } 912 else if (pathInfo.startsWith(destPath) && 913 (destPath.endsWith("/") || pathInfo.startsWith(destPath + '/'))) { 914 res.sendError(403, "Forbidden"); 915 return; 916 } 917 918 String overwrite = req.getHeader("Overwrite"); 919 if (overwrite == null) 920 overwrite = "T"; 921 922 if (! _path.exists(destPath, req, app)) { 923 res.setStatus(res.SC_CREATED); 924 } 925 else if (! overwrite.equals("F")) { 926 removeRecursive(destPath, req); 927 res.setStatus(204, "No Content"); 928 } 929 else { 930 res.sendError(412, "Overwrite not allowed for COPY"); 931 return; 932 } 933 934 if (! _path.exists(getParent(destPath), req, app)) { 935 res.sendError(409, "COPY needs parent of destination"); 936 return; 937 } 938 939 if (_path.isFile(pathInfo, req, app)) { 940 OutputStream os = _path.openWrite(destPath, req, app); 941 WriteStream ws = Vfs.openWrite(os); 942 try { 943 InputStream is = _path.openRead(pathInfo, req, app); 944 try { 945 ws.writeStream(is); 946 } finally { 947 is.close(); 948 } 949 } finally { 950 ws.close(); 951 } 952 return; 953 } 954 else { 955 copyRecursive(pathInfo, destPath, depth, req); 956 return; 957 } 958 } 959 960 private void removeRecursive(String pathInfo, HttpServletRequest req) 961 throws IOException 962 { 963 ServletContext app = getServletContext(); 964 965 if (_path.isDirectory(pathInfo, req, app)) { 966 String []list = _path.list(pathInfo, req, app); 967 968 for (int i = 0; i < list.length; i++) { 969 try { 970 removeRecursive(lookup(pathInfo, list[i]), req); 971 } catch (IOException e) { 972 log.log(Level.WARNING, e.toString(), e); 973 } 974 } 975 } 976 977 _path.remove(pathInfo, req, app); 978 } 979 980 private void copyRecursive(String srcPath, String destPath, int depth, 981 HttpServletRequest req) 982 throws IOException 983 { 984 ServletContext app = getServletContext(); 985 986 if (_path.isDirectory(srcPath, req, app)) { 987 _path.mkdir(destPath, req, app); 988 989 if (depth == 0) 990 return; 991 992 String []list = _path.list(srcPath, req, app); 993 for (int i = 0; i < list.length; i++) { 994 try { 995 copyRecursive(lookup(srcPath, list[i]), 996 lookup(destPath, list[i]), depth - 1, 997 req); 998 } catch (IOException e) { 999 log.log(Level.WARNING, e.toString(), e); 1000 } 1001 } 1002 } 1003 else { 1004 OutputStream os = _path.openWrite(destPath, req, app); 1005 WriteStream ws = Vfs.openWrite(os); 1006 try { 1007 InputStream is = _path.openRead(srcPath, req, app); 1008 try { 1009 ws.writeStream(is); 1010 } finally { 1011 is.close(); 1012 } 1013 } finally { 1014 ws.close(); 1015 } 1016 } 1017 } 1018 1019 private void handleMove(HttpServletRequest req, 1020 HttpServletResponse res, 1021 WriteStream out) 1022 throws ServletException , IOException 1023 { 1024 ServletContext app = getServletContext(); 1025 1026 String pathInfo = req.getPathInfo(); 1027 if (pathInfo == null) 1028 pathInfo = "/"; 1029 1030 int depth = Integer.MAX_VALUE; 1031 1032 if (! _path.exists(pathInfo, req, app)) { 1033 res.sendError(res.SC_NOT_FOUND); 1034 return; 1035 } 1036 1037 String destURI = getDestination(req); 1038 if (destURI == null) { 1039 res.sendError(403, "Forbidden"); 1040 return; 1041 } 1042 1043 String prefix = req.getContextPath(); 1044 if (req.getServletPath() != null) 1045 prefix += req.getServletPath(); 1046 if (! destURI.startsWith(prefix)) { 1047 res.sendError(403, "Forbidden"); 1048 return; 1049 } 1050 1051 String destPath = destURI.substring(prefix.length()); 1052 1053 if (destPath.equals(pathInfo)) { 1054 res.sendError(403, "Forbidden"); 1055 return; 1056 } 1057 else if (destPath.startsWith(pathInfo) && 1058 (pathInfo.endsWith("/") || destPath.startsWith(pathInfo + '/'))) { 1059 res.sendError(403, "Forbidden"); 1060 return; 1061 } 1062 else if (pathInfo.startsWith(destPath) && 1063 (destPath.endsWith("/") || pathInfo.startsWith(destPath + '/'))) { 1064 res.sendError(403, "Forbidden"); 1065 return; 1066 } 1067 1068 String overwrite = req.getHeader("Overwrite"); 1069 if (overwrite == null) 1070 overwrite = "T"; 1071 1072 if (! _path.exists(destPath, req, app)) { 1073 res.setStatus(res.SC_CREATED); 1074 } 1075 else if (! overwrite.equals("F")) { 1076 removeRecursive(destPath, req); 1077 res.setStatus(204, "No Content"); 1078 } 1079 else { 1080 res.sendError(412, "Overwrite not allowed for MOVE"); 1081 return; 1082 } 1083 1084 if (! _path.exists(getParent(destPath), req, app)) { 1085 res.sendError(409, "MOVE needs parent of destination"); 1086 return; 1087 } 1088 1089 if (_path.rename(pathInfo, destPath, req, app)) { 1090 res.setStatus(204, "No Content"); 1092 } 1093 else if (_path.isFile(pathInfo, req, app)) { 1094 HashMap <AttributeName,String > props = getProperties(pathInfo, req, app); 1095 OutputStream os = _path.openWrite(destPath, req, app); 1096 WriteStream ws = Vfs.openWrite(os); 1097 1098 try { 1099 InputStream is = _path.openRead(pathInfo, req, app); 1100 try { 1101 ws.writeStream(is); 1102 } finally { 1103 is.close(); 1104 } 1105 } finally { 1106 ws.close(); 1107 } 1108 setProperties(props, destPath, req, app); 1109 1110 _path.remove(pathInfo, req, app); 1111 } 1112 else { 1113 moveRecursive(pathInfo, destPath, req); 1114 res.setStatus(204, "No Content"); 1115 } 1116 } 1117 1118 private String getDestination(HttpServletRequest request) 1119 { 1120 String dest = request.getHeader("Destination"); 1121 1122 dest = java.net.URLDecoder.decode(dest); 1123 1124 if (dest.startsWith("/")) 1125 return dest; 1126 1127 String prefix = request.getScheme() + "://"; 1128 String host = request.getHeader("Host"); 1129 if (host != null) 1130 prefix = prefix + host.toLowerCase(); 1131 1132 if (dest.startsWith(prefix)) 1133 return dest.substring(prefix.length()); 1134 else 1135 return null; 1136 } 1137 1138 private void moveRecursive(String srcPath, String destPath, 1139 HttpServletRequest req) 1140 throws IOException 1141 { 1142 ServletContext app = getServletContext(); 1143 1144 if (_path.isDirectory(srcPath, req, app)) { 1145 _path.mkdir(destPath, req, app); 1146 1147 String []list = _path.list(srcPath, req, app); 1148 for (int i = 0; i < list.length; i++) { 1149 try { 1150 moveRecursive(lookup(srcPath, list[i]), 1151 lookup(destPath, list[i]), 1152 req); 1153 } catch (IOException e) { 1154 log.log(Level.WARNING, e.toString(), e); 1155 } 1156 } 1157 1158 _path.remove(srcPath, req, app); 1159 } 1160 else { 1161 HashMap <AttributeName,String > props = getProperties(srcPath, req, app); 1162 OutputStream os = _path.openWrite(destPath, req, app); 1163 WriteStream rs = Vfs.openWrite(os); 1164 1165 try { 1166 InputStream is = _path.openRead(srcPath, req, app); 1167 try { 1168 rs.writeStream(is); 1169 } finally { 1170 is.close(); 1171 } 1172 } finally { 1173 rs.close(); 1174 os.close(); 1175 } 1176 1177 setProperties(props, destPath, req, app); 1178 _path.remove(srcPath, req, app); 1179 } 1180 } 1181 1182 1185 private HashMap <AttributeName,String > getProperties(String pathInfo, 1186 HttpServletRequest req, 1187 ServletContext app) 1188 throws IOException 1189 { 1190 HashMap <AttributeName,String > properties = null; 1191 1192 Iterator <AttributeName> iter = _path.getAttributeNames(pathInfo, req, app); 1193 while (iter.hasNext()) { 1194 AttributeName name = iter.next(); 1195 String value = _path.getAttribute(name, pathInfo, req, app); 1196 1197 if (properties == null) 1198 properties = new HashMap <AttributeName,String >(); 1199 1200 properties.put(name, value); 1201 } 1202 1203 return properties; 1204 } 1205 1206 1209 private void setProperties(HashMap <AttributeName,String > map, 1210 String pathInfo, 1211 HttpServletRequest req, 1212 ServletContext app) 1213 throws IOException 1214 { 1215 if (map == null) 1216 return; 1217 1218 Iterator <AttributeName> iter = map.keySet().iterator(); 1219 while (iter.hasNext()) { 1220 AttributeName name = iter.next(); 1221 1222 String value = map.get(name); 1223 1224 _path.setAttribute(name, value, pathInfo, req, app); 1225 } 1226 } 1227 1228 private void handleGet(HttpServletRequest req, 1229 HttpServletResponse res, 1230 WriteStream out) 1231 throws ServletException , IOException 1232 { 1233 ServletContext app = getServletContext(); 1234 1235 String pathInfo = req.getPathInfo(); 1236 if (pathInfo == null) 1237 pathInfo = "/"; 1238 1239 String mimeType = app.getMimeType(pathInfo); 1240 res.setContentType(mimeType); 1241 1242 if (! _path.isFile(pathInfo, req, app) || 1243 ! _path.canRead(pathInfo, req, app)) { 1244 res.sendError(res.SC_NOT_FOUND); 1245 return; 1246 } 1247 1248 long length = _path.getLength(pathInfo, req, app); 1249 res.setContentLength((int) length); 1250 1251 if ("HTTP/1.1".equals(req.getProtocol())) { 1252 res.setDateHeader("Last-Modified", _path.getLastModified(pathInfo, req, app)); 1253 res.setHeader("Cache-Control", "private"); 1254 } 1255 1256 if (req.getMethod().equals("HEAD")) 1257 return; 1258 1259 OutputStream os = res.getOutputStream(); 1260 InputStream is = _path.openRead(pathInfo, req, app); 1261 ReadStream rs = Vfs.openRead(is); 1262 try { 1263 rs.writeToStream(os); 1264 } finally { 1265 rs.close(); 1266 } 1267 } 1268 1269 protected void startMultistatus(HttpServletResponse res, 1270 WriteStream out) 1271 throws IOException 1272 { 1273 res.setStatus(207, "Multistatus"); 1274 res.setContentType("text/xml; charset=\"utf-8\""); 1275 1276 out.println("<?xml version=\"1.0\"?>"); 1277 out.println("<D:multistatus xmlns:D=\"DAV:\">"); 1278 } 1279 1280 protected void sendError(HttpServletResponse res, 1281 WriteStream out, 1282 int status, String statusText, String message) 1283 throws IOException 1284 { 1285 if (statusText == null) 1286 res.setStatus(status); 1287 else 1288 res.setStatus(status, statusText); 1289 1290 res.setContentType("text/html"); 1291 1292 if (statusText != null) { 1293 out.print("<title>"); 1294 out.print(statusText); 1295 out.println("</title>"); 1296 out.print("<h1>"); 1297 out.print(statusText); 1298 out.println("</h1>"); 1299 out.println(message); 1300 } 1301 else { 1302 out.print("<title>"); 1303 out.print(message); 1304 out.println("</title>"); 1305 out.print("<h1>"); 1306 out.print(message); 1307 out.println("</h1>"); 1308 } 1309 } 1310 1311 private void handleDirectory(HttpServletRequest req, 1312 HttpServletResponse res, 1313 WriteStream out, 1314 String pathInfo) 1315 throws IOException , ServletException 1316 { 1317 ServletContext app = getServletContext(); 1318 1319 res.setContentType("text/html"); 1320 1321 out.println("<title>Directory of " + pathInfo + "</title>"); 1322 out.println("<h1>Directory of " + pathInfo + "</h1>"); 1323 1324 String []list = _path.list(pathInfo, req, app); 1325 for (int i = 0; i < list.length; i++) { 1326 out.println("<a HREF=\"" + list[i] + "\">" + list[i] + "</a><br>"); 1327 } 1328 } 1329 1330 private String escapeXml(String data) 1331 { 1332 CharBuffer cb = CharBuffer.allocate(); 1333 for (int i = 0; i < data.length(); i++) { 1334 char ch = data.charAt(i); 1335 1336 switch (ch) { 1337 case '<': 1338 cb.append("<"); 1339 break; 1340 case '>': 1341 cb.append(">"); 1342 break; 1343 case '&': 1344 cb.append("&"); 1345 break; 1346 default: 1347 cb.append(ch); 1348 break; 1349 } 1350 } 1351 1352 return cb.close(); 1353 } 1354 1355 protected String getParent(String pathInfo) 1356 { 1357 int p = pathInfo.lastIndexOf('/', pathInfo.length() - 2); 1358 1359 if (p < 0) 1360 return "/"; 1361 else 1362 return pathInfo.substring(0, p); 1363 } 1364 1365 protected String lookup(String parent, String child) 1366 { 1367 if (parent.endsWith("/")) 1368 return parent + child; 1369 else 1370 return parent + '/' + child; 1371 } 1372 1373 public void destroy() 1374 { 1375 _path.destroy(); 1376 } 1377 1378 static class PropfindHandler extends org.xml.sax.helpers.DefaultHandler { 1379 ArrayList <AttributeName> properties = new ArrayList <AttributeName>(); 1380 boolean inProp; 1381 boolean isPropname; 1382 1383 ArrayList <AttributeName> getProperties() 1384 { 1385 return properties; 1386 } 1387 1388 boolean isPropname() 1389 { 1390 return isPropname; 1391 } 1392 1393 public void startElement (String uri, String localName, 1394 String qName, Attributes attributes) 1395 throws SAXException 1396 { 1397 if (localName.equals("prop")) 1398 inProp = true; 1399 else if (localName.equals("propname")) 1400 isPropname = true; 1401 else if (inProp) { 1402 if (qName.indexOf(':') > 0 && uri.equals("")) 1403 throw new SAXException ("illegal empty namespace"); 1404 1405 properties.add(new AttributeName(uri, localName, qName)); 1406 } 1407 } 1408 1409 public void endElement (String uri, String localName, String qName) 1410 throws SAXException 1411 { 1412 if (localName.equals("prop")) 1413 inProp = false; 1414 } 1415 } 1416 1417 static class ProppatchHandler extends org.xml.sax.helpers.DefaultHandler { 1418 ArrayList <ProppatchCommand> _commands = new ArrayList <ProppatchCommand>(); 1419 boolean _inProp; 1420 boolean _inSet; 1421 boolean _inRemove; 1422 boolean _isPropname; 1423 AttributeName _attributeName; 1424 CharBuffer _value; 1425 1426 boolean isPropname() 1427 { 1428 return _isPropname; 1429 } 1430 1431 ArrayList <ProppatchCommand> getCommands() 1432 { 1433 return _commands; 1434 } 1435 1436 public void startElement(String uri, String localName, 1437 String qName, Attributes attributes) 1438 throws SAXException 1439 { 1440 if (localName.equals("set")) 1441 _inSet = true; 1442 else if (localName.equals("remove")) 1443 _inRemove = true; 1444 else if (localName.equals("prop")) 1445 _inProp = true; 1446 else if (localName.equals("propname")) 1447 _isPropname = true; 1448 else if (! _inProp) { 1449 } 1450 else if (_attributeName == null) { 1451 _attributeName = new AttributeName(uri, localName, qName); 1452 _value = CharBuffer.allocate(); 1453 } 1454 else { 1455 int p = qName.indexOf(':'); 1456 1457 if (p > 0) 1458 _value.append("<" + qName + " xmlns:" + qName.substring(p + 1) + 1459 "=\"" + uri + "\">"); 1460 else 1461 _value.append("<" + qName + " xmlns=\"" + uri + "\">"); 1462 } 1463 } 1464 1465 public void characters(char []buffer, int offset, int length) 1466 { 1467 if (_value != null) 1468 _value.append(buffer, offset, length); 1469 } 1470 1471 public void endElement (String uri, String localName, String qName) 1472 throws SAXException 1473 { 1474 if (localName.equals("prop")) 1475 _inProp = false; 1476 else if (localName.equals("set")) 1477 _inSet = false; 1478 else if (localName.equals("remove")) 1479 _inRemove = false; 1480 else if (_attributeName == null) { 1481 } 1482 else if (localName.equals(_attributeName.getLocal()) && 1483 uri.equals(_attributeName.getNamespace())) { 1484 if (_inSet) { 1485 _commands.add(new ProppatchCommand(ProppatchCommand.SET, 1486 _attributeName, 1487 _value.close())); 1488 } 1489 else if (_inRemove) { 1490 _commands.add(new ProppatchCommand(ProppatchCommand.REMOVE, 1491 _attributeName, 1492 _value.close())); 1493 } 1494 1495 _value = null; 1496 _attributeName = null; 1497 } 1498 else { 1499 _value.append("</" + qName + ">"); 1500 } 1501 } 1502 } 1503 1504 static class ProppatchCommand { 1505 public static int SET = 0; 1506 public static int REMOVE = 1; 1507 public static int CHANGE = 2; 1508 1509 private int _code; 1510 private AttributeName _name; 1511 private String _value; 1512 1513 ProppatchCommand(int code, AttributeName name, String value) 1514 { 1515 _code = code; 1516 _name = name; 1517 _value = value; 1518 } 1519 1520 int getCode() 1521 { 1522 return _code; 1523 } 1524 1525 AttributeName getName() 1526 { 1527 return _name; 1528 } 1529 1530 String getValue() 1531 { 1532 return _value; 1533 } 1534 } 1535} 1536 | Popular Tags |