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