comparison Core/HttpServer/HttpServer.cpp @ 3138:ab46e537f92e

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