comparison OrthancFramework/Sources/HttpServer/HttpServer.cpp @ 4228:c8c0bbaaace3

write access to webdav
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 06 Oct 2020 12:45:11 +0200
parents 7fff7e683d65
children 013d6c6b2387
comparison
equal deleted inserted replaced
4227:7fff7e683d65 4228:c8c0bbaaace3
43 # include <civetweb.h> 43 # include <civetweb.h>
44 # define MONGOOSE_USE_CALLBACKS 1 44 # define MONGOOSE_USE_CALLBACKS 1
45 # if !defined(CIVETWEB_HAS_DISABLE_KEEP_ALIVE) 45 # if !defined(CIVETWEB_HAS_DISABLE_KEEP_ALIVE)
46 # error Macro CIVETWEB_HAS_DISABLE_KEEP_ALIVE must be defined 46 # error Macro CIVETWEB_HAS_DISABLE_KEEP_ALIVE must be defined
47 # endif 47 # endif
48 48 # if !defined(CIVETWEB_HAS_WEBDAV_WRITING)
49 # error Macro CIVETWEB_HAS_WEBDAV_WRITING must be defined
50 # endif
49 #else 51 #else
50 # error "Either Mongoose or Civetweb must be enabled to compile this file" 52 # error "Either Mongoose or Civetweb must be enabled to compile this file"
51 #endif 53 #endif
52 54
53 #include <algorithm> 55 #include <algorithm>
341 343
342 return PostDataStatus_Success; 344 return PostDataStatus_Success;
343 } 345 }
344 346
345 347
348 static PostDataStatus ReadBodyWithoutContentLength(std::string& body,
349 struct mg_connection *connection)
350 {
351 // Store the individual chunks in a temporary file, then read it
352 // back into the memory buffer "body"
353 FileBuffer buffer;
354
355 std::string tmp(1024 * 1024, 0);
356
357 for (;;)
358 {
359 int r = mg_read(connection, &tmp[0], tmp.size());
360 if (r < 0)
361 {
362 return PostDataStatus_Failure;
363 }
364 else if (r == 0)
365 {
366 break;
367 }
368 else
369 {
370 buffer.Append(tmp.c_str(), r);
371 }
372 }
373
374 buffer.Read(body);
375
376 return PostDataStatus_Success;
377 }
378
379
346 static PostDataStatus ReadBodyToString(std::string& body, 380 static PostDataStatus ReadBodyToString(std::string& body,
347 struct mg_connection *connection, 381 struct mg_connection *connection,
348 const IHttpHandler::Arguments& headers) 382 const IHttpHandler::Arguments& headers)
349 { 383 {
350 IHttpHandler::Arguments::const_iterator contentLength = headers.find("content-length"); 384 IHttpHandler::Arguments::const_iterator contentLength = headers.find("content-length");
354 // "Content-Length" is available 388 // "Content-Length" is available
355 return ReadBodyWithContentLength(body, connection, contentLength->second); 389 return ReadBodyWithContentLength(body, connection, contentLength->second);
356 } 390 }
357 else 391 else
358 { 392 {
359 // No Content-Length. Store the individual chunks in a temporary 393 // No Content-Length
360 // file, then read it back into the memory buffer "body" 394 return ReadBodyWithoutContentLength(body, connection);
361 FileBuffer buffer;
362
363 std::string tmp(1024 * 1024, 0);
364
365 for (;;)
366 {
367 int r = mg_read(connection, &tmp[0], tmp.size());
368 if (r < 0)
369 {
370 return PostDataStatus_Failure;
371 }
372 else if (r == 0)
373 {
374 break;
375 }
376 else
377 {
378 buffer.Append(tmp.c_str(), r);
379 }
380 }
381
382 buffer.Read(body);
383
384 return PostDataStatus_Success;
385 } 395 }
386 } 396 }
387 397
388 398
389 static PostDataStatus ReadBodyToStream(IHttpHandler::IChunkedRequestReader& stream, 399 static PostDataStatus ReadBodyToStream(IHttpHandler::IChunkedRequestReader& stream,
694 } 704 }
695 } 705 }
696 } 706 }
697 707
698 708
699 bool HttpServer::HandleWebDav(HttpOutput& output, 709 #if ORTHANC_ENABLE_PUGIXML == 1
700 const std::string& method, 710
701 const IHttpHandler::Arguments& headers, 711 # if CIVETWEB_HAS_WEBDAV_WRITING == 0
702 const std::string& uri) 712 static void AnswerWebDavReadOnly(HttpOutput& output,
703 { 713 const std::string& uri)
714 {
715 LOG(ERROR) << "Orthanc was compiled without support for read-write access to WebDAV: " << uri;
716 output.SendStatus(HttpStatus_403_Forbidden);
717 }
718 # endif
719
720 static bool HandleWebDav(HttpOutput& output,
721 const HttpServer::WebDavBuckets& buckets,
722 const std::string& method,
723 const IHttpHandler::Arguments& headers,
724 const std::string& uri,
725 struct mg_connection *connection /* to read the PUT body if need be */)
726 {
727 if (buckets.empty())
728 {
729 return false; // Speed up things if WebDAV is not used
730 }
731
732 /**
733 * The "buckets" maps an URI relative to the root of the
734 * bucket, to the content of the bucket. The root URI does *not*
735 * contain a trailing slash.
736 **/
737
704 if (method == "OPTIONS") 738 if (method == "OPTIONS")
705 { 739 {
706 // Remove the trailing slash, if any (necessary for davfs2) 740 // Remove the trailing slash, if any (necessary for davfs2)
707 std::string s = uri; 741 std::string s = uri;
708 if (!s.empty() && 742 if (!s.empty() &&
709 s[s.size() - 1] == '/') 743 s[s.size() - 1] == '/')
710 { 744 {
711 s.resize(s.size() - 1); 745 s.resize(s.size() - 1);
712 } 746 }
713 747
714 WebDavBuckets::const_iterator bucket = webDavBuckets_.find(s); 748 HttpServer::WebDavBuckets::const_iterator bucket = buckets.find(s);
715 if (bucket == webDavBuckets_.end()) 749 if (bucket == buckets.end())
716 { 750 {
717 return false; 751 return false;
718 } 752 }
719 else 753 else
720 { 754 {
721 output.AddHeader("DAV", "1"); 755 output.AddHeader("DAV", "1,2"); // Necessary for Windows XP
722 output.AddHeader("Allow", "GET, POST, PUT, DELETE, PROPFIND"); 756
757 #if CIVETWEB_HAS_WEBDAV_WRITING == 1
758 output.AddHeader("Allow", "GET, PUT, OPTIONS, PROPFIND, HEAD, LOCK, UNLOCK, PROPPATCH, MKCOL");
759 #else
760 output.AddHeader("Allow", "GET, PUT, OPTIONS, PROPFIND, HEAD");
761 #endif
762
723 output.SendStatus(HttpStatus_200_Ok); 763 output.SendStatus(HttpStatus_200_Ok);
724 return true; 764 return true;
725 } 765 }
726 } 766 }
727 else if (method == "PROPFIND") 767 else if (method == "GET" ||
728 { 768 method == "PROPFIND" ||
729 IHttpHandler::Arguments::const_iterator i = headers.find("depth"); 769 method == "PROPPATCH" ||
730 if (i == headers.end()) 770 method == "PUT" ||
731 { 771 method == "HEAD" ||
732 throw OrthancException(ErrorCode_NetworkProtocol, "WebDAV PROPFIND without depth"); 772 method == "LOCK" ||
733 } 773 method == "UNLOCK" ||
734 774 method == "MKCOL")
735 int depth = boost::lexical_cast<int>(i->second); 775 {
736 if (depth != 0 && 776 // Locate the WebDAV bucket of interest, if any
737 depth != 1) 777 for (HttpServer::WebDavBuckets::const_iterator bucket = buckets.begin();
738 { 778 bucket != buckets.end(); ++bucket)
739 throw OrthancException(
740 ErrorCode_NetworkProtocol,
741 "WebDAV PROPFIND at unsupported depth (can only be 0 or 1): " + i->second);
742 }
743
744 for (WebDavBuckets::const_iterator bucket = webDavBuckets_.begin();
745 bucket != webDavBuckets_.end(); ++bucket)
746 { 779 {
747 assert(!bucket->first.empty() && 780 assert(!bucket->first.empty() &&
748 bucket->first[bucket->first.size() - 1] != '/' && 781 bucket->first[bucket->first.size() - 1] != '/' &&
749 bucket->second != NULL); 782 bucket->second != NULL);
750 783
758 } 791 }
759 792
760 std::vector<std::string> path; 793 std::vector<std::string> path;
761 Toolbox::SplitUriComponents(path, s); 794 Toolbox::SplitUriComponents(path, s);
762 795
763 std::string answer; 796
797 /**
798 * WebDAV - PROPFIND
799 **/
764 800
765 if (depth == 0) 801 if (method == "PROPFIND")
766 { 802 {
767 if (bucket->second->IsExistingFolder(path)) 803 IHttpHandler::Arguments::const_iterator i = headers.find("depth");
804 if (i == headers.end())
805 {
806 throw OrthancException(ErrorCode_NetworkProtocol, "WebDAV PROPFIND without depth");
807 }
808
809 int depth = boost::lexical_cast<int>(i->second);
810 if (depth != 0 &&
811 depth != 1)
812 {
813 throw OrthancException(
814 ErrorCode_NetworkProtocol,
815 "WebDAV PROPFIND at unsupported depth (can only be 0 or 1): " + i->second);
816 }
817
818 std::string answer;
819
820 if (depth == 0)
821 {
822 MimeType mime;
823 std::string content;
824 boost::posix_time::ptime modificationTime;
825
826 if (bucket->second->IsExistingFolder(path))
827 {
828 IWebDavBucket::Collection c;
829 c.AddResource(new IWebDavBucket::Folder(""));
830 c.Format(answer, uri);
831 }
832 else if (!path.empty() &&
833 bucket->second->GetFileContent(mime, content, modificationTime, path))
834 {
835 std::unique_ptr<IWebDavBucket::File> f(new IWebDavBucket::File(path.back()));
836 f->SetContentLength(content.size());
837 f->SetModificationTime(modificationTime);
838 f->SetMimeType(mime);
839
840 IWebDavBucket::Collection c;
841 c.AddResource(f.release());
842
843 std::vector<std::string> p;
844 Toolbox::SplitUriComponents(p, uri);
845 if (p.empty())
846 {
847 throw OrthancException(ErrorCode_InternalError);
848 }
849
850 p.resize(p.size() - 1);
851 c.Format(answer, Toolbox::FlattenUri(p));
852 }
853 else
854 {
855 output.SendStatus(HttpStatus_404_NotFound);
856 return true;
857 }
858 }
859 else if (depth == 1)
768 { 860 {
769 IWebDavBucket::Collection c; 861 IWebDavBucket::Collection c;
770 c.AddResource(new IWebDavBucket::Folder("")); 862 c.AddResource(new IWebDavBucket::Folder("")); // Necessary for empty folders
863
864 if (!bucket->second->ListCollection(c, path))
865 {
866 output.SendStatus(HttpStatus_404_NotFound);
867 return true;
868 }
869
771 c.Format(answer, uri); 870 c.Format(answer, uri);
772 } 871 }
773 else 872 else
774 { 873 {
775 output.SendStatus(HttpStatus_404_NotFound); 874 throw OrthancException(ErrorCode_InternalError);
776 return true;
777 } 875 }
876
877 output.AddHeader("Content-Type", "application/xml; charset=UTF-8");
878 output.SendStatus(HttpStatus_207_MultiStatus, answer);
879 return true;
778 } 880 }
779 else if (depth == 1) 881
882
883 /**
884 * WebDAV - GET and HEAD
885 **/
886
887 else if (method == "GET" ||
888 method == "HEAD")
780 { 889 {
781 IWebDavBucket::Collection c; 890 MimeType mime;
782 if (!bucket->second->ListCollection(c, path)) 891 std::string content;
892 boost::posix_time::ptime modificationTime;
893
894 if (bucket->second->GetFileContent(mime, content, modificationTime, path))
895 {
896 output.AddHeader("Content-Type", EnumerationToString(mime));
897
898 // "Last-Modified" is necessary on Windows XP. The "Z"
899 // suffix is mandatory on Windows >= 7.
900 output.AddHeader("Last-Modified", boost::posix_time::to_iso_extended_string(modificationTime) + "Z");
901
902 if (method == "GET")
903 {
904 output.Answer(content);
905 }
906 else
907 {
908 output.SendStatus(HttpStatus_200_Ok);
909 }
910 }
911 else
783 { 912 {
784 output.SendStatus(HttpStatus_404_NotFound); 913 output.SendStatus(HttpStatus_404_NotFound);
785 return true;
786 } 914 }
787 915
788 c.Format(answer, uri); 916 return true;
917 }
918
919
920 /**
921 * WebDAV - PUT
922 **/
923
924 else if (method == "PUT")
925 {
926 #if CIVETWEB_HAS_WEBDAV_WRITING == 1
927 std::string body;
928 if (ReadBodyToString(body, connection, headers) == PostDataStatus_Success)
929 {
930 if (bucket->second->StoreFile(body, path))
931 {
932 //output.SendStatus(HttpStatus_200_Ok);
933 output.SendStatus(HttpStatus_201_Created);
934 }
935 else
936 {
937 output.SendStatus(HttpStatus_403_Forbidden);
938 }
939 }
940 else
941 {
942 LOG(ERROR) << "Cannot read the content of a file to be stored in WebDAV";
943 output.SendStatus(HttpStatus_400_BadRequest);
944 }
945 #else
946 AnswerWebDavReadOnly(output, uri);
947 #endif
948
949 return true;
950 }
951
952
953 /**
954 * WebDAV - MKCOL
955 **/
956
957 else if (method == "MKCOL")
958 {
959 #if CIVETWEB_HAS_WEBDAV_WRITING == 1
960 if (bucket->second->CreateFolder(path))
961 {
962 //output.SendStatus(HttpStatus_200_Ok);
963 output.SendStatus(HttpStatus_201_Created);
964 }
965 else
966 {
967 output.SendStatus(HttpStatus_403_Forbidden);
968 }
969 #else
970 AnswerWebDavReadOnly(output, uri);
971 #endif
972
973 return true;
974 }
975
976
977 /**
978 * WebDAV - Faking PROPPATCH, LOCK and UNLOCK
979 **/
980
981 else if (method == "PROPPATCH")
982 {
983 #if CIVETWEB_HAS_WEBDAV_WRITING == 1
984 IWebDavBucket::AnswerFakedProppatch(output, uri);
985 #else
986 AnswerWebDavReadOnly(output, uri);
987 #endif
988 return true;
989 }
990 else if (method == "LOCK")
991 {
992 #if CIVETWEB_HAS_WEBDAV_WRITING == 1
993 IWebDavBucket::AnswerFakedLock(output, uri);
994 #else
995 AnswerWebDavReadOnly(output, uri);
996 #endif
997 return true;
998 }
999 else if (method == "UNLOCK")
1000 {
1001 #if CIVETWEB_HAS_WEBDAV_WRITING == 1
1002 IWebDavBucket::AnswerFakedUnlock(output);
1003 #else
1004 AnswerWebDavReadOnly(output, uri);
1005 #endif
1006 return true;
789 } 1007 }
790 else 1008 else
791 { 1009 {
792 throw OrthancException(ErrorCode_InternalError); 1010 throw OrthancException(ErrorCode_InternalError);
793 } 1011 }
794
795 output.AddHeader("DAV", "1");
796 output.AddHeader("Content-Type", "application/xml");
797 output.SendStatus(HttpStatus_207_MultiStatus, answer);
798 return true;
799 } 1012 }
800 } 1013 }
801 1014
802 return false; 1015 return false;
803 } 1016 }
804 else 1017 else
805 { 1018 {
1019 /**
1020 * WebDAV - Unapplicable method (such as POST and DELETE)
1021 **/
1022
806 return false; 1023 return false;
807 } 1024 }
808 } 1025 }
809 1026 #endif /* ORTHANC_ENABLE_PUGIXML == 1 */
810 1027
1028
811 static void InternalCallback(HttpOutput& output /* out */, 1029 static void InternalCallback(HttpOutput& output /* out */,
812 HttpMethod& method /* out */, 1030 HttpMethod& method /* out */,
813 HttpServer& server, 1031 HttpServer& server,
814 struct mg_connection *connection, 1032 struct mg_connection *connection,
815 const struct mg_request_info *request) 1033 const struct mg_request_info *request)
908 1126
909 1127
910 // Compute the HTTP method, taking method faking into consideration 1128 // Compute the HTTP method, taking method faking into consideration
911 method = HttpMethod_Get; 1129 method = HttpMethod_Get;
912 1130
1131 #if ORTHANC_ENABLE_PUGIXML == 1
913 bool isWebDav = false; 1132 bool isWebDav = false;
1133 #endif
1134
914 HttpMethod filterMethod; 1135 HttpMethod filterMethod;
1136
915 1137
916 if (ExtractMethod(method, request, headers, argumentsGET)) 1138 if (ExtractMethod(method, request, headers, argumentsGET))
917 { 1139 {
918 LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri); 1140 LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri);
919 filterMethod = method; 1141 filterMethod = method;
920 } 1142 }
1143 #if ORTHANC_ENABLE_PUGIXML == 1
921 else if (!strcmp(request->request_method, "OPTIONS") || 1144 else if (!strcmp(request->request_method, "OPTIONS") ||
922 !strcmp(request->request_method, "PROPFIND")) 1145 !strcmp(request->request_method, "PROPFIND") ||
923 { 1146 !strcmp(request->request_method, "HEAD"))
924 LOG(INFO) << "Incoming WebDAV request: " << request->request_method << " " << requestUri; 1147 {
1148 LOG(INFO) << "Incoming read-only WebDAV request: "
1149 << request->request_method << " " << requestUri;
925 filterMethod = HttpMethod_Get; 1150 filterMethod = HttpMethod_Get;
926 isWebDav = true; 1151 isWebDav = true;
927 } 1152 }
1153 else if (!strcmp(request->request_method, "PROPPATCH") ||
1154 !strcmp(request->request_method, "LOCK") ||
1155 !strcmp(request->request_method, "UNLOCK") ||
1156 !strcmp(request->request_method, "MKCOL"))
1157 {
1158 LOG(INFO) << "Incoming read-write WebDAV request: "
1159 << request->request_method << " " << requestUri;
1160 filterMethod = HttpMethod_Put;
1161 isWebDav = true;
1162 }
1163 #endif /* ORTHANC_ENABLE_PUGIXML == 1 */
928 else 1164 else
929 { 1165 {
930 LOG(INFO) << "Unknown HTTP method: " << request->request_method; 1166 LOG(INFO) << "Unknown HTTP method: " << request->request_method;
931 output.SendStatus(HttpStatus_400_BadRequest); 1167 output.SendStatus(HttpStatus_400_BadRequest);
932 return; 1168 return;
947 return; 1183 return;
948 } 1184 }
949 } 1185 }
950 1186
951 1187
952 if (server.HandleWebDav(output, request->request_method, headers, requestUri)) 1188 #if ORTHANC_ENABLE_PUGIXML == 1
1189 if (HandleWebDav(output, server.GetWebDavBuckets(), request->request_method,
1190 headers, requestUri, connection))
953 { 1191 {
954 return; 1192 return;
955 } 1193 }
956 else if (isWebDav) 1194 else if (isWebDav)
957 { 1195 {
958 LOG(INFO) << "No WebDAV bucket is registered against URI: " 1196 LOG(INFO) << "No WebDAV bucket is registered against URI: "
959 << request->request_method << " " << requestUri; 1197 << request->request_method << " " << requestUri;
960 output.SendStatus(HttpStatus_404_NotFound); 1198 output.SendStatus(HttpStatus_404_NotFound);
961 return; 1199 return;
962 } 1200 }
1201 #endif
963 1202
964 1203
965 bool found = false; 1204 bool found = false;
966 1205
967 // Extract the body of the request for PUT and POST, or process 1206 // Extract the body of the request for PUT and POST, or process
1379 throw OrthancException(ErrorCode_HttpPortInUse, 1618 throw OrthancException(ErrorCode_HttpPortInUse,
1380 " (port = " + boost::lexical_cast<std::string>(port_) + ")"); 1619 " (port = " + boost::lexical_cast<std::string>(port_) + ")");
1381 } 1620 }
1382 } 1621 }
1383 1622
1623 #if ORTHANC_ENABLE_PUGIXML == 1
1624 for (WebDavBuckets::iterator it = webDavBuckets_.begin(); it != webDavBuckets_.end(); ++it)
1625 {
1626 assert(it->second != NULL);
1627 it->second->Start();
1628 }
1629 #endif
1630
1384 LOG(WARNING) << "HTTP server listening on port: " << GetPortNumber() 1631 LOG(WARNING) << "HTTP server listening on port: " << GetPortNumber()
1385 << " (HTTPS encryption is " 1632 << " (HTTPS encryption is "
1386 << (IsSslEnabled() ? "enabled" : "disabled") 1633 << (IsSslEnabled() ? "enabled" : "disabled")
1387 << ", remote access is " 1634 << ", remote access is "
1388 << (IsRemoteAccessAllowed() ? "" : "not ") 1635 << (IsRemoteAccessAllowed() ? "" : "not ")
1393 void HttpServer::Stop() 1640 void HttpServer::Stop()
1394 { 1641 {
1395 if (IsRunning()) 1642 if (IsRunning())
1396 { 1643 {
1397 mg_stop(pimpl_->context_); 1644 mg_stop(pimpl_->context_);
1645
1646 #if ORTHANC_ENABLE_PUGIXML == 1
1647 for (WebDavBuckets::iterator it = webDavBuckets_.begin(); it != webDavBuckets_.end(); ++it)
1648 {
1649 assert(it->second != NULL);
1650 it->second->Stop();
1651 }
1652 #endif
1653
1398 pimpl_->context_ = NULL; 1654 pimpl_->context_ = NULL;
1399 } 1655 }
1400 } 1656 }
1401 1657
1402 1658