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 }