comparison Core/HttpServer/HttpServer.cpp @ 3160:fc9a4a2dad63

merge
author Alain Mazy <alain@mazy.be>
date Thu, 24 Jan 2019 10:55:19 +0100
parents 6e8822be2f08
children 6d558598d713
comparison
equal deleted inserted replaced
3159:4cfed5c2eacd 3160:fc9a4a2dad63
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-2019 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 General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34 // http://en.highscore.de/cpp/boost/stringhandling.html
35
36 #include "../PrecompiledHeaders.h"
37 #include "HttpServer.h"
38
39 #include "../Logging.h"
40 #include "../ChunkedBuffer.h"
41 #include "../OrthancException.h"
42 #include "HttpToolbox.h"
43
44 #if ORTHANC_ENABLE_MONGOOSE == 1
45 # include <mongoose.h>
46
47 #elif ORTHANC_ENABLE_CIVETWEB == 1
48 # include <civetweb.h>
49 # define MONGOOSE_USE_CALLBACKS 1
50
51 #else
52 # error "Either Mongoose or Civetweb must be enabled to compile this file"
53 #endif
54
55 #include <algorithm>
56 #include <string.h>
57 #include <boost/lexical_cast.hpp>
58 #include <boost/algorithm/string.hpp>
59 #include <iostream>
60 #include <string.h>
61 #include <stdio.h>
62 #include <boost/thread.hpp>
63
64 #if !defined(ORTHANC_ENABLE_SSL)
65 # error The macro ORTHANC_ENABLE_SSL must be defined
66 #endif
67
68 #if ORTHANC_ENABLE_SSL == 1
69 #include <openssl/opensslv.h>
70 #endif
71
72 #define ORTHANC_REALM "Orthanc Secure Area"
73
74
75 namespace Orthanc
76 {
77 static const char multipart[] = "multipart/form-data; boundary=";
78 static unsigned int multipartLength = sizeof(multipart) / sizeof(char) - 1;
79
80
81 namespace
82 {
83 // Anonymous namespace to avoid clashes between compilation modules
84 class MongooseOutputStream : public IHttpOutputStream
85 {
86 private:
87 struct mg_connection* connection_;
88
89 public:
90 MongooseOutputStream(struct mg_connection* connection) : connection_(connection)
91 {
92 }
93
94 virtual void Send(bool isHeader, const void* buffer, size_t length)
95 {
96 if (length > 0)
97 {
98 int status = mg_write(connection_, buffer, length);
99 if (status != static_cast<int>(length))
100 {
101 // status == 0 when the connection has been closed, -1 on error
102 throw OrthancException(ErrorCode_NetworkProtocol);
103 }
104 }
105 }
106
107 virtual void OnHttpStatusReceived(HttpStatus status)
108 {
109 // Ignore this
110 }
111
112 virtual void DisableKeepAlive()
113 {
114 #if ORTHANC_ENABLE_MONGOOSE == 1
115 throw OrthancException(ErrorCode_NotImplemented,
116 "Only available if using CivetWeb");
117 #elif ORTHANC_ENABLE_CIVETWEB == 1
118 mg_disable_keep_alive(connection_);
119 #endif
120 }
121 };
122
123
124 enum PostDataStatus
125 {
126 PostDataStatus_Success,
127 PostDataStatus_NoLength,
128 PostDataStatus_Pending,
129 PostDataStatus_Failure
130 };
131 }
132
133
134 // TODO Move this to external file
135
136
137 class ChunkedFile : public ChunkedBuffer
138 {
139 private:
140 std::string filename_;
141
142 public:
143 ChunkedFile(const std::string& filename) :
144 filename_(filename)
145 {
146 }
147
148 const std::string& GetFilename() const
149 {
150 return filename_;
151 }
152 };
153
154
155
156 class ChunkStore
157 {
158 private:
159 typedef std::list<ChunkedFile*> Content;
160 Content content_;
161 unsigned int numPlaces_;
162
163 boost::mutex mutex_;
164 std::set<std::string> discardedFiles_;
165
166 void Clear()
167 {
168 for (Content::iterator it = content_.begin();
169 it != content_.end(); ++it)
170 {
171 delete *it;
172 }
173 }
174
175 Content::iterator Find(const std::string& filename)
176 {
177 for (Content::iterator it = content_.begin();
178 it != content_.end(); ++it)
179 {
180 if ((*it)->GetFilename() == filename)
181 {
182 return it;
183 }
184 }
185
186 return content_.end();
187 }
188
189 void Remove(const std::string& filename)
190 {
191 Content::iterator it = Find(filename);
192 if (it != content_.end())
193 {
194 delete *it;
195 content_.erase(it);
196 }
197 }
198
199 public:
200 ChunkStore()
201 {
202 numPlaces_ = 10;
203 }
204
205 ~ChunkStore()
206 {
207 Clear();
208 }
209
210 PostDataStatus Store(std::string& completed,
211 const char* chunkData,
212 size_t chunkSize,
213 const std::string& filename,
214 size_t filesize)
215 {
216 boost::mutex::scoped_lock lock(mutex_);
217
218 std::set<std::string>::iterator wasDiscarded = discardedFiles_.find(filename);
219 if (wasDiscarded != discardedFiles_.end())
220 {
221 discardedFiles_.erase(wasDiscarded);
222 return PostDataStatus_Failure;
223 }
224
225 ChunkedFile* f;
226 Content::iterator it = Find(filename);
227 if (it == content_.end())
228 {
229 f = new ChunkedFile(filename);
230
231 // Make some room
232 if (content_.size() >= numPlaces_)
233 {
234 discardedFiles_.insert(content_.front()->GetFilename());
235 delete content_.front();
236 content_.pop_front();
237 }
238
239 content_.push_back(f);
240 }
241 else
242 {
243 f = *it;
244 }
245
246 f->AddChunk(chunkData, chunkSize);
247
248 if (f->GetNumBytes() > filesize)
249 {
250 Remove(filename);
251 }
252 else if (f->GetNumBytes() == filesize)
253 {
254 f->Flatten(completed);
255 Remove(filename);
256 return PostDataStatus_Success;
257 }
258
259 return PostDataStatus_Pending;
260 }
261
262 /*void Print()
263 {
264 boost::mutex::scoped_lock lock(mutex_);
265
266 printf("ChunkStore status:\n");
267 for (Content::const_iterator i = content_.begin();
268 i != content_.end(); i++)
269 {
270 printf(" [%s]: %d\n", (*i)->GetFilename().c_str(), (*i)->GetNumBytes());
271 }
272 printf("-----\n");
273 }*/
274 };
275
276
277 struct HttpServer::PImpl
278 {
279 struct mg_context *context_;
280 ChunkStore chunkStore_;
281 };
282
283
284 ChunkStore& HttpServer::GetChunkStore()
285 {
286 return pimpl_->chunkStore_;
287 }
288
289
290
291 static PostDataStatus ReadBody(std::string& postData,
292 struct mg_connection *connection,
293 const IHttpHandler::Arguments& headers)
294 {
295 IHttpHandler::Arguments::const_iterator cs = headers.find("content-length");
296 if (cs == headers.end())
297 {
298 return PostDataStatus_NoLength;
299 }
300
301 int length;
302 try
303 {
304 length = boost::lexical_cast<int>(cs->second);
305 }
306 catch (boost::bad_lexical_cast&)
307 {
308 return PostDataStatus_NoLength;
309 }
310
311 if (length < 0)
312 {
313 length = 0;
314 }
315
316 postData.resize(length);
317
318 size_t pos = 0;
319 while (length > 0)
320 {
321 int r = mg_read(connection, &postData[pos], length);
322 if (r <= 0)
323 {
324 return PostDataStatus_Failure;
325 }
326
327 assert(r <= length);
328 length -= r;
329 pos += r;
330 }
331
332 return PostDataStatus_Success;
333 }
334
335
336
337 static PostDataStatus ParseMultipartPost(std::string &completedFile,
338 struct mg_connection *connection,
339 const IHttpHandler::Arguments& headers,
340 const std::string& contentType,
341 ChunkStore& chunkStore)
342 {
343 std::string boundary = "--" + contentType.substr(multipartLength);
344
345 std::string postData;
346 PostDataStatus status = ReadBody(postData, connection, headers);
347
348 if (status != PostDataStatus_Success)
349 {
350 return status;
351 }
352
353 /*for (IHttpHandler::Arguments::const_iterator i = headers.begin(); i != headers.end(); i++)
354 {
355 std::cout << "Header [" << i->first << "] = " << i->second << "\n";
356 }
357 printf("CHUNK\n");*/
358
359 typedef IHttpHandler::Arguments::const_iterator ArgumentIterator;
360
361 ArgumentIterator requestedWith = headers.find("x-requested-with");
362 ArgumentIterator fileName = headers.find("x-file-name");
363 ArgumentIterator fileSizeStr = headers.find("x-file-size");
364
365 if (requestedWith != headers.end() &&
366 requestedWith->second != "XMLHttpRequest")
367 {
368 return PostDataStatus_Failure;
369 }
370
371 size_t fileSize = 0;
372 if (fileSizeStr != headers.end())
373 {
374 try
375 {
376 fileSize = boost::lexical_cast<size_t>(fileSizeStr->second);
377 }
378 catch (boost::bad_lexical_cast&)
379 {
380 return PostDataStatus_Failure;
381 }
382 }
383
384 typedef boost::find_iterator<std::string::iterator> FindIterator;
385 typedef boost::iterator_range<char*> Range;
386
387 //chunkStore.Print();
388
389 try
390 {
391 FindIterator last;
392 for (FindIterator it =
393 make_find_iterator(postData, boost::first_finder(boundary));
394 it!=FindIterator();
395 ++it)
396 {
397 if (last != FindIterator())
398 {
399 Range part(&last->back(), &it->front());
400 Range content = boost::find_first(part, "\r\n\r\n");
401 if (/*content != Range()*/!content.empty())
402 {
403 Range c(&content.back() + 1, &it->front() - 2);
404 size_t chunkSize = c.size();
405
406 if (chunkSize > 0)
407 {
408 const char* chunkData = &c.front();
409
410 if (fileName == headers.end())
411 {
412 // This file is stored in a single chunk
413 completedFile.resize(chunkSize);
414 if (chunkSize > 0)
415 {
416 memcpy(&completedFile[0], chunkData, chunkSize);
417 }
418 return PostDataStatus_Success;
419 }
420 else
421 {
422 return chunkStore.Store(completedFile, chunkData, chunkSize, fileName->second, fileSize);
423 }
424 }
425 }
426 }
427
428 last = it;
429 }
430 }
431 catch (std::length_error&)
432 {
433 return PostDataStatus_Failure;
434 }
435
436 return PostDataStatus_Pending;
437 }
438
439
440 static bool IsAccessGranted(const HttpServer& that,
441 const IHttpHandler::Arguments& headers)
442 {
443 bool granted = false;
444
445 IHttpHandler::Arguments::const_iterator auth = headers.find("authorization");
446 if (auth != headers.end())
447 {
448 std::string s = auth->second;
449 if (s.size() > 6 &&
450 s.substr(0, 6) == "Basic ")
451 {
452 std::string b64 = s.substr(6);
453 granted = that.IsValidBasicHttpAuthentication(b64);
454 }
455 }
456
457 return granted;
458 }
459
460
461 static std::string GetAuthenticatedUsername(const IHttpHandler::Arguments& headers)
462 {
463 IHttpHandler::Arguments::const_iterator auth = headers.find("authorization");
464
465 if (auth == headers.end())
466 {
467 return "";
468 }
469
470 std::string s = auth->second;
471 if (s.size() <= 6 ||
472 s.substr(0, 6) != "Basic ")
473 {
474 return "";
475 }
476
477 std::string b64 = s.substr(6);
478 std::string decoded;
479 Toolbox::DecodeBase64(decoded, b64);
480 size_t semicolons = decoded.find(':');
481
482 if (semicolons == std::string::npos)
483 {
484 // Bad-formatted request
485 return "";
486 }
487 else
488 {
489 return decoded.substr(0, semicolons);
490 }
491 }
492
493
494 static bool ExtractMethod(HttpMethod& method,
495 const struct mg_request_info *request,
496 const IHttpHandler::Arguments& headers,
497 const IHttpHandler::GetArguments& argumentsGET)
498 {
499 std::string overriden;
500
501 // Check whether some PUT/DELETE faking is done
502
503 // 1. Faking with Google's approach
504 IHttpHandler::Arguments::const_iterator methodOverride =
505 headers.find("x-http-method-override");
506
507 if (methodOverride != headers.end())
508 {
509 overriden = methodOverride->second;
510 }
511 else if (!strcmp(request->request_method, "GET"))
512 {
513 // 2. Faking with Ruby on Rail's approach
514 // GET /my/resource?_method=delete <=> DELETE /my/resource
515 for (size_t i = 0; i < argumentsGET.size(); i++)
516 {
517 if (argumentsGET[i].first == "_method")
518 {
519 overriden = argumentsGET[i].second;
520 break;
521 }
522 }
523 }
524
525 if (overriden.size() > 0)
526 {
527 // A faking has been done within this request
528 Toolbox::ToUpperCase(overriden);
529
530 LOG(INFO) << "HTTP method faking has been detected for " << overriden;
531
532 if (overriden == "PUT")
533 {
534 method = HttpMethod_Put;
535 return true;
536 }
537 else if (overriden == "DELETE")
538 {
539 method = HttpMethod_Delete;
540 return true;
541 }
542 else
543 {
544 return false;
545 }
546 }
547
548 // No PUT/DELETE faking was present
549 if (!strcmp(request->request_method, "GET"))
550 {
551 method = HttpMethod_Get;
552 }
553 else if (!strcmp(request->request_method, "POST"))
554 {
555 method = HttpMethod_Post;
556 }
557 else if (!strcmp(request->request_method, "DELETE"))
558 {
559 method = HttpMethod_Delete;
560 }
561 else if (!strcmp(request->request_method, "PUT"))
562 {
563 method = HttpMethod_Put;
564 }
565 else
566 {
567 return false;
568 }
569
570 return true;
571 }
572
573
574 static void ConfigureHttpCompression(HttpOutput& output,
575 const IHttpHandler::Arguments& headers)
576 {
577 // Look if the client wishes HTTP compression
578 // https://en.wikipedia.org/wiki/HTTP_compression
579 IHttpHandler::Arguments::const_iterator it = headers.find("accept-encoding");
580 if (it != headers.end())
581 {
582 std::vector<std::string> encodings;
583 Toolbox::TokenizeString(encodings, it->second, ',');
584
585 for (size_t i = 0; i < encodings.size(); i++)
586 {
587 std::string s = Toolbox::StripSpaces(encodings[i]);
588
589 if (s == "deflate")
590 {
591 output.SetDeflateAllowed(true);
592 }
593 else if (s == "gzip")
594 {
595 output.SetGzipAllowed(true);
596 }
597 }
598 }
599 }
600
601
602 static void InternalCallback(HttpOutput& output /* out */,
603 HttpMethod& method /* out */,
604 HttpServer& server,
605 struct mg_connection *connection,
606 const struct mg_request_info *request)
607 {
608 bool localhost;
609
610 #if ORTHANC_ENABLE_MONGOOSE == 1
611 static const long LOCALHOST = (127ll << 24) + 1ll;
612 localhost = (request->remote_ip == LOCALHOST);
613 #elif ORTHANC_ENABLE_CIVETWEB == 1
614 // The "remote_ip" field of "struct mg_request_info" is tagged as
615 // deprecated in Civetweb, using "remote_addr" instead.
616 localhost = (std::string(request->remote_addr) == "127.0.0.1");
617 #else
618 # error
619 #endif
620
621 // Check remote calls
622 if (!server.IsRemoteAccessAllowed() &&
623 !localhost)
624 {
625 output.SendUnauthorized(server.GetRealm());
626 return;
627 }
628
629
630 // Extract the HTTP headers
631 IHttpHandler::Arguments headers;
632 for (int i = 0; i < request->num_headers; i++)
633 {
634 std::string name = request->http_headers[i].name;
635 std::string value = request->http_headers[i].value;
636
637 std::transform(name.begin(), name.end(), name.begin(), ::tolower);
638 headers.insert(std::make_pair(name, value));
639 VLOG(1) << "HTTP header: [" << name << "]: [" << value << "]";
640 }
641
642 if (server.IsHttpCompressionEnabled())
643 {
644 ConfigureHttpCompression(output, headers);
645 }
646
647
648 // Extract the GET arguments
649 IHttpHandler::GetArguments argumentsGET;
650 if (!strcmp(request->request_method, "GET"))
651 {
652 HttpToolbox::ParseGetArguments(argumentsGET, request->query_string);
653 }
654
655
656 // Compute the HTTP method, taking method faking into consideration
657 method = HttpMethod_Get;
658 if (!ExtractMethod(method, request, headers, argumentsGET))
659 {
660 output.SendStatus(HttpStatus_400_BadRequest);
661 return;
662 }
663
664
665 // Authenticate this connection
666 if (server.IsAuthenticationEnabled() &&
667 !IsAccessGranted(server, headers))
668 {
669 output.SendUnauthorized(server.GetRealm());
670 return;
671 }
672
673
674 #if ORTHANC_ENABLE_MONGOOSE == 1
675 // Apply the filter, if it is installed
676 char remoteIp[24];
677 sprintf(remoteIp, "%d.%d.%d.%d",
678 reinterpret_cast<const uint8_t*>(&request->remote_ip) [3],
679 reinterpret_cast<const uint8_t*>(&request->remote_ip) [2],
680 reinterpret_cast<const uint8_t*>(&request->remote_ip) [1],
681 reinterpret_cast<const uint8_t*>(&request->remote_ip) [0]);
682
683 const char* requestUri = request->uri;
684
685 #elif ORTHANC_ENABLE_CIVETWEB == 1
686 const char* remoteIp = request->remote_addr;
687 const char* requestUri = request->local_uri;
688 #else
689 # error
690 #endif
691
692 if (requestUri == NULL)
693 {
694 requestUri = "";
695 }
696
697 std::string username = GetAuthenticatedUsername(headers);
698
699 IIncomingHttpRequestFilter *filter = server.GetIncomingHttpRequestFilter();
700 if (filter != NULL)
701 {
702 if (!filter->IsAllowed(method, requestUri, remoteIp,
703 username.c_str(), headers, argumentsGET))
704 {
705 //output.SendUnauthorized(server.GetRealm());
706 output.SendStatus(HttpStatus_403_Forbidden);
707 return;
708 }
709 }
710
711
712 // Extract the body of the request for PUT and POST
713
714 // TODO Avoid unneccessary memcopy of the body
715
716 std::string body;
717 if (method == HttpMethod_Post ||
718 method == HttpMethod_Put)
719 {
720 PostDataStatus status;
721
722 IHttpHandler::Arguments::const_iterator ct = headers.find("content-type");
723 if (ct == headers.end())
724 {
725 // No content-type specified. Assume no multi-part content occurs at this point.
726 status = ReadBody(body, connection, headers);
727 }
728 else
729 {
730 std::string contentType = ct->second;
731 if (contentType.size() >= multipartLength &&
732 !memcmp(contentType.c_str(), multipart, multipartLength))
733 {
734 status = ParseMultipartPost(body, connection, headers, contentType, server.GetChunkStore());
735 }
736 else
737 {
738 status = ReadBody(body, connection, headers);
739 }
740 }
741
742 switch (status)
743 {
744 case PostDataStatus_NoLength:
745 output.SendStatus(HttpStatus_411_LengthRequired);
746 return;
747
748 case PostDataStatus_Failure:
749 output.SendStatus(HttpStatus_400_BadRequest);
750 return;
751
752 case PostDataStatus_Pending:
753 output.AnswerEmpty();
754 return;
755
756 default:
757 break;
758 }
759 }
760
761
762 // Decompose the URI into its components
763 UriComponents uri;
764 try
765 {
766 Toolbox::SplitUriComponents(uri, requestUri);
767 }
768 catch (OrthancException&)
769 {
770 output.SendStatus(HttpStatus_400_BadRequest);
771 return;
772 }
773
774
775 LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri);
776
777 bool found = false;
778
779 if (server.HasHandler())
780 {
781 found = server.GetHandler().Handle(output, RequestOrigin_RestApi, remoteIp, username.c_str(),
782 method, uri, headers, argumentsGET, body.c_str(), body.size());
783 }
784
785 if (!found)
786 {
787 throw OrthancException(ErrorCode_UnknownResource);
788 }
789 }
790
791
792 static void ProtectedCallback(struct mg_connection *connection,
793 const struct mg_request_info *request)
794 {
795 try
796 {
797 #if ORTHANC_ENABLE_MONGOOSE == 1
798 void *that = request->user_data;
799 const char* requestUri = request->uri;
800 #elif ORTHANC_ENABLE_CIVETWEB == 1
801 // https://github.com/civetweb/civetweb/issues/409
802 void *that = mg_get_user_data(mg_get_context(connection));
803 const char* requestUri = request->local_uri;
804 #else
805 # error
806 #endif
807
808 if (requestUri == NULL)
809 {
810 requestUri = "";
811 }
812
813 HttpServer* server = reinterpret_cast<HttpServer*>(that);
814
815 if (server == NULL)
816 {
817 MongooseOutputStream stream(connection);
818 HttpOutput output(stream, false /* assume no keep-alive */);
819 output.SendStatus(HttpStatus_500_InternalServerError);
820 return;
821 }
822
823 MongooseOutputStream stream(connection);
824 HttpOutput output(stream, server->IsKeepAliveEnabled());
825 HttpMethod method = HttpMethod_Get;
826
827 try
828 {
829 try
830 {
831 InternalCallback(output, method, *server, connection, request);
832 }
833 catch (OrthancException&)
834 {
835 throw; // Pass the exception to the main handler below
836 }
837 // Now convert native exceptions as OrthancException
838 catch (boost::bad_lexical_cast&)
839 {
840 throw OrthancException(ErrorCode_BadParameterType,
841 "Syntax error in some user-supplied data");
842 }
843 catch (std::runtime_error&)
844 {
845 // Presumably an error while parsing the JSON body
846 throw OrthancException(ErrorCode_BadRequest);
847 }
848 catch (std::bad_alloc&)
849 {
850 throw OrthancException(ErrorCode_NotEnoughMemory,
851 "The server hosting Orthanc is running out of memory");
852 }
853 catch (...)
854 {
855 throw OrthancException(ErrorCode_InternalError,
856 "An unhandled exception was generated inside the HTTP server");
857 }
858 }
859 catch (OrthancException& e)
860 {
861 assert(server != NULL);
862
863 // Using this candidate handler results in an exception
864 try
865 {
866 if (server->GetExceptionFormatter() == NULL)
867 {
868 LOG(ERROR) << "Exception in the HTTP handler: " << e.What();
869 output.SendStatus(e.GetHttpStatus());
870 }
871 else
872 {
873 server->GetExceptionFormatter()->Format(output, e, method, requestUri);
874 }
875 }
876 catch (OrthancException&)
877 {
878 // An exception here reflects the fact that the status code
879 // was already set by the HTTP handler.
880 }
881 }
882 }
883 catch (...)
884 {
885 // We should never arrive at this point, where it is even impossible to send an answer
886 LOG(ERROR) << "Catastrophic error inside the HTTP server, giving up";
887 }
888 }
889
890
891 #if MONGOOSE_USE_CALLBACKS == 0
892 static void* Callback(enum mg_event event,
893 struct mg_connection *connection,
894 const struct mg_request_info *request)
895 {
896 if (event == MG_NEW_REQUEST)
897 {
898 ProtectedCallback(connection, request);
899
900 // Mark as processed
901 return (void*) "";
902 }
903 else
904 {
905 return NULL;
906 }
907 }
908
909 #elif MONGOOSE_USE_CALLBACKS == 1
910 static int Callback(struct mg_connection *connection)
911 {
912 const struct mg_request_info *request = mg_get_request_info(connection);
913
914 ProtectedCallback(connection, request);
915
916 return 1; // Do not let Mongoose handle the request by itself
917 }
918
919 #else
920 # error Please set MONGOOSE_USE_CALLBACKS
921 #endif
922
923
924
925
926
927 bool HttpServer::IsRunning() const
928 {
929 return (pimpl_->context_ != NULL);
930 }
931
932
933 HttpServer::HttpServer() : pimpl_(new PImpl)
934 {
935 pimpl_->context_ = NULL;
936 handler_ = NULL;
937 remoteAllowed_ = false;
938 authentication_ = false;
939 ssl_ = false;
940 port_ = 8000;
941 filter_ = NULL;
942 keepAlive_ = false;
943 httpCompression_ = true;
944 exceptionFormatter_ = NULL;
945 realm_ = ORTHANC_REALM;
946 threadsCount_ = 50; // Default value in mongoose
947 tcpNoDelay_ = true;
948
949 #if ORTHANC_ENABLE_MONGOOSE == 1
950 LOG(INFO) << "This Orthanc server uses Mongoose as its embedded HTTP server";
951 #endif
952
953 #if ORTHANC_ENABLE_CIVETWEB == 1
954 LOG(INFO) << "This Orthanc server uses CivetWeb as its embedded HTTP server";
955 #endif
956
957 #if ORTHANC_ENABLE_SSL == 1
958 // Check for the Heartbleed exploit
959 // https://en.wikipedia.org/wiki/OpenSSL#Heartbleed_bug
960 if (OPENSSL_VERSION_NUMBER < 0x1000107fL /* openssl-1.0.1g */ &&
961 OPENSSL_VERSION_NUMBER >= 0x1000100fL /* openssl-1.0.1 */)
962 {
963 LOG(WARNING) << "This version of OpenSSL is vulnerable to the Heartbleed exploit";
964 }
965 #endif
966 }
967
968
969 HttpServer::~HttpServer()
970 {
971 Stop();
972 }
973
974
975 void HttpServer::SetPortNumber(uint16_t port)
976 {
977 Stop();
978 port_ = port;
979 }
980
981 void HttpServer::Start()
982 {
983 #if ORTHANC_ENABLE_MONGOOSE == 1
984 LOG(INFO) << "Starting embedded Web server using Mongoose";
985 #elif ORTHANC_ENABLE_CIVETWEB == 1
986 LOG(INFO) << "Starting embedded Web server using Civetweb";
987 #else
988 # error
989 #endif
990
991 if (!IsRunning())
992 {
993 std::string port = boost::lexical_cast<std::string>(port_);
994 std::string numThreads = boost::lexical_cast<std::string>(threadsCount_);
995
996 if (ssl_)
997 {
998 port += "s";
999 }
1000
1001 const char *options[] = {
1002 // Set the TCP port for the HTTP server
1003 "listening_ports", port.c_str(),
1004
1005 // Optimization reported by Chris Hafey
1006 // https://groups.google.com/d/msg/orthanc-users/CKueKX0pJ9E/_UCbl8T-VjIJ
1007 "enable_keep_alive", (keepAlive_ ? "yes" : "no"),
1008
1009 #if ORTHANC_ENABLE_CIVETWEB == 1
1010 // https://github.com/civetweb/civetweb/blob/master/docs/UserManual.md#enable_keep_alive-no
1011 "keep_alive_timeout_ms", (keepAlive_ ? "500" : "0"),
1012 #endif
1013
1014 #if ORTHANC_ENABLE_CIVETWEB == 1
1015 // Disable TCP Nagle's algorithm to maximize speed (this
1016 // option is not available in Mongoose).
1017 // https://groups.google.com/d/topic/civetweb/35HBR9seFjU/discussion
1018 // https://eklitzke.org/the-caveats-of-tcp-nodelay
1019 "tcp_nodelay", (tcpNoDelay_ ? "1" : "0"),
1020 #endif
1021
1022 // Set the number of threads
1023 "num_threads", numThreads.c_str(),
1024
1025 // Set the SSL certificate, if any. This must be the last option.
1026 ssl_ ? "ssl_certificate" : NULL,
1027 certificate_.c_str(),
1028 NULL
1029 };
1030
1031 #if MONGOOSE_USE_CALLBACKS == 0
1032 pimpl_->context_ = mg_start(&Callback, this, options);
1033
1034 #elif MONGOOSE_USE_CALLBACKS == 1
1035 struct mg_callbacks callbacks;
1036 memset(&callbacks, 0, sizeof(callbacks));
1037 callbacks.begin_request = Callback;
1038 pimpl_->context_ = mg_start(&callbacks, this, options);
1039
1040 #else
1041 # error Please set MONGOOSE_USE_CALLBACKS
1042 #endif
1043
1044 if (!pimpl_->context_)
1045 {
1046 throw OrthancException(ErrorCode_HttpPortInUse);
1047 }
1048
1049 LOG(WARNING) << "HTTP server listening on port: " << GetPortNumber()
1050 << " (HTTPS encryption is "
1051 << (IsSslEnabled() ? "enabled" : "disabled")
1052 << ", remote access is "
1053 << (IsRemoteAccessAllowed() ? "" : "not ")
1054 << "allowed)";
1055 }
1056 }
1057
1058 void HttpServer::Stop()
1059 {
1060 if (IsRunning())
1061 {
1062 mg_stop(pimpl_->context_);
1063 pimpl_->context_ = NULL;
1064 }
1065 }
1066
1067
1068 void HttpServer::ClearUsers()
1069 {
1070 Stop();
1071 registeredUsers_.clear();
1072 }
1073
1074
1075 void HttpServer::RegisterUser(const char* username,
1076 const char* password)
1077 {
1078 Stop();
1079
1080 std::string tag = std::string(username) + ":" + std::string(password);
1081 std::string encoded;
1082 Toolbox::EncodeBase64(encoded, tag);
1083 registeredUsers_.insert(encoded);
1084 }
1085
1086 void HttpServer::SetSslEnabled(bool enabled)
1087 {
1088 Stop();
1089
1090 #if ORTHANC_ENABLE_SSL == 0
1091 if (enabled)
1092 {
1093 throw OrthancException(ErrorCode_SslDisabled);
1094 }
1095 else
1096 {
1097 ssl_ = false;
1098 }
1099 #else
1100 ssl_ = enabled;
1101 #endif
1102 }
1103
1104
1105 void HttpServer::SetKeepAliveEnabled(bool enabled)
1106 {
1107 Stop();
1108 keepAlive_ = enabled;
1109 LOG(INFO) << "HTTP keep alive is " << (enabled ? "enabled" : "disabled");
1110
1111 #if ORTHANC_ENABLE_MONGOOSE == 1
1112 if (enabled)
1113 {
1114 LOG(WARNING) << "You should disable HTTP keep alive, as you are using Mongoose";
1115 }
1116 #endif
1117 }
1118
1119
1120 void HttpServer::SetAuthenticationEnabled(bool enabled)
1121 {
1122 Stop();
1123 authentication_ = enabled;
1124 }
1125
1126 void HttpServer::SetSslCertificate(const char* path)
1127 {
1128 Stop();
1129 certificate_ = path;
1130 }
1131
1132 void HttpServer::SetRemoteAccessAllowed(bool allowed)
1133 {
1134 Stop();
1135 remoteAllowed_ = allowed;
1136 }
1137
1138 void HttpServer::SetHttpCompressionEnabled(bool enabled)
1139 {
1140 Stop();
1141 httpCompression_ = enabled;
1142 LOG(WARNING) << "HTTP compression is " << (enabled ? "enabled" : "disabled");
1143 }
1144
1145 void HttpServer::SetIncomingHttpRequestFilter(IIncomingHttpRequestFilter& filter)
1146 {
1147 Stop();
1148 filter_ = &filter;
1149 }
1150
1151
1152 void HttpServer::SetHttpExceptionFormatter(IHttpExceptionFormatter& formatter)
1153 {
1154 Stop();
1155 exceptionFormatter_ = &formatter;
1156 }
1157
1158
1159 bool HttpServer::IsValidBasicHttpAuthentication(const std::string& basic) const
1160 {
1161 return registeredUsers_.find(basic) != registeredUsers_.end();
1162 }
1163
1164
1165 void HttpServer::Register(IHttpHandler& handler)
1166 {
1167 Stop();
1168 handler_ = &handler;
1169 }
1170
1171
1172 IHttpHandler& HttpServer::GetHandler() const
1173 {
1174 if (handler_ == NULL)
1175 {
1176 throw OrthancException(ErrorCode_InternalError);
1177 }
1178
1179 return *handler_;
1180 }
1181
1182
1183 void HttpServer::SetThreadsCount(unsigned int threads)
1184 {
1185 if (threads <= 0)
1186 {
1187 throw OrthancException(ErrorCode_ParameterOutOfRange);
1188 }
1189
1190 Stop();
1191 threadsCount_ = threads;
1192 }
1193
1194
1195 void HttpServer::SetTcpNoDelay(bool tcpNoDelay)
1196 {
1197 Stop();
1198 tcpNoDelay_ = tcpNoDelay;
1199 LOG(INFO) << "TCP_NODELAY for the HTTP sockets is set to "
1200 << (tcpNoDelay ? "true" : "false");
1201 }
1202 }