1 /** 2 * Orthanc - A Lightweight, RESTful DICOM Store 3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics 4 * Department, University Hospital of Liege, Belgium 5 * Copyright (C) 2017-2021 Osimis S.A., Belgium 6 * 7 * This program is free software: you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public License 9 * as published by the Free Software Foundation, either version 3 of 10 * the License, or (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this program. If not, see 19 * <http://www.gnu.org/licenses/>. 20 **/ 21 22 23 // http://en.highscore.de/cpp/boost/stringhandling.html 24 25 #include "../PrecompiledHeaders.h" 26 #include "HttpServer.h" 27 28 #include "../ChunkedBuffer.h" 29 #include "../FileBuffer.h" 30 #include "../Logging.h" 31 #include "../OrthancException.h" 32 #include "../TemporaryFile.h" 33 #include "HttpToolbox.h" 34 #include "IHttpHandler.h" 35 #include "MultipartStreamReader.h" 36 #include "StringHttpOutput.h" 37 38 #if ORTHANC_ENABLE_PUGIXML == 1 39 # include "IWebDavBucket.h" 40 #endif 41 42 #if ORTHANC_ENABLE_MONGOOSE == 1 43 # include <mongoose.h> 44 45 #elif ORTHANC_ENABLE_CIVETWEB == 1 46 # include <civetweb.h> 47 # define MONGOOSE_USE_CALLBACKS 1 48 # if !defined(CIVETWEB_HAS_DISABLE_KEEP_ALIVE) 49 # error Macro CIVETWEB_HAS_DISABLE_KEEP_ALIVE must be defined 50 # endif 51 # if !defined(CIVETWEB_HAS_WEBDAV_WRITING) 52 # error Macro CIVETWEB_HAS_WEBDAV_WRITING must be defined 53 # endif 54 #else 55 # error "Either Mongoose or Civetweb must be enabled to compile this file" 56 #endif 57 58 #include <algorithm> 59 #include <boost/algorithm/string.hpp> 60 #include <boost/algorithm/string/predicate.hpp> 61 #include <boost/filesystem.hpp> 62 #include <boost/lexical_cast.hpp> 63 #include <boost/thread.hpp> 64 #include <iostream> 65 #include <stdio.h> 66 #include <string.h> 67 68 #if !defined(ORTHANC_ENABLE_SSL) 69 # error The macro ORTHANC_ENABLE_SSL must be defined 70 #endif 71 72 #if ORTHANC_ENABLE_SSL == 1 73 # include <openssl/opensslv.h> 74 # include <openssl/err.h> 75 #endif 76 77 #define ORTHANC_REALM "Orthanc Secure Area" 78 79 80 namespace Orthanc 81 { 82 namespace 83 { 84 // Anonymous namespace to avoid clashes between compilation modules 85 class MongooseOutputStream : public IHttpOutputStream 86 { 87 private: 88 struct mg_connection* connection_; 89 90 public: MongooseOutputStream(struct mg_connection * connection)91 explicit MongooseOutputStream(struct mg_connection* connection) : 92 connection_(connection) 93 { 94 } 95 Send(bool isHeader,const void * buffer,size_t length)96 virtual void Send(bool isHeader, 97 const void* buffer, 98 size_t length) ORTHANC_OVERRIDE 99 { 100 if (length > 0) 101 { 102 int status = mg_write(connection_, buffer, length); 103 if (status != static_cast<int>(length)) 104 { 105 // status == 0 when the connection has been closed, -1 on error 106 throw OrthancException(ErrorCode_NetworkProtocol); 107 } 108 } 109 } 110 OnHttpStatusReceived(HttpStatus status)111 virtual void OnHttpStatusReceived(HttpStatus status) ORTHANC_OVERRIDE 112 { 113 // Ignore this 114 } 115 DisableKeepAlive()116 virtual void DisableKeepAlive() ORTHANC_OVERRIDE 117 { 118 #if ORTHANC_ENABLE_MONGOOSE == 1 119 throw OrthancException(ErrorCode_NotImplemented, 120 "Only available if using CivetWeb"); 121 122 #elif ORTHANC_ENABLE_CIVETWEB == 1 123 # if CIVETWEB_HAS_DISABLE_KEEP_ALIVE == 1 124 # if CIVETWEB_VERSION_MAJOR == 1 && CIVETWEB_VERSION_MINOR <= 13 // From "civetweb-1.13.patch" 125 mg_disable_keep_alive(connection_); 126 # else 127 /** 128 * Function "mg_disable_keep_alive()" contributed by Sebastien 129 * Jodogne was renamed as "mg_disable_connection_keep_alive()" 130 * in the official CivetWeb repository: 131 * https://github.com/civetweb/civetweb/commit/78d45f4c4b0ab821f4f259b21ad3783f6d6c556a 132 **/ 133 mg_disable_connection_keep_alive(connection_); 134 # endif 135 # else 136 # if defined(__GNUC__) || defined(__clang__) 137 # warning The function "mg_disable_keep_alive()" is not available, DICOMweb might run slowly 138 # endif 139 throw OrthancException(ErrorCode_NotImplemented, 140 "Only available if using a patched version of CivetWeb"); 141 # endif 142 143 #else 144 # error Please support your embedded Web server here 145 #endif 146 } 147 }; 148 149 150 enum PostDataStatus 151 { 152 PostDataStatus_Success, 153 PostDataStatus_NoLength, 154 PostDataStatus_Pending, 155 PostDataStatus_Failure 156 }; 157 } 158 159 160 namespace 161 { 162 class ChunkedFile : public ChunkedBuffer 163 { 164 private: 165 std::string filename_; 166 167 public: ChunkedFile(const std::string & filename)168 explicit ChunkedFile(const std::string& filename) : 169 filename_(filename) 170 { 171 } 172 GetFilename() const173 const std::string& GetFilename() const 174 { 175 return filename_; 176 } 177 }; 178 } 179 180 181 182 class HttpServer::ChunkStore : public boost::noncopyable 183 { 184 private: 185 typedef std::list<ChunkedFile*> Content; 186 Content content_; 187 unsigned int numPlaces_; 188 189 boost::mutex mutex_; 190 std::set<std::string> discardedFiles_; 191 Clear()192 void Clear() 193 { 194 for (Content::iterator it = content_.begin(); 195 it != content_.end(); ++it) 196 { 197 delete *it; 198 } 199 } 200 Find(const std::string & filename)201 Content::iterator Find(const std::string& filename) 202 { 203 for (Content::iterator it = content_.begin(); 204 it != content_.end(); ++it) 205 { 206 if ((*it)->GetFilename() == filename) 207 { 208 return it; 209 } 210 } 211 212 return content_.end(); 213 } 214 Remove(const std::string & filename)215 void Remove(const std::string& filename) 216 { 217 Content::iterator it = Find(filename); 218 if (it != content_.end()) 219 { 220 delete *it; 221 content_.erase(it); 222 } 223 } 224 225 public: ChunkStore()226 ChunkStore() 227 { 228 numPlaces_ = 10; 229 } 230 ~ChunkStore()231 ~ChunkStore() 232 { 233 Clear(); 234 } 235 Store(std::string & completed,const void * chunkData,size_t chunkSize,const std::string & filename,size_t filesize)236 PostDataStatus Store(std::string& completed, 237 const void* chunkData, 238 size_t chunkSize, 239 const std::string& filename, 240 size_t filesize) 241 { 242 boost::mutex::scoped_lock lock(mutex_); 243 244 std::set<std::string>::iterator wasDiscarded = discardedFiles_.find(filename); 245 if (wasDiscarded != discardedFiles_.end()) 246 { 247 discardedFiles_.erase(wasDiscarded); 248 return PostDataStatus_Failure; 249 } 250 251 ChunkedFile* f; 252 Content::iterator it = Find(filename); 253 if (it == content_.end()) 254 { 255 f = new ChunkedFile(filename); 256 257 // Make some room 258 if (content_.size() >= numPlaces_) 259 { 260 discardedFiles_.insert(content_.front()->GetFilename()); 261 delete content_.front(); 262 content_.pop_front(); 263 } 264 265 content_.push_back(f); 266 } 267 else 268 { 269 f = *it; 270 } 271 272 f->AddChunk(chunkData, chunkSize); 273 274 if (f->GetNumBytes() > filesize) 275 { 276 Remove(filename); 277 } 278 else if (f->GetNumBytes() == filesize) 279 { 280 f->Flatten(completed); 281 Remove(filename); 282 return PostDataStatus_Success; 283 } 284 285 return PostDataStatus_Pending; 286 } 287 288 /*void Print() 289 { 290 boost::mutex::scoped_lock lock(mutex_); 291 292 printf("ChunkStore status:\n"); 293 for (Content::const_iterator i = content_.begin(); 294 i != content_.end(); i++) 295 { 296 printf(" [%s]: %d\n", (*i)->GetFilename().c_str(), (*i)->GetNumBytes()); 297 } 298 printf("-----\n"); 299 }*/ 300 }; 301 302 303 struct HttpServer::PImpl 304 { 305 struct mg_context *context_; 306 ChunkStore chunkStore_; 307 PImplOrthanc::HttpServer::PImpl308 PImpl() : 309 context_(NULL) 310 { 311 } 312 }; 313 314 315 class HttpServer::MultipartFormDataHandler : public MultipartStreamReader::IHandler 316 { 317 private: 318 IHttpHandler& handler_; 319 ChunkStore& chunkStore_; 320 const std::string& remoteIp_; 321 const std::string& username_; 322 const UriComponents& uri_; 323 bool isJQueryUploadChunk_; 324 std::string jqueryUploadFileName_; 325 size_t jqueryUploadFileSize_; 326 HandleInternal(const MultipartStreamReader::HttpHeaders & headers,const void * part,size_t size)327 void HandleInternal(const MultipartStreamReader::HttpHeaders& headers, 328 const void* part, 329 size_t size) 330 { 331 StringHttpOutput stringOutput; 332 HttpOutput fakeOutput(stringOutput, false); 333 HttpToolbox::GetArguments getArguments; 334 335 if (!handler_.Handle(fakeOutput, RequestOrigin_RestApi, remoteIp_.c_str(), username_.c_str(), 336 HttpMethod_Post, uri_, headers, getArguments, part, size)) 337 { 338 throw OrthancException(ErrorCode_UnknownResource); 339 } 340 } 341 342 public: MultipartFormDataHandler(IHttpHandler & handler,ChunkStore & chunkStore,const std::string & remoteIp,const std::string & username,const UriComponents & uri,const MultipartStreamReader::HttpHeaders & headers)343 MultipartFormDataHandler(IHttpHandler& handler, 344 ChunkStore& chunkStore, 345 const std::string& remoteIp, 346 const std::string& username, 347 const UriComponents& uri, 348 const MultipartStreamReader::HttpHeaders& headers) : 349 handler_(handler), 350 chunkStore_(chunkStore), 351 remoteIp_(remoteIp), 352 username_(username), 353 uri_(uri), 354 isJQueryUploadChunk_(false), 355 jqueryUploadFileSize_(0) // Dummy initialization 356 { 357 typedef HttpToolbox::Arguments::const_iterator Iterator; 358 359 Iterator requestedWith = headers.find("x-requested-with"); 360 if (requestedWith != headers.end() && 361 requestedWith->second != "XMLHttpRequest") 362 { 363 throw OrthancException(ErrorCode_NetworkProtocol, "HTTP header \"X-Requested-With\" should be " 364 "\"XMLHttpRequest\" in multipart uploads"); 365 } 366 367 Iterator fileName = headers.find("x-file-name"); 368 Iterator fileSize = headers.find("x-file-size"); 369 if (fileName != headers.end() || 370 fileSize != headers.end()) 371 { 372 if (fileName == headers.end()) 373 { 374 throw OrthancException(ErrorCode_NetworkProtocol, "HTTP header \"X-File-Name\" is missing"); 375 } 376 377 if (fileSize == headers.end()) 378 { 379 throw OrthancException(ErrorCode_NetworkProtocol, "HTTP header \"X-File-Size\" is missing"); 380 } 381 382 isJQueryUploadChunk_ = true; 383 jqueryUploadFileName_ = fileName->second; 384 385 try 386 { 387 int64_t s = boost::lexical_cast<int64_t>(fileSize->second); 388 if (s < 0) 389 { 390 throw OrthancException(ErrorCode_NetworkProtocol, "HTTP header \"X-File-Size\" has negative value"); 391 } 392 else 393 { 394 jqueryUploadFileSize_ = static_cast<size_t>(s); 395 if (static_cast<int64_t>(jqueryUploadFileSize_) != s) 396 { 397 throw OrthancException(ErrorCode_NotEnoughMemory); 398 } 399 } 400 } 401 catch (boost::bad_lexical_cast& e) 402 { 403 throw OrthancException(ErrorCode_NetworkProtocol, "HTTP header \"X-File-Size\" is not an integer"); 404 } 405 } 406 } 407 HandlePart(const MultipartStreamReader::HttpHeaders & headers,const void * part,size_t size)408 virtual void HandlePart(const MultipartStreamReader::HttpHeaders& headers, 409 const void* part, 410 size_t size) ORTHANC_OVERRIDE 411 { 412 if (isJQueryUploadChunk_) 413 { 414 std::string completedFile; 415 416 PostDataStatus status = chunkStore_.Store(completedFile, part, size, jqueryUploadFileName_, jqueryUploadFileSize_); 417 418 switch (status) 419 { 420 case PostDataStatus_Failure: 421 throw OrthancException(ErrorCode_NetworkProtocol, "Error in the multipart form upload"); 422 423 case PostDataStatus_Success: 424 assert(completedFile.size() == jqueryUploadFileSize_); 425 HandleInternal(headers, completedFile.empty() ? NULL : completedFile.c_str(), completedFile.size()); 426 break; 427 428 case PostDataStatus_Pending: 429 break; 430 431 default: 432 throw OrthancException(ErrorCode_InternalError); 433 } 434 } 435 else 436 { 437 HandleInternal(headers, part, size); 438 } 439 } 440 }; 441 442 ProcessMultipartFormData(const std::string & remoteIp,const std::string & username,const UriComponents & uri,const std::map<std::string,std::string> & headers,const std::string & body,const std::string & boundary)443 void HttpServer::ProcessMultipartFormData(const std::string& remoteIp, 444 const std::string& username, 445 const UriComponents& uri, 446 const std::map<std::string, std::string>& headers, 447 const std::string& body, 448 const std::string& boundary) 449 { 450 MultipartFormDataHandler handler(GetHandler(), pimpl_->chunkStore_, remoteIp, username, uri, headers); 451 452 MultipartStreamReader reader(boundary); 453 reader.SetHandler(handler); 454 reader.AddChunk(body); 455 reader.CloseStream(); 456 } 457 458 ReadBodyWithContentLength(std::string & body,struct mg_connection * connection,const std::string & contentLength)459 static PostDataStatus ReadBodyWithContentLength(std::string& body, 460 struct mg_connection *connection, 461 const std::string& contentLength) 462 { 463 size_t length; 464 try 465 { 466 int64_t tmp = boost::lexical_cast<int64_t>(contentLength); 467 if (tmp < 0) 468 { 469 return PostDataStatus_NoLength; 470 } 471 472 length = static_cast<size_t>(tmp); 473 } 474 catch (boost::bad_lexical_cast&) 475 { 476 return PostDataStatus_NoLength; 477 } 478 479 body.resize(length); 480 481 size_t pos = 0; 482 while (length > 0) 483 { 484 int r = mg_read(connection, &body[pos], length); 485 if (r <= 0) 486 { 487 return PostDataStatus_Failure; 488 } 489 490 assert(static_cast<size_t>(r) <= length); 491 length -= r; 492 pos += r; 493 } 494 495 return PostDataStatus_Success; 496 } 497 498 ReadBodyWithoutContentLength(std::string & body,struct mg_connection * connection)499 static PostDataStatus ReadBodyWithoutContentLength(std::string& body, 500 struct mg_connection *connection) 501 { 502 // Store the individual chunks in a temporary file, then read it 503 // back into the memory buffer "body" 504 FileBuffer buffer; 505 506 std::string tmp(1024 * 1024, 0); 507 508 for (;;) 509 { 510 int r = mg_read(connection, &tmp[0], tmp.size()); 511 if (r < 0) 512 { 513 return PostDataStatus_Failure; 514 } 515 else if (r == 0) 516 { 517 break; 518 } 519 else 520 { 521 buffer.Append(tmp.c_str(), r); 522 } 523 } 524 525 buffer.Read(body); 526 527 return PostDataStatus_Success; 528 } 529 530 ReadBodyToString(std::string & body,struct mg_connection * connection,const HttpToolbox::Arguments & headers)531 static PostDataStatus ReadBodyToString(std::string& body, 532 struct mg_connection *connection, 533 const HttpToolbox::Arguments& headers) 534 { 535 HttpToolbox::Arguments::const_iterator contentLength = headers.find("content-length"); 536 537 if (contentLength != headers.end()) 538 { 539 // "Content-Length" is available 540 return ReadBodyWithContentLength(body, connection, contentLength->second); 541 } 542 else 543 { 544 // No Content-Length 545 return ReadBodyWithoutContentLength(body, connection); 546 } 547 } 548 549 ReadBodyToStream(IHttpHandler::IChunkedRequestReader & stream,struct mg_connection * connection,const HttpToolbox::Arguments & headers)550 static PostDataStatus ReadBodyToStream(IHttpHandler::IChunkedRequestReader& stream, 551 struct mg_connection *connection, 552 const HttpToolbox::Arguments& headers) 553 { 554 HttpToolbox::Arguments::const_iterator contentLength = headers.find("content-length"); 555 556 if (contentLength != headers.end()) 557 { 558 // "Content-Length" is available 559 std::string body; 560 PostDataStatus status = ReadBodyWithContentLength(body, connection, contentLength->second); 561 562 if (status == PostDataStatus_Success && 563 !body.empty()) 564 { 565 stream.AddBodyChunk(body.c_str(), body.size()); 566 } 567 568 return status; 569 } 570 else 571 { 572 // No Content-Length: This is a chunked transfer. Stream the HTTP connection. 573 std::string tmp(1024 * 1024, 0); 574 575 for (;;) 576 { 577 int r = mg_read(connection, &tmp[0], tmp.size()); 578 if (r < 0) 579 { 580 return PostDataStatus_Failure; 581 } 582 else if (r == 0) 583 { 584 break; 585 } 586 else 587 { 588 stream.AddBodyChunk(tmp.c_str(), r); 589 } 590 } 591 592 return PostDataStatus_Success; 593 } 594 } 595 596 597 enum AccessMode 598 { 599 AccessMode_Forbidden, 600 AccessMode_AuthorizationToken, 601 AccessMode_RegisteredUser 602 }; 603 604 IsAccessGranted(const HttpServer & that,const HttpToolbox::Arguments & headers)605 static AccessMode IsAccessGranted(const HttpServer& that, 606 const HttpToolbox::Arguments& headers) 607 { 608 static const std::string BASIC = "Basic "; 609 static const std::string BEARER = "Bearer "; 610 611 HttpToolbox::Arguments::const_iterator auth = headers.find("authorization"); 612 if (auth != headers.end()) 613 { 614 std::string s = auth->second; 615 if (boost::starts_with(s, BASIC)) 616 { 617 std::string b64 = s.substr(BASIC.length()); 618 if (that.IsValidBasicHttpAuthentication(b64)) 619 { 620 return AccessMode_RegisteredUser; 621 } 622 } 623 else if (boost::starts_with(s, BEARER) && 624 that.GetIncomingHttpRequestFilter() != NULL) 625 { 626 // New in Orthanc 1.8.1 627 std::string token = s.substr(BEARER.length()); 628 if (that.GetIncomingHttpRequestFilter()->IsValidBearerToken(token)) 629 { 630 return AccessMode_AuthorizationToken; 631 } 632 } 633 } 634 635 return AccessMode_Forbidden; 636 } 637 638 GetAuthenticatedUsername(const HttpToolbox::Arguments & headers)639 static std::string GetAuthenticatedUsername(const HttpToolbox::Arguments& headers) 640 { 641 HttpToolbox::Arguments::const_iterator auth = headers.find("authorization"); 642 643 if (auth == headers.end()) 644 { 645 return ""; 646 } 647 648 std::string s = auth->second; 649 if (s.size() <= 6 || 650 s.substr(0, 6) != "Basic ") 651 { 652 return ""; 653 } 654 655 std::string b64 = s.substr(6); 656 std::string decoded; 657 Toolbox::DecodeBase64(decoded, b64); 658 size_t semicolons = decoded.find(':'); 659 660 if (semicolons == std::string::npos) 661 { 662 // Bad-formatted request 663 return ""; 664 } 665 else 666 { 667 return decoded.substr(0, semicolons); 668 } 669 } 670 671 ExtractMethod(HttpMethod & method,const struct mg_request_info * request,const HttpToolbox::Arguments & headers,const HttpToolbox::GetArguments & argumentsGET)672 static bool ExtractMethod(HttpMethod& method, 673 const struct mg_request_info *request, 674 const HttpToolbox::Arguments& headers, 675 const HttpToolbox::GetArguments& argumentsGET) 676 { 677 std::string overriden; 678 679 // Check whether some PUT/DELETE faking is done 680 681 // 1. Faking with Google's approach 682 HttpToolbox::Arguments::const_iterator methodOverride = 683 headers.find("x-http-method-override"); 684 685 if (methodOverride != headers.end()) 686 { 687 overriden = methodOverride->second; 688 } 689 else if (!strcmp(request->request_method, "GET")) 690 { 691 // 2. Faking with Ruby on Rail's approach 692 // GET /my/resource?_method=delete <=> DELETE /my/resource 693 for (size_t i = 0; i < argumentsGET.size(); i++) 694 { 695 if (argumentsGET[i].first == "_method") 696 { 697 overriden = argumentsGET[i].second; 698 break; 699 } 700 } 701 } 702 703 if (overriden.size() > 0) 704 { 705 // A faking has been done within this request 706 Toolbox::ToUpperCase(overriden); 707 708 CLOG(INFO, HTTP) << "HTTP method faking has been detected for " << overriden; 709 710 if (overriden == "PUT") 711 { 712 method = HttpMethod_Put; 713 return true; 714 } 715 else if (overriden == "DELETE") 716 { 717 method = HttpMethod_Delete; 718 return true; 719 } 720 else 721 { 722 return false; 723 } 724 } 725 726 // No PUT/DELETE faking was present 727 if (!strcmp(request->request_method, "GET")) 728 { 729 method = HttpMethod_Get; 730 } 731 else if (!strcmp(request->request_method, "POST")) 732 { 733 method = HttpMethod_Post; 734 } 735 else if (!strcmp(request->request_method, "DELETE")) 736 { 737 method = HttpMethod_Delete; 738 } 739 else if (!strcmp(request->request_method, "PUT")) 740 { 741 method = HttpMethod_Put; 742 } 743 else 744 { 745 return false; 746 } 747 748 return true; 749 } 750 751 ConfigureHttpCompression(HttpOutput & output,const HttpToolbox::Arguments & headers)752 static void ConfigureHttpCompression(HttpOutput& output, 753 const HttpToolbox::Arguments& headers) 754 { 755 // Look if the client wishes HTTP compression 756 // https://en.wikipedia.org/wiki/HTTP_compression 757 HttpToolbox::Arguments::const_iterator it = headers.find("accept-encoding"); 758 if (it != headers.end()) 759 { 760 std::vector<std::string> encodings; 761 Toolbox::TokenizeString(encodings, it->second, ','); 762 763 for (size_t i = 0; i < encodings.size(); i++) 764 { 765 std::string s = Toolbox::StripSpaces(encodings[i]); 766 767 if (s == "deflate") 768 { 769 output.SetDeflateAllowed(true); 770 } 771 else if (s == "gzip") 772 { 773 output.SetGzipAllowed(true); 774 } 775 } 776 } 777 } 778 779 780 #if ORTHANC_ENABLE_PUGIXML == 1 781 782 # if CIVETWEB_HAS_WEBDAV_WRITING == 0 AnswerWebDavReadOnly(HttpOutput & output,const std::string & uri)783 static void AnswerWebDavReadOnly(HttpOutput& output, 784 const std::string& uri) 785 { 786 CLOG(ERROR, HTTP) << "Orthanc was compiled without support for read-write access to WebDAV: " << uri; 787 output.SendStatus(HttpStatus_403_Forbidden); 788 } 789 # endif 790 HandleWebDav(HttpOutput & output,const HttpServer::WebDavBuckets & buckets,const std::string & method,const HttpToolbox::Arguments & headers,const std::string & uri,struct mg_connection * connection)791 static bool HandleWebDav(HttpOutput& output, 792 const HttpServer::WebDavBuckets& buckets, 793 const std::string& method, 794 const HttpToolbox::Arguments& headers, 795 const std::string& uri, 796 struct mg_connection *connection /* to read the PUT body if need be */) 797 { 798 if (buckets.empty()) 799 { 800 return false; // Speed up things if WebDAV is not used 801 } 802 803 /** 804 * The "buckets" maps an URI relative to the root of the 805 * bucket, to the content of the bucket. The root URI does *not* 806 * contain a trailing slash. 807 **/ 808 809 if (method == "OPTIONS") 810 { 811 // Remove the trailing slash, if any (necessary for davfs2) 812 std::string s = uri; 813 if (!s.empty() && 814 s[s.size() - 1] == '/') 815 { 816 s.resize(s.size() - 1); 817 } 818 819 HttpServer::WebDavBuckets::const_iterator bucket = buckets.find(s); 820 if (bucket == buckets.end()) 821 { 822 return false; 823 } 824 else 825 { 826 output.AddHeader("DAV", "1,2"); // Necessary for Windows XP 827 828 #if CIVETWEB_HAS_WEBDAV_WRITING == 1 829 output.AddHeader("Allow", "GET, PUT, DELETE, OPTIONS, PROPFIND, HEAD, LOCK, UNLOCK, PROPPATCH, MKCOL"); 830 #else 831 output.AddHeader("Allow", "GET, PUT, DELETE, OPTIONS, PROPFIND, HEAD"); 832 #endif 833 834 output.SendStatus(HttpStatus_200_Ok); 835 return true; 836 } 837 } 838 else if (method == "GET" || 839 method == "PROPFIND" || 840 method == "PROPPATCH" || 841 method == "PUT" || 842 method == "DELETE" || 843 method == "HEAD" || 844 method == "LOCK" || 845 method == "UNLOCK" || 846 method == "MKCOL") 847 { 848 // Locate the WebDAV bucket of interest, if any 849 for (HttpServer::WebDavBuckets::const_iterator bucket = buckets.begin(); 850 bucket != buckets.end(); ++bucket) 851 { 852 assert(!bucket->first.empty() && 853 bucket->first[bucket->first.size() - 1] != '/' && 854 bucket->second != NULL); 855 856 if (uri == bucket->first || 857 boost::starts_with(uri, bucket->first + "/")) 858 { 859 std::string s = uri.substr(bucket->first.size()); 860 if (s.empty()) 861 { 862 s = "/"; 863 } 864 865 std::vector<std::string> path; 866 Toolbox::SplitUriComponents(path, s); 867 868 869 /** 870 * WebDAV - PROPFIND 871 **/ 872 873 if (method == "PROPFIND") 874 { 875 HttpToolbox::Arguments::const_iterator i = headers.find("depth"); 876 if (i == headers.end()) 877 { 878 throw OrthancException(ErrorCode_NetworkProtocol, "WebDAV PROPFIND without depth"); 879 } 880 881 int depth = boost::lexical_cast<int>(i->second); 882 if (depth != 0 && 883 depth != 1) 884 { 885 throw OrthancException( 886 ErrorCode_NetworkProtocol, 887 "WebDAV PROPFIND at unsupported depth (can only be 0 or 1): " + i->second); 888 } 889 890 std::string answer; 891 892 MimeType mime; 893 std::string content; 894 boost::posix_time::ptime modificationTime = boost::posix_time::second_clock::universal_time(); 895 896 if (bucket->second->IsExistingFolder(path)) 897 { 898 if (depth == 0) 899 { 900 IWebDavBucket::Collection c; 901 c.Format(answer, uri); 902 } 903 else if (depth == 1) 904 { 905 IWebDavBucket::Collection c; 906 907 if (!bucket->second->ListCollection(c, path)) 908 { 909 output.SendStatus(HttpStatus_404_NotFound); 910 return true; 911 } 912 913 c.Format(answer, uri); 914 } 915 else 916 { 917 throw OrthancException(ErrorCode_InternalError); 918 } 919 } 920 else if (!path.empty() && 921 bucket->second->GetFileContent(mime, content, modificationTime, path)) 922 { 923 if (depth == 0 || 924 depth == 1) 925 { 926 std::unique_ptr<IWebDavBucket::File> f(new IWebDavBucket::File(path.back())); 927 f->SetContentLength(content.size()); 928 f->SetModificationTime(modificationTime); 929 f->SetMimeType(mime); 930 931 IWebDavBucket::Collection c; 932 c.AddResource(f.release()); 933 934 std::vector<std::string> p; 935 Toolbox::SplitUriComponents(p, uri); 936 if (p.empty()) 937 { 938 throw OrthancException(ErrorCode_InternalError); 939 } 940 941 p.resize(p.size() - 1); 942 c.Format(answer, Toolbox::FlattenUri(p)); 943 } 944 else 945 { 946 throw OrthancException(ErrorCode_InternalError); 947 } 948 } 949 else 950 { 951 output.SendStatus(HttpStatus_404_NotFound); 952 return true; 953 } 954 955 output.AddHeader("Content-Type", "application/xml; charset=UTF-8"); 956 output.SendStatus(HttpStatus_207_MultiStatus, answer); 957 return true; 958 } 959 960 961 /** 962 * WebDAV - GET and HEAD 963 **/ 964 965 else if (method == "GET" || 966 method == "HEAD") 967 { 968 MimeType mime; 969 std::string content; 970 boost::posix_time::ptime modificationTime; 971 972 if (bucket->second->GetFileContent(mime, content, modificationTime, path)) 973 { 974 output.AddHeader("Content-Type", EnumerationToString(mime)); 975 976 // "Last-Modified" is necessary on Windows XP. The "Z" 977 // suffix is mandatory on Windows >= 7. 978 output.AddHeader("Last-Modified", boost::posix_time::to_iso_extended_string(modificationTime) + "Z"); 979 980 if (method == "GET") 981 { 982 output.Answer(content); 983 } 984 else 985 { 986 output.SendStatus(HttpStatus_200_Ok); 987 } 988 } 989 else 990 { 991 output.SendStatus(HttpStatus_404_NotFound); 992 } 993 994 return true; 995 } 996 997 998 /** 999 * WebDAV - PUT 1000 **/ 1001 1002 else if (method == "PUT") 1003 { 1004 #if CIVETWEB_HAS_WEBDAV_WRITING == 1 1005 std::string body; 1006 if (ReadBodyToString(body, connection, headers) == PostDataStatus_Success) 1007 { 1008 if (bucket->second->StoreFile(body, path)) 1009 { 1010 //output.SendStatus(HttpStatus_200_Ok); 1011 output.SendStatus(HttpStatus_201_Created); 1012 } 1013 else 1014 { 1015 output.SendStatus(HttpStatus_403_Forbidden); 1016 } 1017 } 1018 else 1019 { 1020 CLOG(ERROR, HTTP) << "Cannot read the content of a file to be stored in WebDAV"; 1021 output.SendStatus(HttpStatus_400_BadRequest); 1022 } 1023 #else 1024 AnswerWebDavReadOnly(output, uri); 1025 #endif 1026 1027 return true; 1028 } 1029 1030 1031 /** 1032 * WebDAV - DELETE 1033 **/ 1034 1035 else if (method == "DELETE") 1036 { 1037 if (bucket->second->DeleteItem(path)) 1038 { 1039 output.SendStatus(HttpStatus_204_NoContent); 1040 } 1041 else 1042 { 1043 output.SendStatus(HttpStatus_403_Forbidden); 1044 } 1045 return true; 1046 } 1047 1048 1049 /** 1050 * WebDAV - MKCOL 1051 **/ 1052 1053 else if (method == "MKCOL") 1054 { 1055 #if CIVETWEB_HAS_WEBDAV_WRITING == 1 1056 if (bucket->second->CreateFolder(path)) 1057 { 1058 //output.SendStatus(HttpStatus_200_Ok); 1059 output.SendStatus(HttpStatus_201_Created); 1060 } 1061 else 1062 { 1063 output.SendStatus(HttpStatus_403_Forbidden); 1064 } 1065 #else 1066 AnswerWebDavReadOnly(output, uri); 1067 #endif 1068 1069 return true; 1070 } 1071 1072 1073 /** 1074 * WebDAV - Faking PROPPATCH, LOCK and UNLOCK 1075 **/ 1076 1077 else if (method == "PROPPATCH") 1078 { 1079 #if CIVETWEB_HAS_WEBDAV_WRITING == 1 1080 IWebDavBucket::AnswerFakedProppatch(output, uri); 1081 #else 1082 AnswerWebDavReadOnly(output, uri); 1083 #endif 1084 return true; 1085 } 1086 else if (method == "LOCK") 1087 { 1088 #if CIVETWEB_HAS_WEBDAV_WRITING == 1 1089 IWebDavBucket::AnswerFakedLock(output, uri); 1090 #else 1091 AnswerWebDavReadOnly(output, uri); 1092 #endif 1093 return true; 1094 } 1095 else if (method == "UNLOCK") 1096 { 1097 #if CIVETWEB_HAS_WEBDAV_WRITING == 1 1098 IWebDavBucket::AnswerFakedUnlock(output); 1099 #else 1100 AnswerWebDavReadOnly(output, uri); 1101 #endif 1102 return true; 1103 } 1104 else 1105 { 1106 throw OrthancException(ErrorCode_InternalError); 1107 } 1108 } 1109 } 1110 1111 return false; 1112 } 1113 else 1114 { 1115 /** 1116 * WebDAV - Unapplicable method (such as POST and DELETE) 1117 **/ 1118 1119 return false; 1120 } 1121 } 1122 #endif /* ORTHANC_ENABLE_PUGIXML == 1 */ 1123 1124 InternalCallback(HttpOutput & output,HttpMethod & method,HttpServer & server,struct mg_connection * connection,const struct mg_request_info * request)1125 static void InternalCallback(HttpOutput& output /* out */, 1126 HttpMethod& method /* out */, 1127 HttpServer& server, 1128 struct mg_connection *connection, 1129 const struct mg_request_info *request) 1130 { 1131 bool localhost; 1132 1133 #if ORTHANC_ENABLE_MONGOOSE == 1 1134 static const long LOCALHOST = (127ll << 24) + 1ll; 1135 localhost = (request->remote_ip == LOCALHOST); 1136 #elif ORTHANC_ENABLE_CIVETWEB == 1 1137 // The "remote_ip" field of "struct mg_request_info" is tagged as 1138 // deprecated in Civetweb, using "remote_addr" instead. 1139 localhost = (std::string(request->remote_addr) == "127.0.0.1"); 1140 #else 1141 # error 1142 #endif 1143 1144 // Check remote calls 1145 if (!server.IsRemoteAccessAllowed() && 1146 !localhost) 1147 { 1148 output.SendUnauthorized(server.GetRealm()); // 401 error 1149 return; 1150 } 1151 1152 1153 // Extract the HTTP headers 1154 HttpToolbox::Arguments headers; 1155 for (int i = 0; i < request->num_headers; i++) 1156 { 1157 std::string name = request->http_headers[i].name; 1158 std::string value = request->http_headers[i].value; 1159 1160 std::transform(name.begin(), name.end(), name.begin(), ::tolower); 1161 headers.insert(std::make_pair(name, value)); 1162 CLOG(TRACE, HTTP) << "HTTP header: [" << name << "]: [" << value << "]"; 1163 } 1164 1165 if (server.IsHttpCompressionEnabled()) 1166 { 1167 ConfigureHttpCompression(output, headers); 1168 } 1169 1170 1171 // Extract the GET arguments 1172 HttpToolbox::GetArguments argumentsGET; 1173 if (!strcmp(request->request_method, "GET")) 1174 { 1175 HttpToolbox::ParseGetArguments(argumentsGET, request->query_string); 1176 } 1177 1178 1179 AccessMode accessMode = IsAccessGranted(server, headers); 1180 1181 // Authenticate this connection 1182 if (server.IsAuthenticationEnabled() && 1183 accessMode == AccessMode_Forbidden) 1184 { 1185 output.SendUnauthorized(server.GetRealm()); // 401 error 1186 return; 1187 } 1188 1189 1190 #if ORTHANC_ENABLE_MONGOOSE == 1 1191 // Apply the filter, if it is installed 1192 char remoteIp[24]; 1193 sprintf(remoteIp, "%d.%d.%d.%d", 1194 reinterpret_cast<const uint8_t*>(&request->remote_ip) [3], 1195 reinterpret_cast<const uint8_t*>(&request->remote_ip) [2], 1196 reinterpret_cast<const uint8_t*>(&request->remote_ip) [1], 1197 reinterpret_cast<const uint8_t*>(&request->remote_ip) [0]); 1198 1199 const char* requestUri = request->uri; 1200 1201 #elif ORTHANC_ENABLE_CIVETWEB == 1 1202 const char* remoteIp = request->remote_addr; 1203 const char* requestUri = request->local_uri; 1204 #else 1205 # error 1206 #endif 1207 1208 if (requestUri == NULL) 1209 { 1210 requestUri = ""; 1211 } 1212 1213 // Decompose the URI into its components 1214 UriComponents uri; 1215 try 1216 { 1217 Toolbox::SplitUriComponents(uri, requestUri); 1218 } 1219 catch (OrthancException&) 1220 { 1221 output.SendStatus(HttpStatus_400_BadRequest); 1222 return; 1223 } 1224 1225 1226 // Compute the HTTP method, taking method faking into consideration 1227 method = HttpMethod_Get; 1228 1229 #if ORTHANC_ENABLE_PUGIXML == 1 1230 bool isWebDav = false; 1231 #endif 1232 1233 HttpMethod filterMethod; 1234 1235 1236 if (ExtractMethod(method, request, headers, argumentsGET)) 1237 { 1238 CLOG(INFO, HTTP) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri); 1239 filterMethod = method; 1240 } 1241 #if ORTHANC_ENABLE_PUGIXML == 1 1242 else if (!strcmp(request->request_method, "OPTIONS") || 1243 !strcmp(request->request_method, "PROPFIND") || 1244 !strcmp(request->request_method, "HEAD")) 1245 { 1246 CLOG(INFO, HTTP) << "Incoming read-only WebDAV request: " 1247 << request->request_method << " " << requestUri; 1248 filterMethod = HttpMethod_Get; 1249 isWebDav = true; 1250 } 1251 else if (!strcmp(request->request_method, "PROPPATCH") || 1252 !strcmp(request->request_method, "LOCK") || 1253 !strcmp(request->request_method, "UNLOCK") || 1254 !strcmp(request->request_method, "MKCOL")) 1255 { 1256 CLOG(INFO, HTTP) << "Incoming read-write WebDAV request: " 1257 << request->request_method << " " << requestUri; 1258 filterMethod = HttpMethod_Put; 1259 isWebDav = true; 1260 } 1261 #endif /* ORTHANC_ENABLE_PUGIXML == 1 */ 1262 else 1263 { 1264 CLOG(INFO, HTTP) << "Unknown HTTP method: " << request->request_method; 1265 output.SendStatus(HttpStatus_400_BadRequest); 1266 return; 1267 } 1268 1269 1270 const std::string username = GetAuthenticatedUsername(headers); 1271 1272 if (accessMode != AccessMode_AuthorizationToken) 1273 { 1274 // Check that this access is granted by the user's authorization 1275 // filter. In the case of an authorization bearer token, grant 1276 // full access to the API. 1277 1278 assert(accessMode == AccessMode_Forbidden || // Could be the case if "!server.IsAuthenticationEnabled()" 1279 accessMode == AccessMode_RegisteredUser); 1280 1281 IIncomingHttpRequestFilter *filter = server.GetIncomingHttpRequestFilter(); 1282 if (filter != NULL && 1283 !filter->IsAllowed(filterMethod, requestUri, remoteIp, 1284 username.c_str(), headers, argumentsGET)) 1285 { 1286 output.SendStatus(HttpStatus_403_Forbidden); 1287 return; 1288 } 1289 } 1290 1291 1292 #if ORTHANC_ENABLE_PUGIXML == 1 1293 if (HandleWebDav(output, server.GetWebDavBuckets(), request->request_method, 1294 headers, requestUri, connection)) 1295 { 1296 return; 1297 } 1298 else if (isWebDav) 1299 { 1300 CLOG(INFO, HTTP) << "No WebDAV bucket is registered against URI: " 1301 << request->request_method << " " << requestUri; 1302 output.SendStatus(HttpStatus_404_NotFound); 1303 return; 1304 } 1305 #endif 1306 1307 1308 bool found = false; 1309 1310 // Extract the body of the request for PUT and POST, or process 1311 // the body as a stream 1312 1313 std::string body; 1314 if (method == HttpMethod_Post || 1315 method == HttpMethod_Put) 1316 { 1317 PostDataStatus status; 1318 1319 bool isMultipartForm = false; 1320 1321 std::string type, subType, boundary; 1322 HttpToolbox::Arguments::const_iterator ct = headers.find("content-type"); 1323 if (method == HttpMethod_Post && 1324 ct != headers.end() && 1325 MultipartStreamReader::ParseMultipartContentType(type, subType, boundary, ct->second) && 1326 type == "multipart/form-data") 1327 { 1328 /** 1329 * The user uses the "upload" form of Orthanc Explorer, for 1330 * file uploads through a HTML form. 1331 **/ 1332 isMultipartForm = true; 1333 1334 status = ReadBodyToString(body, connection, headers); 1335 if (status == PostDataStatus_Success) 1336 { 1337 server.ProcessMultipartFormData(remoteIp, username, uri, headers, body, boundary); 1338 output.SendStatus(HttpStatus_200_Ok); 1339 return; 1340 } 1341 } 1342 1343 if (!isMultipartForm) 1344 { 1345 std::unique_ptr<IHttpHandler::IChunkedRequestReader> stream; 1346 1347 if (server.HasHandler()) 1348 { 1349 found = server.GetHandler().CreateChunkedRequestReader 1350 (stream, RequestOrigin_RestApi, remoteIp, username.c_str(), method, uri, headers); 1351 } 1352 1353 if (found) 1354 { 1355 if (stream.get() == NULL) 1356 { 1357 throw OrthancException(ErrorCode_InternalError); 1358 } 1359 1360 status = ReadBodyToStream(*stream, connection, headers); 1361 1362 if (status == PostDataStatus_Success) 1363 { 1364 stream->Execute(output); 1365 } 1366 } 1367 else 1368 { 1369 status = ReadBodyToString(body, connection, headers); 1370 } 1371 } 1372 1373 switch (status) 1374 { 1375 case PostDataStatus_NoLength: 1376 output.SendStatus(HttpStatus_411_LengthRequired); 1377 return; 1378 1379 case PostDataStatus_Failure: 1380 output.SendStatus(HttpStatus_400_BadRequest); 1381 return; 1382 1383 case PostDataStatus_Pending: 1384 output.AnswerEmpty(); 1385 return; 1386 1387 case PostDataStatus_Success: 1388 break; 1389 1390 default: 1391 throw OrthancException(ErrorCode_InternalError); 1392 } 1393 } 1394 1395 if (!found && 1396 server.HasHandler()) 1397 { 1398 found = server.GetHandler().Handle(output, RequestOrigin_RestApi, remoteIp, username.c_str(), 1399 method, uri, headers, argumentsGET, body.c_str(), body.size()); 1400 } 1401 1402 if (!found) 1403 { 1404 throw OrthancException(ErrorCode_UnknownResource); 1405 } 1406 } 1407 1408 ProtectedCallback(struct mg_connection * connection,const struct mg_request_info * request)1409 static void ProtectedCallback(struct mg_connection *connection, 1410 const struct mg_request_info *request) 1411 { 1412 try 1413 { 1414 #if ORTHANC_ENABLE_MONGOOSE == 1 1415 void *that = request->user_data; 1416 const char* requestUri = request->uri; 1417 #elif ORTHANC_ENABLE_CIVETWEB == 1 1418 // https://github.com/civetweb/civetweb/issues/409 1419 void *that = mg_get_user_data(mg_get_context(connection)); 1420 const char* requestUri = request->local_uri; 1421 #else 1422 # error 1423 #endif 1424 1425 if (requestUri == NULL) 1426 { 1427 requestUri = ""; 1428 } 1429 1430 HttpServer* server = reinterpret_cast<HttpServer*>(that); 1431 1432 if (server == NULL) 1433 { 1434 MongooseOutputStream stream(connection); 1435 HttpOutput output(stream, false /* assume no keep-alive */); 1436 output.SendStatus(HttpStatus_500_InternalServerError); 1437 return; 1438 } 1439 1440 MongooseOutputStream stream(connection); 1441 HttpOutput output(stream, server->IsKeepAliveEnabled()); 1442 HttpMethod method = HttpMethod_Get; 1443 1444 try 1445 { 1446 try 1447 { 1448 InternalCallback(output, method, *server, connection, request); 1449 } 1450 catch (OrthancException&) 1451 { 1452 throw; // Pass the exception to the main handler below 1453 } 1454 // Now convert native exceptions as OrthancException 1455 catch (boost::bad_lexical_cast&) 1456 { 1457 throw OrthancException(ErrorCode_BadParameterType, 1458 "Syntax error in some user-supplied data"); 1459 } 1460 catch (boost::filesystem::filesystem_error& e) 1461 { 1462 throw OrthancException(ErrorCode_InternalError, 1463 "Error while accessing the filesystem: " + e.path1().string()); 1464 } 1465 catch (std::runtime_error&) 1466 { 1467 throw OrthancException(ErrorCode_BadRequest, 1468 "Presumably an error while parsing the JSON body"); 1469 } 1470 catch (std::bad_alloc&) 1471 { 1472 throw OrthancException(ErrorCode_NotEnoughMemory, 1473 "The server hosting Orthanc is running out of memory"); 1474 } 1475 catch (...) 1476 { 1477 throw OrthancException(ErrorCode_InternalError, 1478 "An unhandled exception was generated inside the HTTP server"); 1479 } 1480 } 1481 catch (OrthancException& e) 1482 { 1483 assert(server != NULL); 1484 1485 // Using this candidate handler results in an exception 1486 try 1487 { 1488 if (server->GetExceptionFormatter() == NULL) 1489 { 1490 CLOG(ERROR, HTTP) << "Exception in the HTTP handler: " << e.What(); 1491 output.SendStatus(e.GetHttpStatus()); 1492 } 1493 else 1494 { 1495 server->GetExceptionFormatter()->Format(output, e, method, requestUri); 1496 } 1497 } 1498 catch (OrthancException&) 1499 { 1500 // An exception here reflects the fact that the status code 1501 // was already set by the HTTP handler. 1502 } 1503 } 1504 } 1505 catch (...) 1506 { 1507 // We should never arrive at this point, where it is even impossible to send an answer 1508 CLOG(ERROR, HTTP) << "Catastrophic error inside the HTTP server, giving up"; 1509 } 1510 } 1511 1512 1513 #if MONGOOSE_USE_CALLBACKS == 0 Callback(enum mg_event event,struct mg_connection * connection,const struct mg_request_info * request)1514 static void* Callback(enum mg_event event, 1515 struct mg_connection *connection, 1516 const struct mg_request_info *request) 1517 { 1518 if (event == MG_NEW_REQUEST) 1519 { 1520 ProtectedCallback(connection, request); 1521 1522 // Mark as processed 1523 return (void*) ""; 1524 } 1525 else 1526 { 1527 return NULL; 1528 } 1529 } 1530 1531 #elif MONGOOSE_USE_CALLBACKS == 1 Callback(struct mg_connection * connection)1532 static int Callback(struct mg_connection *connection) 1533 { 1534 const struct mg_request_info *request = mg_get_request_info(connection); 1535 1536 ProtectedCallback(connection, request); 1537 1538 return 1; // Do not let Mongoose handle the request by itself 1539 } 1540 1541 #else 1542 # error Please set MONGOOSE_USE_CALLBACKS 1543 #endif 1544 1545 1546 1547 1548 IsRunning() const1549 bool HttpServer::IsRunning() const 1550 { 1551 return (pimpl_->context_ != NULL); 1552 } 1553 1554 HttpServer()1555 HttpServer::HttpServer() : 1556 pimpl_(new PImpl), 1557 handler_(NULL), 1558 remoteAllowed_(false), 1559 authentication_(false), 1560 sslVerifyPeers_(false), 1561 ssl_(false), 1562 sslMinimumVersion_(0), // Default to any of "SSL2+SSL3+TLS1.0+TLS1.1+TLS1.2" 1563 sslHasCiphers_(false), 1564 port_(8000), 1565 filter_(NULL), 1566 keepAlive_(false), 1567 httpCompression_(true), 1568 exceptionFormatter_(NULL), 1569 realm_(ORTHANC_REALM), 1570 threadsCount_(50), // Default value in mongoose/civetweb 1571 tcpNoDelay_(true), 1572 requestTimeout_(30) // Default value in mongoose/civetweb (30 seconds) 1573 { 1574 #if ORTHANC_ENABLE_MONGOOSE == 1 1575 CLOG(INFO, HTTP) << "This Orthanc server uses Mongoose as its embedded HTTP server"; 1576 #endif 1577 1578 #if ORTHANC_ENABLE_CIVETWEB == 1 1579 CLOG(INFO, HTTP) << "This Orthanc server uses CivetWeb as its embedded HTTP server"; 1580 #endif 1581 1582 #if ORTHANC_ENABLE_SSL == 1 1583 // Check for the Heartbleed exploit 1584 // https://en.wikipedia.org/wiki/OpenSSL#Heartbleed_bug 1585 if (OPENSSL_VERSION_NUMBER < 0x1000107fL /* openssl-1.0.1g */ && 1586 OPENSSL_VERSION_NUMBER >= 0x1000100fL /* openssl-1.0.1 */) 1587 { 1588 CLOG(WARNING, HTTP) << "This version of OpenSSL is vulnerable to the Heartbleed exploit"; 1589 } 1590 #endif 1591 } 1592 1593 ~HttpServer()1594 HttpServer::~HttpServer() 1595 { 1596 Stop(); 1597 1598 #if ORTHANC_ENABLE_PUGIXML == 1 1599 for (WebDavBuckets::iterator it = webDavBuckets_.begin(); it != webDavBuckets_.end(); ++it) 1600 { 1601 assert(it->second != NULL); 1602 delete it->second; 1603 } 1604 #endif 1605 } 1606 1607 SetPortNumber(uint16_t port)1608 void HttpServer::SetPortNumber(uint16_t port) 1609 { 1610 Stop(); 1611 port_ = port; 1612 } 1613 GetPortNumber() const1614 uint16_t HttpServer::GetPortNumber() const 1615 { 1616 return port_; 1617 } 1618 Start()1619 void HttpServer::Start() 1620 { 1621 #if ORTHANC_ENABLE_MONGOOSE == 1 1622 CLOG(INFO, HTTP) << "Starting embedded Web server using Mongoose"; 1623 #elif ORTHANC_ENABLE_CIVETWEB == 1 1624 CLOG(INFO, HTTP) << "Starting embedded Web server using Civetweb"; 1625 #else 1626 # error 1627 #endif 1628 1629 if (!IsRunning()) 1630 { 1631 std::string port = boost::lexical_cast<std::string>(port_); 1632 std::string numThreads = boost::lexical_cast<std::string>(threadsCount_); 1633 std::string requestTimeoutMilliseconds = boost::lexical_cast<std::string>(requestTimeout_ * 1000); 1634 std::string keepAliveTimeoutMilliseconds = boost::lexical_cast<std::string>(CIVETWEB_KEEP_ALIVE_TIMEOUT_SECONDS * 1000); 1635 std::string sslMinimumVersion = boost::lexical_cast<std::string>(sslMinimumVersion_); 1636 1637 if (ssl_) 1638 { 1639 port += "s"; 1640 } 1641 1642 std::vector<const char*> options; 1643 1644 // Set the TCP port for the HTTP server 1645 options.push_back("listening_ports"); 1646 options.push_back(port.c_str()); 1647 1648 // Optimization reported by Chris Hafey 1649 // https://groups.google.com/d/msg/orthanc-users/CKueKX0pJ9E/_UCbl8T-VjIJ 1650 options.push_back("enable_keep_alive"); 1651 options.push_back(keepAlive_ ? "yes" : "no"); 1652 1653 #if ORTHANC_ENABLE_CIVETWEB == 1 1654 /** 1655 * The "keep_alive_timeout_ms" cannot use milliseconds, as the 1656 * value of "timeout" in the HTTP header "Keep-Alive" must be 1657 * expressed in seconds (at least for the Java client). 1658 * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Keep-Alive 1659 * https://github.com/civetweb/civetweb/blob/master/docs/UserManual.md#enable_keep_alive-no 1660 * https://github.com/civetweb/civetweb/blob/master/docs/UserManual.md#keep_alive_timeout_ms-500-or-0 1661 **/ 1662 options.push_back("keep_alive_timeout_ms"); 1663 options.push_back(keepAlive_ ? keepAliveTimeoutMilliseconds.c_str() : "0"); 1664 #endif 1665 1666 #if ORTHANC_ENABLE_CIVETWEB == 1 1667 // Disable TCP Nagle's algorithm to maximize speed (this 1668 // option is not available in Mongoose). 1669 // https://groups.google.com/d/topic/civetweb/35HBR9seFjU/discussion 1670 // https://eklitzke.org/the-caveats-of-tcp-nodelay 1671 options.push_back("tcp_nodelay"); 1672 options.push_back(tcpNoDelay_ ? "1" : "0"); 1673 #endif 1674 1675 // Set the number of threads 1676 options.push_back("num_threads"); 1677 options.push_back(numThreads.c_str()); 1678 1679 // Set the timeout for the HTTP server 1680 options.push_back("request_timeout_ms"); 1681 options.push_back(requestTimeoutMilliseconds.c_str()); 1682 1683 // Set the client authentication 1684 options.push_back("ssl_verify_peer"); 1685 options.push_back(sslVerifyPeers_ ? "yes" : "no"); 1686 1687 if (sslVerifyPeers_) 1688 { 1689 // Set the trusted client certificates (for X509 mutual authentication) 1690 options.push_back("ssl_ca_file"); 1691 options.push_back(trustedClientCertificates_.c_str()); 1692 } 1693 1694 if (ssl_) 1695 { 1696 // Restrict minimum SSL/TLS protocol version 1697 options.push_back("ssl_protocol_version"); 1698 options.push_back(sslMinimumVersion.c_str()); 1699 1700 // Set the accepted ciphers list 1701 if (sslHasCiphers_) 1702 { 1703 options.push_back("ssl_cipher_list"); 1704 options.push_back(sslCiphers_.c_str()); 1705 } 1706 1707 // Set the SSL certificate, if any 1708 options.push_back("ssl_certificate"); 1709 options.push_back(certificate_.c_str()); 1710 }; 1711 1712 assert(options.size() % 2 == 0); 1713 options.push_back(NULL); 1714 1715 #if MONGOOSE_USE_CALLBACKS == 0 1716 pimpl_->context_ = mg_start(&Callback, this, &options[0]); 1717 1718 #elif MONGOOSE_USE_CALLBACKS == 1 1719 struct mg_callbacks callbacks; 1720 memset(&callbacks, 0, sizeof(callbacks)); 1721 callbacks.begin_request = Callback; 1722 pimpl_->context_ = mg_start(&callbacks, this, &options[0]); 1723 1724 #else 1725 # error Please set MONGOOSE_USE_CALLBACKS 1726 #endif 1727 1728 if (!pimpl_->context_) 1729 { 1730 bool isSslError = false; 1731 1732 #if ORTHANC_ENABLE_SSL == 1 1733 for (;;) 1734 { 1735 unsigned long code = ERR_get_error(); 1736 if (code == 0) 1737 { 1738 break; 1739 } 1740 else 1741 { 1742 isSslError = true; 1743 char message[1024]; 1744 ERR_error_string_n(code, message, sizeof(message) - 1); 1745 CLOG(ERROR, HTTP) << "OpenSSL error: " << message; 1746 } 1747 } 1748 #endif 1749 1750 if (isSslError) 1751 { 1752 throw OrthancException(ErrorCode_SslInitialization); 1753 } 1754 else 1755 { 1756 throw OrthancException(ErrorCode_HttpPortInUse, 1757 " (port = " + boost::lexical_cast<std::string>(port_) + ")"); 1758 } 1759 } 1760 1761 #if ORTHANC_ENABLE_PUGIXML == 1 1762 for (WebDavBuckets::iterator it = webDavBuckets_.begin(); it != webDavBuckets_.end(); ++it) 1763 { 1764 assert(it->second != NULL); 1765 it->second->Start(); 1766 } 1767 #endif 1768 1769 CLOG(WARNING, HTTP) << "HTTP server listening on port: " << GetPortNumber() 1770 << " (HTTPS encryption is " 1771 << (IsSslEnabled() ? "enabled" : "disabled") 1772 << ", remote access is " 1773 << (IsRemoteAccessAllowed() ? "" : "not ") 1774 << "allowed)"; 1775 } 1776 } 1777 Stop()1778 void HttpServer::Stop() 1779 { 1780 if (IsRunning()) 1781 { 1782 mg_stop(pimpl_->context_); 1783 1784 #if ORTHANC_ENABLE_PUGIXML == 1 1785 for (WebDavBuckets::iterator it = webDavBuckets_.begin(); it != webDavBuckets_.end(); ++it) 1786 { 1787 assert(it->second != NULL); 1788 it->second->Stop(); 1789 } 1790 #endif 1791 1792 pimpl_->context_ = NULL; 1793 } 1794 } 1795 1796 ClearUsers()1797 void HttpServer::ClearUsers() 1798 { 1799 Stop(); 1800 registeredUsers_.clear(); 1801 } 1802 1803 RegisterUser(const char * username,const char * password)1804 void HttpServer::RegisterUser(const char* username, 1805 const char* password) 1806 { 1807 Stop(); 1808 1809 std::string tag = std::string(username) + ":" + std::string(password); 1810 std::string encoded; 1811 Toolbox::EncodeBase64(encoded, tag); 1812 registeredUsers_.insert(encoded); 1813 } 1814 IsAuthenticationEnabled() const1815 bool HttpServer::IsAuthenticationEnabled() const 1816 { 1817 return authentication_; 1818 } 1819 SetSslEnabled(bool enabled)1820 void HttpServer::SetSslEnabled(bool enabled) 1821 { 1822 Stop(); 1823 1824 #if ORTHANC_ENABLE_SSL == 0 1825 if (enabled) 1826 { 1827 throw OrthancException(ErrorCode_SslDisabled); 1828 } 1829 else 1830 { 1831 ssl_ = false; 1832 } 1833 #else 1834 ssl_ = enabled; 1835 #endif 1836 } 1837 SetSslVerifyPeers(bool enabled)1838 void HttpServer::SetSslVerifyPeers(bool enabled) 1839 { 1840 Stop(); 1841 1842 #if ORTHANC_ENABLE_SSL == 0 1843 if (enabled) 1844 { 1845 throw OrthancException(ErrorCode_SslDisabled); 1846 } 1847 else 1848 { 1849 sslVerifyPeers_ = false; 1850 } 1851 #else 1852 sslVerifyPeers_ = enabled; 1853 #endif 1854 } 1855 SetSslMinimumVersion(unsigned int version)1856 void HttpServer::SetSslMinimumVersion(unsigned int version) 1857 { 1858 Stop(); 1859 sslMinimumVersion_ = version; 1860 1861 std::string info; 1862 1863 switch (version) 1864 { 1865 case 0: 1866 info = "SSL2+SSL3+TLS1.0+TLS1.1+TLS1.2"; 1867 break; 1868 1869 case 1: 1870 info = "SSL3+TLS1.0+TLS1.1+TLS1.2"; 1871 break; 1872 1873 case 2: 1874 info = "TLS1.0+TLS1.1+TLS1.2"; 1875 break; 1876 1877 case 3: 1878 info = "TLS1.1+TLS1.2"; 1879 break; 1880 1881 case 4: 1882 info = "TLS1.2"; 1883 break; 1884 1885 default: 1886 info = "Unknown value (" + boost::lexical_cast<std::string>(version) + ")"; 1887 break; 1888 } 1889 1890 CLOG(INFO, HTTP) << "Minimal accepted version of SSL/TLS protocol: " << info; 1891 } 1892 SetSslCiphers(const std::list<std::string> & ciphers)1893 void HttpServer::SetSslCiphers(const std::list<std::string>& ciphers) 1894 { 1895 Stop(); 1896 1897 sslHasCiphers_ = true; 1898 sslCiphers_.clear(); 1899 1900 for (std::list<std::string>::const_iterator 1901 it = ciphers.begin(); it != ciphers.end(); ++it) 1902 { 1903 if (it->empty()) 1904 { 1905 throw OrthancException(ErrorCode_ParameterOutOfRange, "Empty name for a cipher"); 1906 } 1907 1908 if (!sslCiphers_.empty()) 1909 { 1910 sslCiphers_ += ':'; 1911 } 1912 1913 sslCiphers_ += (*it); 1914 } 1915 1916 CLOG(INFO, HTTP) << "List of accepted SSL ciphers: " << sslCiphers_; 1917 1918 if (sslCiphers_.empty()) 1919 { 1920 CLOG(WARNING, HTTP) << "No cipher is accepted for SSL"; 1921 } 1922 } 1923 SetKeepAliveEnabled(bool enabled)1924 void HttpServer::SetKeepAliveEnabled(bool enabled) 1925 { 1926 Stop(); 1927 keepAlive_ = enabled; 1928 CLOG(INFO, HTTP) << "HTTP keep alive is " << (enabled ? "enabled" : "disabled"); 1929 1930 #if ORTHANC_ENABLE_MONGOOSE == 1 1931 if (enabled) 1932 { 1933 CLOG(WARNING, HTTP) << "You should disable HTTP keep alive, as you are using Mongoose"; 1934 } 1935 #endif 1936 } 1937 GetSslCertificate() const1938 const std::string &HttpServer::GetSslCertificate() const 1939 { 1940 return certificate_; 1941 } 1942 1943 SetAuthenticationEnabled(bool enabled)1944 void HttpServer::SetAuthenticationEnabled(bool enabled) 1945 { 1946 Stop(); 1947 authentication_ = enabled; 1948 } 1949 IsSslEnabled() const1950 bool HttpServer::IsSslEnabled() const 1951 { 1952 return ssl_; 1953 } 1954 SetSslCertificate(const char * path)1955 void HttpServer::SetSslCertificate(const char* path) 1956 { 1957 Stop(); 1958 certificate_ = path; 1959 } 1960 IsRemoteAccessAllowed() const1961 bool HttpServer::IsRemoteAccessAllowed() const 1962 { 1963 return remoteAllowed_; 1964 } 1965 SetSslTrustedClientCertificates(const char * path)1966 void HttpServer::SetSslTrustedClientCertificates(const char* path) 1967 { 1968 Stop(); 1969 trustedClientCertificates_ = path; 1970 } 1971 IsKeepAliveEnabled() const1972 bool HttpServer::IsKeepAliveEnabled() const 1973 { 1974 return keepAlive_; 1975 } 1976 SetRemoteAccessAllowed(bool allowed)1977 void HttpServer::SetRemoteAccessAllowed(bool allowed) 1978 { 1979 Stop(); 1980 remoteAllowed_ = allowed; 1981 } 1982 IsHttpCompressionEnabled() const1983 bool HttpServer::IsHttpCompressionEnabled() const 1984 { 1985 return httpCompression_;; 1986 } 1987 SetHttpCompressionEnabled(bool enabled)1988 void HttpServer::SetHttpCompressionEnabled(bool enabled) 1989 { 1990 Stop(); 1991 httpCompression_ = enabled; 1992 CLOG(WARNING, HTTP) << "HTTP compression is " << (enabled ? "enabled" : "disabled"); 1993 } 1994 GetIncomingHttpRequestFilter() const1995 IIncomingHttpRequestFilter *HttpServer::GetIncomingHttpRequestFilter() const 1996 { 1997 return filter_; 1998 } 1999 SetIncomingHttpRequestFilter(IIncomingHttpRequestFilter & filter)2000 void HttpServer::SetIncomingHttpRequestFilter(IIncomingHttpRequestFilter& filter) 2001 { 2002 Stop(); 2003 filter_ = &filter; 2004 } 2005 2006 SetHttpExceptionFormatter(IHttpExceptionFormatter & formatter)2007 void HttpServer::SetHttpExceptionFormatter(IHttpExceptionFormatter& formatter) 2008 { 2009 Stop(); 2010 exceptionFormatter_ = &formatter; 2011 } 2012 GetExceptionFormatter()2013 IHttpExceptionFormatter *HttpServer::GetExceptionFormatter() 2014 { 2015 return exceptionFormatter_; 2016 } 2017 GetRealm() const2018 const std::string &HttpServer::GetRealm() const 2019 { 2020 return realm_; 2021 } 2022 SetRealm(const std::string & realm)2023 void HttpServer::SetRealm(const std::string &realm) 2024 { 2025 realm_ = realm; 2026 } 2027 2028 IsValidBasicHttpAuthentication(const std::string & basic) const2029 bool HttpServer::IsValidBasicHttpAuthentication(const std::string& basic) const 2030 { 2031 return registeredUsers_.find(basic) != registeredUsers_.end(); 2032 } 2033 2034 Register(IHttpHandler & handler)2035 void HttpServer::Register(IHttpHandler& handler) 2036 { 2037 Stop(); 2038 handler_ = &handler; 2039 } 2040 HasHandler() const2041 bool HttpServer::HasHandler() const 2042 { 2043 return handler_ != NULL; 2044 } 2045 2046 GetHandler() const2047 IHttpHandler& HttpServer::GetHandler() const 2048 { 2049 if (handler_ == NULL) 2050 { 2051 throw OrthancException(ErrorCode_InternalError); 2052 } 2053 2054 return *handler_; 2055 } 2056 2057 SetThreadsCount(unsigned int threads)2058 void HttpServer::SetThreadsCount(unsigned int threads) 2059 { 2060 if (threads == 0) 2061 { 2062 throw OrthancException(ErrorCode_ParameterOutOfRange); 2063 } 2064 2065 Stop(); 2066 threadsCount_ = threads; 2067 2068 CLOG(INFO, HTTP) << "The embedded HTTP server will use " << threads << " threads"; 2069 } 2070 GetThreadsCount() const2071 unsigned int HttpServer::GetThreadsCount() const 2072 { 2073 return threadsCount_; 2074 } 2075 2076 SetTcpNoDelay(bool tcpNoDelay)2077 void HttpServer::SetTcpNoDelay(bool tcpNoDelay) 2078 { 2079 Stop(); 2080 tcpNoDelay_ = tcpNoDelay; 2081 CLOG(INFO, HTTP) << "TCP_NODELAY for the HTTP sockets is set to " 2082 << (tcpNoDelay ? "true" : "false"); 2083 } 2084 IsTcpNoDelay() const2085 bool HttpServer::IsTcpNoDelay() const 2086 { 2087 return tcpNoDelay_; 2088 } 2089 2090 SetRequestTimeout(unsigned int seconds)2091 void HttpServer::SetRequestTimeout(unsigned int seconds) 2092 { 2093 if (seconds == 0) 2094 { 2095 throw OrthancException(ErrorCode_ParameterOutOfRange, 2096 "Request timeout must be a stricly positive integer"); 2097 } 2098 2099 Stop(); 2100 requestTimeout_ = seconds; 2101 CLOG(INFO, HTTP) << "Request timeout in the HTTP server is set to " << seconds << " seconds"; 2102 } 2103 GetRequestTimeout() const2104 unsigned int HttpServer::GetRequestTimeout() const 2105 { 2106 return requestTimeout_; 2107 } 2108 2109 2110 #if ORTHANC_ENABLE_PUGIXML == 1 GetWebDavBuckets()2111 HttpServer::WebDavBuckets& HttpServer::GetWebDavBuckets() 2112 { 2113 return webDavBuckets_; 2114 } 2115 #endif 2116 2117 2118 #if ORTHANC_ENABLE_PUGIXML == 1 Register(const std::vector<std::string> & root,IWebDavBucket * bucket)2119 void HttpServer::Register(const std::vector<std::string>& root, 2120 IWebDavBucket* bucket) 2121 { 2122 std::unique_ptr<IWebDavBucket> protection(bucket); 2123 2124 if (bucket == NULL) 2125 { 2126 throw OrthancException(ErrorCode_NullPointer); 2127 } 2128 2129 Stop(); 2130 2131 #if CIVETWEB_HAS_WEBDAV_WRITING == 0 2132 if (webDavBuckets_.size() == 0) 2133 { 2134 CLOG(WARNING, HTTP) << "Your version of the Orthanc framework was compiled " 2135 << "without support for writing into WebDAV collections"; 2136 } 2137 #endif 2138 2139 const std::string s = Toolbox::FlattenUri(root); 2140 2141 if (webDavBuckets_.find(s) != webDavBuckets_.end()) 2142 { 2143 throw OrthancException(ErrorCode_ParameterOutOfRange, 2144 "Cannot register two WebDAV buckets at the same root: " + s); 2145 } 2146 else 2147 { 2148 CLOG(INFO, HTTP) << "Branching WebDAV bucket at: " << s; 2149 webDavBuckets_[s] = protection.release(); 2150 } 2151 } 2152 #endif 2153 } 2154