1 /** 2 * Authors: Szabo Bogdan <szabobogdan@yahoo.com> 3 * Date: 2 15, 2015 4 * License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 5 * Copyright: Public Domain 6 */ 7 module vibedav.base; 8 9 public import vibedav.prop; 10 public import vibedav.ifheader; 11 public import vibedav.locks; 12 public import vibedav.http; 13 public import vibedav.davresource; 14 15 import vibe.core.log; 16 import vibe.core.file; 17 import vibe.inet.mimetypes; 18 import vibe.inet.message; 19 import vibe.http.server; 20 21 import std.conv : to; 22 import std.algorithm; 23 import std.file; 24 import std.path; 25 import std.digest.md; 26 import std.datetime; 27 import std.array; 28 import std..string; 29 import std.stdio; 30 import std.typecons; 31 import std.uri; 32 import std.algorithm.sorting, std.algorithm.setops; 33 34 class DavStorage { 35 static { 36 DavLockList locks; 37 } 38 } 39 40 class DavException : Exception { 41 HTTPStatus status; 42 string mime; 43 44 /// 45 this(HTTPStatus status, string msg, string mime = "plain/text", string file = __FILE__, size_t line = __LINE__, Throwable next = null) 46 { 47 super(msg, file, line, next); 48 this.status = status; 49 this.mime = mime; 50 } 51 52 /// 53 this(HTTPStatus status, string msg, Throwable next, string mime = "plain/text", string file = __FILE__, size_t line = __LINE__) 54 { 55 super(msg, next, file, line); 56 this.status = status; 57 this.mime = mime; 58 } 59 } 60 61 62 enum NoticeAction { 63 created, 64 deleted, 65 changed 66 } 67 68 string stripSlashes(string path) { 69 return path.stripBeginSlashes.stripEndSlasshes; 70 } 71 72 string stripBeginSlashes(string path) { 73 if(path.length > 0 && path[0] == '/') 74 path = path[1..$]; 75 76 if(path.length > 1 && path[0..2] == "./") 77 path = path[2..$]; 78 79 return path; 80 } 81 82 string stripEndSlasshes(string path) { 83 if(path.length > 0 && path[path.length-1] == '/') 84 path = path[0..$-1]; 85 86 return path; 87 } 88 89 Path getFilePath(Path baseUrlPath, Path basePath, URL url) { 90 string path = url.path.toString.stripSlashes; 91 string filePath; 92 93 filePath = path[baseUrlPath.toString.length..$].stripSlashes; 94 95 return basePath ~ filePath; 96 } 97 98 @("Basic getFilePath") 99 unittest { 100 auto path = getFilePath(Path("test/"), Path("/base/"), URL("http://127.0.0.1/test/file.txt")); 101 assert(path.toString == "/base/file.txt"); 102 } 103 104 string urlToString(URL url) { 105 string name = url.path.toNativeString; 106 return name.replace(" ", "%20"); 107 } 108 109 struct DavReport { 110 string name; 111 string ns; 112 } 113 114 string reportName(DavProp reportBody) { 115 if(reportBody[0].name == "?xml") 116 return reportBody[1].tagName ~ ":" ~ reportBody[1].namespace; 117 118 return reportBody[0].tagName ~ ":" ~ reportBody[0].namespace; 119 } 120 121 DavReport getReportProperty(T...)() { 122 static if(T.length == 0) 123 static assert(false, "There is no `@DavReport` attribute."); 124 else static if( is(typeof(T[0]) == DavReport) ) 125 return T[0]; 126 else 127 return getResourceProperty!(T[1..$]); 128 } 129 130 bool hasDavReport(I)(string key) { 131 bool result = false; 132 133 void keyExist(T...)() { 134 static if(T.length > 0) { 135 enum val = getReportProperty!(__traits(getAttributes, __traits(getMember, I, T[0]))); 136 enum staticKey = val.name ~ ":" ~ val.ns; 137 138 if(staticKey == key) 139 result = true; 140 141 keyExist!(T[1..$])(); 142 } 143 } 144 145 keyExist!(__traits(allMembers, I))(); 146 147 return result; 148 } 149 150 void getDavReport(I)(I plugin, DavRequest request, DavResponse response) { 151 string key = request.content.reportName; 152 153 void getProp(T...)() { 154 static if(T.length > 0) { 155 enum val = getReportProperty!(__traits(getAttributes, __traits(getMember, I, T[0]))); 156 enum staticKey = val.name ~ ":" ~ val.ns; 157 158 159 if(staticKey == key) { 160 __traits(getMember, plugin, T[0])(request, response); 161 } 162 163 getProp!(T[1..$])(); 164 } 165 } 166 167 getProp!(__traits(allMembers, I))(); 168 } 169 170 interface IDavResourceAccess { 171 bool exists(URL url, string username); 172 Path[] childList(URL url, string username); 173 bool canCreateCollection(URL url, string username); 174 bool canCreateResource(URL url, string username); 175 176 void removeResource(URL url, string username); 177 DavResource getResource(URL url, string username); 178 DavResource createCollection(URL url, string username); 179 DavResource createResource(URL url, string username); 180 181 void bindResourcePlugins(DavResource resource); 182 } 183 184 interface IDavPlugin : IDavResourceAccess { 185 186 bool hasReport(URL url, string username, string name); 187 void report(DavRequest request, DavResponse response); 188 189 void notice(NoticeAction action, DavResource resource); 190 191 @property { 192 IDav dav(); 193 string name(); 194 195 string[] support(URL url, string username); 196 } 197 } 198 199 interface IDavPluginHub { 200 void registerPlugin(IDavPlugin plugin); 201 bool hasPlugin(string name); 202 } 203 204 205 abstract class BaseDavPlugin : IDavPlugin { 206 protected IDav _dav; 207 208 this(IDav dav) { 209 dav.registerPlugin(this); 210 _dav = dav; 211 } 212 213 Path[] childList(URL url, string username) { 214 return []; 215 } 216 217 bool exists(URL url, string username) { 218 return false; 219 } 220 221 bool canCreateCollection(URL url, string username) { 222 return false; 223 } 224 225 bool canCreateResource(URL url, string username) { 226 return false; 227 } 228 229 void removeResource(URL url, string username) { 230 throw new DavException(HTTPStatus.internalServerError, "Can't remove resource."); 231 } 232 233 DavResource getResource(URL url, string username) { 234 if(exists(url, username)) { 235 auto resource = new DavResource(_dav, url); 236 237 resource.name = url.path.head.to!string; 238 resource.username = username; 239 240 return resource; 241 } 242 243 throw new DavException(HTTPStatus.internalServerError, "Can't get resource."); 244 } 245 246 DavResource createCollection(URL url, string username) { 247 throw new DavException(HTTPStatus.internalServerError, "Can't create collection."); 248 } 249 250 DavResource createResource(URL url, string username) { 251 throw new DavException(HTTPStatus.internalServerError, "Can't create resource."); 252 } 253 254 void bindResourcePlugins(DavResource resource) { } 255 256 bool hasReport(URL url, string username, string name) { 257 return false; 258 } 259 260 void report(DavRequest request, DavResponse response) { 261 throw new DavException(HTTPStatus.internalServerError, "Can't get report."); 262 } 263 264 void notice(NoticeAction action, DavResource resource) { 265 266 } 267 268 @property { 269 IDav dav() { 270 return _dav; 271 } 272 273 string[] support(URL url, string username) { 274 return []; 275 } 276 } 277 } 278 279 interface IDav : IDavResourceAccess, IDavPluginHub { 280 void options(DavRequest request, DavResponse response); 281 void propfind(DavRequest request, DavResponse response); 282 void lock(DavRequest request, DavResponse response); 283 void get(DavRequest request, DavResponse response); 284 void put(DavRequest request, DavResponse response); 285 void proppatch(DavRequest request, DavResponse response); 286 void mkcol(DavRequest request, DavResponse response) ; 287 void remove(DavRequest request, DavResponse response); 288 void move(DavRequest request, DavResponse response); 289 void copy(DavRequest request, DavResponse response); 290 void report(DavRequest request, DavResponse response); 291 292 void notice(NoticeAction action, DavResource resource); 293 294 DavResource[] getResources(URL url, ulong depth, string username); 295 296 @property 297 Path rootUrl(); 298 Path path(URL); 299 } 300 301 /// The main DAV protocol implementation 302 class Dav : IDav { 303 protected { 304 Path _rootUrl; 305 306 IDavPlugin[] plugins; 307 } 308 309 @property 310 Path rootUrl() { 311 return _rootUrl; 312 } 313 314 Path path(URL url) { 315 auto root = Path("/") ~ rootUrl; 316 317 if(url.path.startsWith(root)) { 318 return url.path.relativeTo(root); 319 } 320 321 throw new DavException(HTTPStatus.internalServerError, "`" ~ url.path.toString ~ "` is not a valid DAV path."); 322 } 323 324 this(string rootUrl) { 325 _rootUrl = rootUrl; 326 _rootUrl.endsWithSlash = true; 327 DavStorage.locks = new DavLockList; 328 } 329 330 protected { 331 DavResource getOrCreateResource(URL url, string username, out HTTPStatus status) { 332 DavResource resource; 333 334 if(exists(url, username)) { 335 resource = getResource(url, username); 336 status = HTTPStatus.ok; 337 } else { 338 339 if(!exists(url.parentURL, username)) 340 throw new DavException(HTTPStatus.conflict, "A resource cannot be created at the destination until one or more intermediate collections have been created."); 341 342 resource = createResource(url, username); 343 status = HTTPStatus.created; 344 } 345 346 return resource; 347 } 348 349 Path checkPath(Path path) { 350 path.endsWithSlash = true; 351 return path; 352 } 353 } 354 355 private { 356 357 bool[string] defaultPropList() { 358 bool[string] list; 359 360 list["creationdate:DAV:"] = true; 361 list["displayname:DAV:"] = true; 362 list["getcontentlength:DAV:"] = true; 363 list["getcontenttype:DAV:"] = true; 364 list["getetag:DAV:"] = true; 365 list["getlastmodified:DAV:"] = true; 366 list["resourcetype:DAV:"] = true; 367 368 return list; 369 } 370 371 bool[string] propList(DavProp document) { 372 bool[string] list; 373 374 if(document is null || "allprop" in document["propfind"]) 375 return defaultPropList; 376 377 auto properties = document["propfind"]["prop"]; 378 379 if(properties.length > 0) 380 foreach(string key, p; properties) 381 list[p.tagName ~ ":" ~ p.namespace] = true; 382 383 return list; 384 } 385 } 386 387 void registerPlugin(IDavPlugin plugin) { 388 assert(plugin !is null, "can't set null plugin"); 389 assert(!hasPlugin(plugin.name), plugin.name ~ " already added."); 390 plugins ~= plugin; 391 } 392 393 bool hasPlugin(string name) { 394 395 foreach_reverse(plugin; plugins) 396 if(plugin.name == name) 397 return true; 398 399 return false; 400 } 401 402 void removeResource(URL url, string username) { 403 foreach_reverse(plugin; plugins) 404 if(plugin.exists(url, username)) { 405 notice(NoticeAction.deleted, getResource(url, username)); 406 return plugin.removeResource(url, username); 407 } 408 409 throw new DavException(HTTPStatus.notFound, "`" ~ url.toString ~ "` not found."); 410 } 411 412 DavResource getResource(URL url, string username) { 413 foreach_reverse(plugin; plugins) { 414 if(plugin.exists(url, username)) { 415 auto res = plugin.getResource(url, username); 416 bindResourcePlugins(res); 417 418 return res; 419 } 420 } 421 422 throw new DavException(HTTPStatus.notFound, "`" ~ url.toString ~ "` not found."); 423 } 424 425 DavResource[] getResources(URL url, ulong depth, string username) { 426 DavResource[] list; 427 DavResource[] tmpList; 428 429 tmpList ~= getResource(url, username); 430 431 while(tmpList.length > 0 && depth > 0) { 432 auto oldLen = tmpList.length; 433 434 foreach(resource; tmpList) { 435 if(resource.isCollection) { 436 auto childList = childList(resource.url, username); 437 438 foreach(path; childList) { 439 try { 440 tmpList ~= getResource(URL("http", "", 80, Path("/") ~_rootUrl ~ path), username); 441 } catch(DavException e) { 442 if(e.status == HTTPStatus.notFound) { 443 throw new DavException(HTTPStatus.internalServerError, 444 "Resource `" ~ url.to!string ~ "` said that it has `" ~ path.toString ~ "` child but it can not be found."); 445 } else { 446 throw e; 447 } 448 } 449 } 450 } 451 } 452 453 list ~= tmpList[0..oldLen]; 454 tmpList = tmpList[oldLen..$]; 455 depth--; 456 } 457 458 list ~= tmpList; 459 460 return list; 461 } 462 463 DavResource createCollection(URL url, string username) { 464 foreach_reverse(plugin; plugins) 465 if(plugin.canCreateCollection(url, username)) { 466 auto res = plugin.createCollection(url, username); 467 bindResourcePlugins(res); 468 469 notice(NoticeAction.created, res); 470 471 return res; 472 } 473 474 throw new DavException(HTTPStatus.methodNotAllowed, "No plugin available."); 475 } 476 477 DavResource createResource(URL url, string username) { 478 foreach_reverse(plugin; plugins) 479 if(plugin.canCreateResource(url, username)) { 480 auto res = plugin.createResource(url, username); 481 bindResourcePlugins(res); 482 483 notice(NoticeAction.created, res); 484 485 return res; 486 } 487 488 throw new DavException(HTTPStatus.methodNotAllowed, "No plugin available."); 489 } 490 491 void bindResourcePlugins(DavResource resource) { 492 foreach(plugin; plugins) { 493 plugin.bindResourcePlugins(resource); 494 } 495 } 496 497 bool exists(URL url, string username) { 498 foreach_reverse(plugin; plugins) { 499 if(plugin.exists(url, username)) 500 return true; 501 } 502 503 return false; 504 } 505 506 Path[] childList(URL url, string username) { 507 bool[Path] childs; 508 509 foreach_reverse(plugin; plugins) { 510 foreach(item; plugin.childList(url, username)) { 511 childs[item] = true; 512 } 513 } 514 515 return childs.keys; 516 } 517 518 bool canCreateCollection(URL url, string username) { 519 foreach_reverse(plugin; plugins) 520 if(plugin.canCreateCollection(url, username)) 521 return true; 522 523 return false; 524 } 525 526 bool canCreateResource(URL url, string username) { 527 foreach_reverse(plugin; plugins) 528 if(plugin.canCreateResource(url, username)) 529 return true; 530 531 return false; 532 } 533 534 void options(DavRequest request, DavResponse response) { 535 DavResource resource = getResource(request.url, request.username); 536 537 string path = request.path; 538 539 string[] support; 540 541 foreach_reverse(plugin; plugins) 542 support ~= plugin.support(request.url, request.username); 543 544 auto allow = "OPTIONS, GET, HEAD, DELETE, PROPFIND, PUT, PROPPATCH, COPY, MOVE, LOCK, UNLOCK, REPORT"; 545 546 response["Accept-Ranges"] = "bytes"; 547 response["DAV"] = uniq(support).join(","); 548 response["Allow"] = allow; 549 response["MS-Author-Via"] = "DAV"; 550 551 response.flush; 552 } 553 554 void propfind(DavRequest request, DavResponse response) { 555 bool[string] requestedProperties = propList(request.content); 556 DavResource[] list; 557 558 if(!exists(request.url, request.username)) 559 throw new DavException(HTTPStatus.notFound, "Resource does not exist."); 560 561 list = getResources(request.url, request.depth, request.username); 562 563 response.setPropContent(list, requestedProperties); 564 565 response.flush; 566 } 567 568 void proppatch(DavRequest request, DavResponse response) { 569 auto ifHeader = request.ifCondition; 570 response.statusCode = HTTPStatus.ok; 571 572 DavStorage.locks.check(request.url, ifHeader); 573 DavResource resource = getResource(request.url, request.username); 574 575 notice(NoticeAction.changed, resource); 576 577 auto xmlString = resource.propPatch(request.content); 578 579 response.content = xmlString; 580 response.flush; 581 } 582 583 void report(DavRequest request, DavResponse response) { 584 auto ifHeader = request.ifCondition; 585 586 string report = ""; 587 588 foreach_reverse(plugin; plugins) { 589 if(plugin.hasReport(request.url, request.username, request.content.reportName)) { 590 plugin.report(request, response); 591 return; 592 } 593 } 594 595 throw new DavException(HTTPStatus.notFound, "There is no report."); 596 } 597 598 void lock(DavRequest request, DavResponse response) { 599 auto ifHeader = request.ifCondition; 600 601 DavLockInfo currentLock; 602 603 auto resource = getOrCreateResource(request.url, request.username, response.statusCode); 604 605 if(request.contentLength != 0) { 606 currentLock = DavLockInfo.fromXML(request.content, resource); 607 608 if(currentLock.scopeLock == DavLockInfo.Scope.sharedLock && DavStorage.locks.hasExclusiveLock(resource.fullURL)) 609 throw new DavException(HTTPStatus.locked, "Already has an exclusive locked."); 610 else if(currentLock.scopeLock == DavLockInfo.Scope.exclusiveLock && DavStorage.locks.hasLock(resource.fullURL)) 611 throw new DavException(HTTPStatus.locked, "Already locked."); 612 else if(currentLock.scopeLock == DavLockInfo.Scope.exclusiveLock) 613 DavStorage.locks.check(request.url, ifHeader); 614 615 DavStorage.locks.add(currentLock); 616 } else if(request.contentLength == 0) { 617 string uuid = ifHeader.getAttr("", resource.href); 618 619 auto tmpUrl = resource.url; 620 while(currentLock is null) { 621 currentLock = DavStorage.locks[tmpUrl.toString, uuid]; 622 tmpUrl = tmpUrl.parentURL; 623 } 624 } else if(ifHeader.isEmpty) 625 throw new DavException(HTTPStatus.internalServerError, "LOCK body expected."); 626 627 if(currentLock is null) 628 throw new DavException(HTTPStatus.internalServerError, "LOCK not created."); 629 630 currentLock.timeout = request.timeout; 631 632 response["Lock-Token"] = "<" ~ currentLock.uuid ~ ">"; 633 response.mimeType = "application/xml"; 634 response.content = `<?xml version="1.0" encoding="utf-8" ?><d:prop xmlns:d="DAV:"><d:lockdiscovery> ` ~ currentLock.toString ~ `</d:lockdiscovery></d:prop>`; 635 response.flush; 636 } 637 638 void unlock(DavRequest request, DavResponse response) { 639 auto resource = getResource(request.url, request.username); 640 641 DavStorage.locks.remove(resource, request.lockToken); 642 643 response.statusCode = HTTPStatus.noContent; 644 response.flush; 645 } 646 647 void get(DavRequest request, DavResponse response) { 648 DavResource resource = getResource(request.url, request.username); 649 650 response["Etag"] = "\"" ~ resource.eTag ~ "\""; 651 response["Last-Modified"] = toRFC822DateTimeString(resource.lastModified); 652 response["Content-Type"] = resource.contentType; 653 response["Content-Length"] = resource.contentLength.to!string; 654 655 if(!request.ifModifiedSince(resource) || !request.ifNoneMatch(resource)) { 656 response.statusCode = HTTPStatus.NotModified; 657 response.flush; 658 return; 659 } 660 661 response.flush(resource); 662 DavStorage.locks.setETag(resource.url, resource.eTag); 663 } 664 665 void head(DavRequest request, DavResponse response) { 666 DavResource resource = getResource(request.url, request.username); 667 668 response["Etag"] = "\"" ~ resource.eTag ~ "\""; 669 response["Last-Modified"] = toRFC822DateTimeString(resource.lastModified); 670 response["Content-Type"] = resource.contentType; 671 response["Content-Length"] = resource.contentLength.to!string; 672 673 if(!request.ifModifiedSince(resource) || !request.ifNoneMatch(resource)) { 674 response.statusCode = HTTPStatus.NotModified; 675 response.flush; 676 return; 677 } 678 679 response.flush; 680 DavStorage.locks.setETag(resource.url, resource.eTag); 681 } 682 683 void put(DavRequest request, DavResponse response) { 684 DavResource resource = getOrCreateResource(request.url, request.username, response.statusCode); 685 DavStorage.locks.check(request.url, request.ifCondition); 686 687 resource.setContent(request.stream, request.contentLength); 688 notice(NoticeAction.changed, resource); 689 690 DavStorage.locks.setETag(resource.url, resource.eTag); 691 692 response.statusCode = HTTPStatus.created; 693 694 response.flush; 695 } 696 697 void mkcol(DavRequest request, DavResponse response) { 698 auto ifHeader = request.ifCondition; 699 700 if(request.contentLength > 0) 701 throw new DavException(HTTPStatus.unsupportedMediaType, "Body must be empty"); 702 703 if(!exists(request.url.parentURL, request.username)) 704 throw new DavException(HTTPStatus.conflict, "Missing parent"); 705 706 DavStorage.locks.check(request.url, ifHeader); 707 708 response.statusCode = HTTPStatus.created; 709 createCollection(request.url, request.username); 710 notice(NoticeAction.created, getResource(request.url, request.username)); 711 response.flush; 712 } 713 714 void remove(DavRequest request, DavResponse response) { 715 auto ifHeader = request.ifCondition; 716 auto url = request.url; 717 718 response.statusCode = HTTPStatus.noContent; 719 720 if(url.anchor != "" || request.requestUrl.indexOf("#") != -1) 721 throw new DavException(HTTPStatus.conflict, "Missing parent"); 722 723 if(!exists(url, request.username)) 724 throw new DavException(HTTPStatus.notFound, "Not found."); 725 726 DavStorage.locks.check(url, ifHeader); 727 728 removeResource(url, request.username); 729 730 response.flush; 731 } 732 733 void move(DavRequest request, DavResponse response) { 734 auto ifHeader = request.ifCondition; 735 auto resource = getResource(request.url, request.username); 736 737 DavStorage.locks.check(request.url, ifHeader); 738 DavStorage.locks.check(request.destination, ifHeader); 739 740 copy(request, response); 741 remove(request, response); 742 743 response.flush; 744 } 745 746 void copy(DavRequest request, DavResponse response) { 747 string username = request.username; 748 749 URL getDestinationUrl(DavResource source) const { 750 auto path = request.url.path; 751 auto sourcePath = source.url.path[path.length..$]; 752 auto destinationPath = request.destination.path ~ sourcePath; 753 754 auto url = source.url; 755 url.path = destinationPath; 756 757 return url; 758 } 759 760 void localCopy(DavResource source, DavResource destination) { 761 if(source.isCollection) { 762 auto list = getResources(request.url, DavDepth.infinity, username); 763 764 foreach(child; list) { 765 auto destinationUrl = getDestinationUrl(child); 766 767 if(child.isCollection && !exists(destinationUrl, username)) { 768 createCollection(getDestinationUrl(child), username); 769 } else if(!child.isCollection) { 770 HTTPStatus statusCode; 771 DavResource destinationChild = getOrCreateResource(getDestinationUrl(child), username, statusCode); 772 destinationChild.setContent(child.stream, child.contentLength); 773 } 774 } 775 } else { 776 destination.setContent(source.stream, source.contentLength); 777 } 778 779 source.copyPropertiesTo(destination.url); 780 } 781 782 DavResource source = getResource(request.url, username); 783 DavResource destination; 784 HTTPStatus destinationStatus; 785 786 DavStorage.locks.check(request.destination, request.ifCondition); 787 788 if(!exists(request.destination.parentURL, username)) 789 throw new DavException(HTTPStatus.conflict, "Conflict. `" ~ request.destination.parentURL.toString ~ "` does not exist."); 790 791 if(!request.overwrite && exists(request.destination, username)) 792 throw new DavException(HTTPStatus.preconditionFailed, "Destination already exists."); 793 794 if(exists(request.destination, username)) 795 destination = getResource(request.destination, username); 796 797 response.statusCode = HTTPStatus.created; 798 799 URL destinationUrl = request.destination; 800 801 if(destination !is null && destination.isCollection && !source.isCollection) { 802 destinationUrl.path = destinationUrl.path ~ source.url.path.head; 803 destination = null; 804 response.statusCode = HTTPStatus.noContent; 805 } 806 807 if(destination !is null && !destination.isCollection && source.isCollection) { 808 removeResource(destinationUrl, username); 809 } 810 811 if(destination is null) { 812 if(source.isCollection) 813 destination = createCollection(destinationUrl, username); 814 else 815 destination = createResource(destinationUrl, username); 816 } 817 818 localCopy(source, destination); 819 notice(NoticeAction.changed, destination); 820 821 response.flush; 822 } 823 824 void notice(NoticeAction action, DavResource resource) { 825 foreach_reverse(plugin; plugins) 826 plugin.notice(action, resource); 827 } 828 } 829 830 /// Hook vibe.d requests to the right DAV method 831 HTTPServerRequestDelegate serveDav(T : IDav)(T dav) { 832 void callback(HTTPServerRequest req, HTTPServerResponse res) 833 { 834 try { 835 debug { 836 writeln("\n\n\n"); 837 838 writeln("=========================================================="); 839 writeln(req.fullURL); 840 writeln("Method: ", req.method, "\n"); 841 842 foreach(key, val; req.headers) 843 writeln(key, ": ", val); 844 } 845 846 writeln(1); 847 DavRequest request = DavRequest(req); 848 DavResponse response = DavResponse(res); 849 850 writeln(2); 851 if(req.method == HTTPMethod.OPTIONS) { 852 dav.options(request, response); 853 } else if(req.method == HTTPMethod.PROPFIND) { 854 dav.propfind(request, response); 855 } else if(req.method == HTTPMethod.HEAD) { 856 dav.head(request, response); 857 } else if(req.method == HTTPMethod.GET) { 858 dav.get(request, response); 859 } else if(req.method == HTTPMethod.PUT) { 860 dav.put(request, response); 861 } else if(req.method == HTTPMethod.PROPPATCH) { 862 dav.proppatch(request, response); 863 } else if(req.method == HTTPMethod.LOCK) { 864 dav.lock(request, response); 865 } else if(req.method == HTTPMethod.UNLOCK) { 866 dav.unlock(request, response); 867 } else if(req.method == HTTPMethod.MKCOL) { 868 dav.mkcol(request, response); 869 } else if(req.method == HTTPMethod.DELETE) { 870 dav.remove(request, response); 871 } else if(req.method == HTTPMethod.COPY) { 872 dav.copy(request, response); 873 } else if(req.method == HTTPMethod.MOVE) { 874 dav.move(request, response); 875 } else if(req.method == HTTPMethod.REPORT) { 876 dav.report(request, response); 877 } else { 878 res.statusCode = HTTPStatus.notImplemented; 879 res.writeBody("", "text/plain"); 880 } 881 882 883 writeln(3); 884 } catch(DavException e) { 885 writeln(4); 886 writeln("ERROR:",e.status.to!int, "(", e.status, ") - ", e.msg); 887 888 res.statusCode = e.status; 889 res.writeBody(e.msg, e.mime); 890 } catch(Throwable t) { 891 892 writeln(5); 893 writeln("ERROR:",t); 894 res.statusCode = 500; 895 res.writeBody(t.to!string); 896 } 897 898 writeln(6); 899 debug { 900 writeln("\nSENT:", res.statusCode.to!int, "(", res.statusCode, ")"); 901 } 902 903 writeln(7); 904 } 905 906 return &callback; 907 }