Mercurial > hg > orthanc
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 } |