Mercurial > hg > orthanc
comparison OrthancFramework/Sources/HttpServer/HttpServer.cpp @ 4227:7fff7e683d65
basic implementation of WebDAV handler
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 05 Oct 2020 10:55:24 +0200 |
parents | 7bd5eab3ba25 |
children | c8c0bbaaace3 |
comparison
equal
deleted
inserted
replaced
4226:7bd5eab3ba25 | 4227:7fff7e683d65 |
---|---|
49 #else | 49 #else |
50 # error "Either Mongoose or Civetweb must be enabled to compile this file" | 50 # error "Either Mongoose or Civetweb must be enabled to compile this file" |
51 #endif | 51 #endif |
52 | 52 |
53 #include <algorithm> | 53 #include <algorithm> |
54 #include <boost/algorithm/string.hpp> | |
55 #include <boost/algorithm/string/predicate.hpp> | |
56 #include <boost/filesystem.hpp> | |
57 #include <boost/lexical_cast.hpp> | |
58 #include <boost/thread.hpp> | |
59 #include <iostream> | |
60 #include <stdio.h> | |
54 #include <string.h> | 61 #include <string.h> |
55 #include <boost/lexical_cast.hpp> | |
56 #include <boost/algorithm/string.hpp> | |
57 #include <boost/filesystem.hpp> | |
58 #include <iostream> | |
59 #include <string.h> | |
60 #include <stdio.h> | |
61 #include <boost/thread.hpp> | |
62 | 62 |
63 #if !defined(ORTHANC_ENABLE_SSL) | 63 #if !defined(ORTHANC_ENABLE_SSL) |
64 # error The macro ORTHANC_ENABLE_SSL must be defined | 64 # error The macro ORTHANC_ENABLE_SSL must be defined |
65 #endif | 65 #endif |
66 | 66 |
694 } | 694 } |
695 } | 695 } |
696 } | 696 } |
697 | 697 |
698 | 698 |
699 bool HttpServer::HandleWebDav(HttpOutput& output, | |
700 const std::string& method, | |
701 const IHttpHandler::Arguments& headers, | |
702 const std::string& uri) | |
703 { | |
704 if (method == "OPTIONS") | |
705 { | |
706 // Remove the trailing slash, if any (necessary for davfs2) | |
707 std::string s = uri; | |
708 if (!s.empty() && | |
709 s[s.size() - 1] == '/') | |
710 { | |
711 s.resize(s.size() - 1); | |
712 } | |
713 | |
714 WebDavBuckets::const_iterator bucket = webDavBuckets_.find(s); | |
715 if (bucket == webDavBuckets_.end()) | |
716 { | |
717 return false; | |
718 } | |
719 else | |
720 { | |
721 output.AddHeader("DAV", "1"); | |
722 output.AddHeader("Allow", "GET, POST, PUT, DELETE, PROPFIND"); | |
723 output.SendStatus(HttpStatus_200_Ok); | |
724 return true; | |
725 } | |
726 } | |
727 else if (method == "PROPFIND") | |
728 { | |
729 IHttpHandler::Arguments::const_iterator i = headers.find("depth"); | |
730 if (i == headers.end()) | |
731 { | |
732 throw OrthancException(ErrorCode_NetworkProtocol, "WebDAV PROPFIND without depth"); | |
733 } | |
734 | |
735 int depth = boost::lexical_cast<int>(i->second); | |
736 if (depth != 0 && | |
737 depth != 1) | |
738 { | |
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 { | |
747 assert(!bucket->first.empty() && | |
748 bucket->first[bucket->first.size() - 1] != '/' && | |
749 bucket->second != NULL); | |
750 | |
751 if (uri == bucket->first || | |
752 boost::starts_with(uri, bucket->first + "/")) | |
753 { | |
754 std::string s = uri.substr(bucket->first.size()); | |
755 if (s.empty()) | |
756 { | |
757 s = "/"; | |
758 } | |
759 | |
760 std::vector<std::string> path; | |
761 Toolbox::SplitUriComponents(path, s); | |
762 | |
763 std::string answer; | |
764 | |
765 if (depth == 0) | |
766 { | |
767 if (bucket->second->IsExistingFolder(path)) | |
768 { | |
769 IWebDavBucket::Collection c; | |
770 c.AddResource(new IWebDavBucket::Folder("")); | |
771 c.Format(answer, uri); | |
772 } | |
773 else | |
774 { | |
775 output.SendStatus(HttpStatus_404_NotFound); | |
776 return true; | |
777 } | |
778 } | |
779 else if (depth == 1) | |
780 { | |
781 IWebDavBucket::Collection c; | |
782 if (!bucket->second->ListCollection(c, path)) | |
783 { | |
784 output.SendStatus(HttpStatus_404_NotFound); | |
785 return true; | |
786 } | |
787 | |
788 c.Format(answer, uri); | |
789 } | |
790 else | |
791 { | |
792 throw OrthancException(ErrorCode_InternalError); | |
793 } | |
794 | |
795 output.AddHeader("DAV", "1"); | |
796 output.AddHeader("Content-Type", "application/xml"); | |
797 output.SendStatus(HttpStatus_207_MultiStatus, answer); | |
798 return true; | |
799 } | |
800 } | |
801 | |
802 return false; | |
803 } | |
804 else | |
805 { | |
806 return false; | |
807 } | |
808 } | |
809 | |
810 | |
699 static void InternalCallback(HttpOutput& output /* out */, | 811 static void InternalCallback(HttpOutput& output /* out */, |
700 HttpMethod& method /* out */, | 812 HttpMethod& method /* out */, |
701 HttpServer& server, | 813 HttpServer& server, |
702 struct mg_connection *connection, | 814 struct mg_connection *connection, |
703 const struct mg_request_info *request) | 815 const struct mg_request_info *request) |
748 { | 860 { |
749 HttpToolbox::ParseGetArguments(argumentsGET, request->query_string); | 861 HttpToolbox::ParseGetArguments(argumentsGET, request->query_string); |
750 } | 862 } |
751 | 863 |
752 | 864 |
753 // Compute the HTTP method, taking method faking into consideration | |
754 method = HttpMethod_Get; | |
755 if (!ExtractMethod(method, request, headers, argumentsGET)) | |
756 { | |
757 output.SendStatus(HttpStatus_400_BadRequest); | |
758 return; | |
759 } | |
760 | |
761 | |
762 // Authenticate this connection | 865 // Authenticate this connection |
763 if (server.IsAuthenticationEnabled() && | 866 if (server.IsAuthenticationEnabled() && |
764 !IsAccessGranted(server, headers)) | 867 !IsAccessGranted(server, headers)) |
765 { | 868 { |
766 output.SendUnauthorized(server.GetRealm()); | 869 output.SendUnauthorized(server.GetRealm()); |
789 if (requestUri == NULL) | 892 if (requestUri == NULL) |
790 { | 893 { |
791 requestUri = ""; | 894 requestUri = ""; |
792 } | 895 } |
793 | 896 |
794 std::string username = GetAuthenticatedUsername(headers); | 897 // Decompose the URI into its components |
898 UriComponents uri; | |
899 try | |
900 { | |
901 Toolbox::SplitUriComponents(uri, requestUri); | |
902 } | |
903 catch (OrthancException&) | |
904 { | |
905 output.SendStatus(HttpStatus_400_BadRequest); | |
906 return; | |
907 } | |
908 | |
909 | |
910 // Compute the HTTP method, taking method faking into consideration | |
911 method = HttpMethod_Get; | |
912 | |
913 bool isWebDav = false; | |
914 HttpMethod filterMethod; | |
915 | |
916 if (ExtractMethod(method, request, headers, argumentsGET)) | |
917 { | |
918 LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri); | |
919 filterMethod = method; | |
920 } | |
921 else if (!strcmp(request->request_method, "OPTIONS") || | |
922 !strcmp(request->request_method, "PROPFIND")) | |
923 { | |
924 LOG(INFO) << "Incoming WebDAV request: " << request->request_method << " " << requestUri; | |
925 filterMethod = HttpMethod_Get; | |
926 isWebDav = true; | |
927 } | |
928 else | |
929 { | |
930 LOG(INFO) << "Unknown HTTP method: " << request->request_method; | |
931 output.SendStatus(HttpStatus_400_BadRequest); | |
932 return; | |
933 } | |
934 | |
935 | |
936 // Check that this connection is allowed by the user's authentication filter | |
937 const std::string username = GetAuthenticatedUsername(headers); | |
795 | 938 |
796 IIncomingHttpRequestFilter *filter = server.GetIncomingHttpRequestFilter(); | 939 IIncomingHttpRequestFilter *filter = server.GetIncomingHttpRequestFilter(); |
797 if (filter != NULL) | 940 if (filter != NULL) |
798 { | 941 { |
799 if (!filter->IsAllowed(method, requestUri, remoteIp, | 942 if (!filter->IsAllowed(filterMethod, requestUri, remoteIp, |
800 username.c_str(), headers, argumentsGET)) | 943 username.c_str(), headers, argumentsGET)) |
801 { | 944 { |
802 //output.SendUnauthorized(server.GetRealm()); | 945 //output.SendUnauthorized(server.GetRealm()); |
803 output.SendStatus(HttpStatus_403_Forbidden); | 946 output.SendStatus(HttpStatus_403_Forbidden); |
804 return; | 947 return; |
805 } | 948 } |
806 } | 949 } |
807 | 950 |
808 | 951 |
809 // Decompose the URI into its components | 952 if (server.HandleWebDav(output, request->request_method, headers, requestUri)) |
810 UriComponents uri; | 953 { |
811 try | |
812 { | |
813 Toolbox::SplitUriComponents(uri, requestUri); | |
814 } | |
815 catch (OrthancException&) | |
816 { | |
817 output.SendStatus(HttpStatus_400_BadRequest); | |
818 return; | 954 return; |
819 } | 955 } |
820 | 956 else if (isWebDav) |
821 LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri); | 957 { |
958 LOG(INFO) << "No WebDAV bucket is registered against URI: " | |
959 << request->request_method << " " << requestUri; | |
960 output.SendStatus(HttpStatus_404_NotFound); | |
961 return; | |
962 } | |
822 | 963 |
823 | 964 |
824 bool found = false; | 965 bool found = false; |
825 | 966 |
826 // Extract the body of the request for PUT and POST, or process | 967 // Extract the body of the request for PUT and POST, or process |
1443 if (bucket == NULL) | 1584 if (bucket == NULL) |
1444 { | 1585 { |
1445 throw OrthancException(ErrorCode_NullPointer); | 1586 throw OrthancException(ErrorCode_NullPointer); |
1446 } | 1587 } |
1447 | 1588 |
1448 std::string s = "/"; | 1589 const std::string s = Toolbox::FlattenUri(root); |
1449 for (size_t i = 0; i < root.size(); i++) | |
1450 { | |
1451 if (root[i].empty()) | |
1452 { | |
1453 throw OrthancException(ErrorCode_ParameterOutOfRange, "An URI component cannot be empty"); | |
1454 } | |
1455 | |
1456 s += root[i]; | |
1457 } | |
1458 | 1590 |
1459 if (webDavBuckets_.find(s) != webDavBuckets_.end()) | 1591 if (webDavBuckets_.find(s) != webDavBuckets_.end()) |
1460 { | 1592 { |
1461 throw OrthancException(ErrorCode_ParameterOutOfRange, | 1593 throw OrthancException(ErrorCode_ParameterOutOfRange, |
1462 "Cannot register two WebDAV buckets at the same root: " + s); | 1594 "Cannot register two WebDAV buckets at the same root: " + s); |
1463 } | 1595 } |
1464 else | 1596 else |
1465 { | 1597 { |
1598 LOG(INFO) << "Branching WebDAV bucket at: " << s; | |
1466 webDavBuckets_[s] = protection.release(); | 1599 webDavBuckets_[s] = protection.release(); |
1467 } | 1600 } |
1468 } | 1601 } |
1469 #endif | 1602 #endif |
1470 } | 1603 } |