1 /** 2 * Orthanc - A Lightweight, RESTful DICOM Store 3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics 4 * Department, University Hospital of Liege, Belgium 5 * Copyright (C) 2017-2021 Osimis S.A., Belgium 6 * 7 * This program is free software: you can redistribute it and/or 8 * modify it under the terms of the GNU Lesser General Public License 9 * as published by the Free Software Foundation, either version 3 of 10 * the License, or (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, but 13 * WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Lesser General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public 18 * License along with this program. If not, see 19 * <http://www.gnu.org/licenses/>. 20 **/ 21 22 23 #include "../PrecompiledHeaders.h" 24 #include "HttpOutput.h" 25 26 #include "../ChunkedBuffer.h" 27 #include "../Compression/GzipCompressor.h" 28 #include "../Compression/ZlibCompressor.h" 29 #include "../Logging.h" 30 #include "../OrthancException.h" 31 #include "../Toolbox.h" 32 33 #include <iostream> 34 #include <vector> 35 #include <stdio.h> 36 #include <boost/lexical_cast.hpp> 37 38 39 #if ORTHANC_ENABLE_CIVETWEB == 1 40 # if !defined(CIVETWEB_HAS_DISABLE_KEEP_ALIVE) 41 # error Macro CIVETWEB_HAS_DISABLE_KEEP_ALIVE must be defined 42 # endif 43 #endif 44 45 46 namespace Orthanc 47 { StateMachine(IHttpOutputStream & stream,bool isKeepAlive)48 HttpOutput::StateMachine::StateMachine(IHttpOutputStream& stream, 49 bool isKeepAlive) : 50 stream_(stream), 51 state_(State_WritingHeader), 52 status_(HttpStatus_200_Ok), 53 hasContentLength_(false), 54 contentLength_(0), 55 contentPosition_(0), 56 keepAlive_(isKeepAlive) 57 { 58 } 59 ~StateMachine()60 HttpOutput::StateMachine::~StateMachine() 61 { 62 if (state_ != State_Done) 63 { 64 //asm volatile ("int3;"); 65 //LOG(ERROR) << "This HTTP answer does not contain any body"; 66 } 67 68 if (hasContentLength_ && contentPosition_ != contentLength_) 69 { 70 LOG(ERROR) << "This HTTP answer has not sent the proper number of bytes in its body"; 71 } 72 } 73 74 SetHttpStatus(HttpStatus status)75 void HttpOutput::StateMachine::SetHttpStatus(HttpStatus status) 76 { 77 if (state_ != State_WritingHeader) 78 { 79 throw OrthancException(ErrorCode_BadSequenceOfCalls); 80 } 81 82 status_ = status; 83 } 84 85 SetContentLength(uint64_t length)86 void HttpOutput::StateMachine::SetContentLength(uint64_t length) 87 { 88 if (state_ != State_WritingHeader) 89 { 90 throw OrthancException(ErrorCode_BadSequenceOfCalls); 91 } 92 93 hasContentLength_ = true; 94 contentLength_ = length; 95 } 96 SetContentType(const char * contentType)97 void HttpOutput::StateMachine::SetContentType(const char* contentType) 98 { 99 AddHeader("Content-Type", contentType); 100 } 101 SetContentFilename(const char * filename)102 void HttpOutput::StateMachine::SetContentFilename(const char* filename) 103 { 104 // TODO Escape double quotes 105 AddHeader("Content-Disposition", "filename=\"" + std::string(filename) + "\""); 106 } 107 SetCookie(const std::string & cookie,const std::string & value)108 void HttpOutput::StateMachine::SetCookie(const std::string& cookie, 109 const std::string& value) 110 { 111 if (state_ != State_WritingHeader) 112 { 113 throw OrthancException(ErrorCode_BadSequenceOfCalls); 114 } 115 116 // TODO Escape "=" characters 117 AddHeader("Set-Cookie", cookie + "=" + value); 118 } 119 120 AddHeader(const std::string & header,const std::string & value)121 void HttpOutput::StateMachine::AddHeader(const std::string& header, 122 const std::string& value) 123 { 124 if (state_ != State_WritingHeader) 125 { 126 throw OrthancException(ErrorCode_BadSequenceOfCalls); 127 } 128 129 headers_.push_back(header + ": " + value + "\r\n"); 130 } 131 ClearHeaders()132 void HttpOutput::StateMachine::ClearHeaders() 133 { 134 if (state_ != State_WritingHeader) 135 { 136 throw OrthancException(ErrorCode_BadSequenceOfCalls); 137 } 138 139 headers_.clear(); 140 } 141 SendBody(const void * buffer,size_t length)142 void HttpOutput::StateMachine::SendBody(const void* buffer, size_t length) 143 { 144 if (state_ == State_Done) 145 { 146 if (length == 0) 147 { 148 return; 149 } 150 else 151 { 152 throw OrthancException(ErrorCode_BadSequenceOfCalls, 153 "Because of keep-alive connections, the entire body must " 154 "be sent at once or Content-Length must be given"); 155 } 156 } 157 158 if (state_ == State_WritingMultipart) 159 { 160 throw OrthancException(ErrorCode_InternalError); 161 } 162 163 if (state_ == State_WritingHeader) 164 { 165 // Send the HTTP header before writing the body 166 167 stream_.OnHttpStatusReceived(status_); 168 169 std::string s = "HTTP/1.1 " + 170 boost::lexical_cast<std::string>(status_) + 171 " " + std::string(EnumerationToString(status_)) + 172 "\r\n"; 173 174 if (keepAlive_) 175 { 176 s += "Connection: keep-alive\r\n"; 177 178 /** 179 * [LIFY-2311] The "Keep-Alive" HTTP header was missing in 180 * Orthanc <= 1.8.0, which notably caused failures if 181 * uploading DICOM instances by applying Java's 182 * "org.apache.http.client.methods.HttpPost()" on "/instances" 183 * URI, if "PoolingHttpClientConnectionManager" was in used. A 184 * workaround was to manually set a timeout for the keep-alive 185 * client to, say, 200 milliseconds, by using 186 * "HttpClients.custom().setKeepAliveStrategy((httpResponse,httpContext)->200)". 187 * Note that the "timeout" value can only be integer in the 188 * HTTP header, so we can't use the milliseconds granularity. 189 **/ 190 s += ("Keep-Alive: timeout=" + 191 boost::lexical_cast<std::string>(CIVETWEB_KEEP_ALIVE_TIMEOUT_SECONDS) + "\r\n"); 192 } 193 else 194 { 195 s += "Connection: close\r\n"; 196 } 197 198 for (std::list<std::string>::const_iterator 199 it = headers_.begin(); it != headers_.end(); ++it) 200 { 201 s += *it; 202 } 203 204 if (status_ != HttpStatus_200_Ok) 205 { 206 hasContentLength_ = false; 207 } 208 209 uint64_t contentLength = (hasContentLength_ ? contentLength_ : length); 210 s += "Content-Length: " + boost::lexical_cast<std::string>(contentLength) + "\r\n\r\n"; 211 212 stream_.Send(true, s.c_str(), s.size()); 213 state_ = State_WritingBody; 214 } 215 216 if (hasContentLength_ && 217 contentPosition_ + length > contentLength_) 218 { 219 throw OrthancException(ErrorCode_BadSequenceOfCalls, 220 "The body size exceeds what was declared with SetContentSize()"); 221 } 222 223 if (length > 0) 224 { 225 stream_.Send(false, buffer, length); 226 contentPosition_ += length; 227 } 228 229 if (!hasContentLength_ || 230 contentPosition_ == contentLength_) 231 { 232 state_ = State_Done; 233 } 234 } 235 236 CloseBody()237 void HttpOutput::StateMachine::CloseBody() 238 { 239 switch (state_) 240 { 241 case State_WritingHeader: 242 SetContentLength(0); 243 SendBody(NULL, 0); 244 break; 245 246 case State_WritingBody: 247 if (!hasContentLength_ || 248 contentPosition_ == contentLength_) 249 { 250 state_ = State_Done; 251 } 252 else 253 { 254 throw OrthancException(ErrorCode_BadSequenceOfCalls, 255 "The body size has not reached what was declared with SetContentSize()"); 256 } 257 258 break; 259 260 case State_WritingMultipart: 261 throw OrthancException(ErrorCode_BadSequenceOfCalls, 262 "Cannot invoke CloseBody() with multipart outputs"); 263 264 case State_Done: 265 return; // Ignore 266 267 default: 268 throw OrthancException(ErrorCode_InternalError); 269 } 270 } 271 272 GetPreferredCompression(size_t bodySize) const273 HttpCompression HttpOutput::GetPreferredCompression(size_t bodySize) const 274 { 275 #if 0 276 // TODO Do not compress small files? 277 if (bodySize < 512) 278 { 279 return HttpCompression_None; 280 } 281 #endif 282 283 // Prefer "gzip" over "deflate" if the choice is offered 284 285 if (isGzipAllowed_) 286 { 287 return HttpCompression_Gzip; 288 } 289 else if (isDeflateAllowed_) 290 { 291 return HttpCompression_Deflate; 292 } 293 else 294 { 295 return HttpCompression_None; 296 } 297 } 298 299 HttpOutput(IHttpOutputStream & stream,bool isKeepAlive)300 HttpOutput::HttpOutput(IHttpOutputStream &stream, 301 bool isKeepAlive) : 302 stateMachine_(stream, isKeepAlive), 303 isDeflateAllowed_(false), 304 isGzipAllowed_(false) 305 { 306 } 307 SetDeflateAllowed(bool allowed)308 void HttpOutput::SetDeflateAllowed(bool allowed) 309 { 310 isDeflateAllowed_ = allowed; 311 } 312 IsDeflateAllowed() const313 bool HttpOutput::IsDeflateAllowed() const 314 { 315 return isDeflateAllowed_; 316 } 317 SetGzipAllowed(bool allowed)318 void HttpOutput::SetGzipAllowed(bool allowed) 319 { 320 isGzipAllowed_ = allowed; 321 } 322 IsGzipAllowed() const323 bool HttpOutput::IsGzipAllowed() const 324 { 325 return isGzipAllowed_; 326 } 327 328 SendMethodNotAllowed(const std::string & allowed)329 void HttpOutput::SendMethodNotAllowed(const std::string& allowed) 330 { 331 stateMachine_.ClearHeaders(); 332 stateMachine_.SetHttpStatus(HttpStatus_405_MethodNotAllowed); 333 stateMachine_.AddHeader("Allow", allowed); 334 stateMachine_.SendBody(NULL, 0); 335 } 336 337 SendStatus(HttpStatus status,const char * message,size_t messageSize)338 void HttpOutput::SendStatus(HttpStatus status, 339 const char* message, 340 size_t messageSize) 341 { 342 if (status == HttpStatus_301_MovedPermanently || 343 //status == HttpStatus_401_Unauthorized || 344 status == HttpStatus_405_MethodNotAllowed) 345 { 346 throw OrthancException(ErrorCode_ParameterOutOfRange, 347 "Please use the dedicated methods to this HTTP status code in HttpOutput"); 348 } 349 350 stateMachine_.SetHttpStatus(status); 351 stateMachine_.SendBody(message, messageSize); 352 } 353 SendStatus(HttpStatus status)354 void HttpOutput::SendStatus(HttpStatus status) 355 { 356 SendStatus(status, NULL, 0); 357 } 358 SendStatus(HttpStatus status,const std::string & message)359 void HttpOutput::SendStatus(HttpStatus status, const std::string &message) 360 { 361 SendStatus(status, message.c_str(), message.size()); 362 } 363 SetContentType(MimeType contentType)364 void HttpOutput::SetContentType(MimeType contentType) 365 { 366 stateMachine_.SetContentType(EnumerationToString(contentType)); 367 } 368 SetContentType(const std::string & contentType)369 void HttpOutput::SetContentType(const std::string &contentType) 370 { 371 stateMachine_.SetContentType(contentType.c_str()); 372 } 373 SetContentFilename(const char * filename)374 void HttpOutput::SetContentFilename(const char *filename) 375 { 376 stateMachine_.SetContentFilename(filename); 377 } 378 SetCookie(const std::string & cookie,const std::string & value)379 void HttpOutput::SetCookie(const std::string &cookie, const std::string &value) 380 { 381 stateMachine_.SetCookie(cookie, value); 382 } 383 AddHeader(const std::string & key,const std::string & value)384 void HttpOutput::AddHeader(const std::string &key, const std::string &value) 385 { 386 stateMachine_.AddHeader(key, value); 387 } 388 389 Redirect(const std::string & path)390 void HttpOutput::Redirect(const std::string& path) 391 { 392 stateMachine_.ClearHeaders(); 393 stateMachine_.SetHttpStatus(HttpStatus_301_MovedPermanently); 394 stateMachine_.AddHeader("Location", path); 395 stateMachine_.SendBody(NULL, 0); 396 } 397 398 SendUnauthorized(const std::string & realm)399 void HttpOutput::SendUnauthorized(const std::string& realm) 400 { 401 stateMachine_.ClearHeaders(); 402 stateMachine_.SetHttpStatus(HttpStatus_401_Unauthorized); 403 stateMachine_.AddHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\""); 404 stateMachine_.SendBody(NULL, 0); 405 } 406 StartMultipart(const std::string & subType,const std::string & contentType)407 void HttpOutput::StartMultipart(const std::string &subType, const std::string &contentType) 408 { 409 stateMachine_.StartMultipart(subType, contentType); 410 } 411 SendMultipartItem(const void * item,size_t size,const std::map<std::string,std::string> & headers)412 void HttpOutput::SendMultipartItem(const void *item, 413 size_t size, 414 const std::map<std::string, std::string> &headers) 415 { 416 stateMachine_.SendMultipartItem(item, size, headers); 417 } 418 CloseMultipart()419 void HttpOutput::CloseMultipart() 420 { 421 stateMachine_.CloseMultipart(); 422 } 423 IsWritingMultipart() const424 bool HttpOutput::IsWritingMultipart() const 425 { 426 return stateMachine_.GetState() == StateMachine::State_WritingMultipart; 427 } 428 429 Answer(const void * buffer,size_t length)430 void HttpOutput::Answer(const void* buffer, 431 size_t length) 432 { 433 if (length == 0) 434 { 435 AnswerEmpty(); 436 return; 437 } 438 439 HttpCompression compression = GetPreferredCompression(length); 440 441 if (compression == HttpCompression_None) 442 { 443 stateMachine_.SetContentLength(length); 444 stateMachine_.SendBody(buffer, length); 445 return; 446 } 447 448 std::string compressed, encoding; 449 450 switch (compression) 451 { 452 case HttpCompression_Deflate: 453 { 454 encoding = "deflate"; 455 ZlibCompressor compressor; 456 // Do not prefix the buffer with its uncompressed size, to be compatible with "deflate" 457 compressor.SetPrefixWithUncompressedSize(false); 458 compressor.Compress(compressed, buffer, length); 459 break; 460 } 461 462 case HttpCompression_Gzip: 463 { 464 encoding = "gzip"; 465 GzipCompressor compressor; 466 compressor.Compress(compressed, buffer, length); 467 break; 468 } 469 470 default: 471 throw OrthancException(ErrorCode_InternalError); 472 } 473 474 LOG(TRACE) << "Compressing a HTTP answer using " << encoding; 475 476 // The body is empty, do not use HTTP compression 477 if (compressed.size() == 0) 478 { 479 AnswerEmpty(); 480 } 481 else 482 { 483 stateMachine_.AddHeader("Content-Encoding", encoding); 484 stateMachine_.SetContentLength(compressed.size()); 485 stateMachine_.SendBody(compressed.c_str(), compressed.size()); 486 } 487 488 stateMachine_.CloseBody(); 489 } 490 491 Answer(const std::string & str)492 void HttpOutput::Answer(const std::string& str) 493 { 494 Answer(str.size() == 0 ? NULL : str.c_str(), str.size()); 495 } 496 497 AnswerEmpty()498 void HttpOutput::AnswerEmpty() 499 { 500 stateMachine_.CloseBody(); 501 } 502 503 CheckHeadersCompatibilityWithMultipart() const504 void HttpOutput::StateMachine::CheckHeadersCompatibilityWithMultipart() const 505 { 506 for (std::list<std::string>::const_iterator 507 it = headers_.begin(); it != headers_.end(); ++it) 508 { 509 if (!Toolbox::StartsWith(*it, "Set-Cookie: ")) 510 { 511 throw OrthancException(ErrorCode_BadSequenceOfCalls, 512 "The only headers that can be set in multipart answers " 513 "are Set-Cookie (here: " + *it + " is set)"); 514 } 515 } 516 } 517 518 PrepareMultipartMainHeader(std::string & boundary,std::string & contentTypeHeader,const std::string & subType,const std::string & contentType)519 static void PrepareMultipartMainHeader(std::string& boundary, 520 std::string& contentTypeHeader, 521 const std::string& subType, 522 const std::string& contentType) 523 { 524 if (subType != "mixed" && 525 subType != "related") 526 { 527 throw OrthancException(ErrorCode_ParameterOutOfRange); 528 } 529 530 /** 531 * Fix for issue 54 ("Decide what to do wrt. quoting of multipart 532 * answers"). The "type" parameter in the "Content-Type" HTTP 533 * header must be quoted if it contains a forward slash "/". This 534 * is necessary for DICOMweb compatibility with OsiriX, but breaks 535 * compatibility with old releases of the client in the Orthanc 536 * DICOMweb plugin <= 0.3 (releases >= 0.4 work fine). 537 * 538 * Full history is available at the following locations: 539 * - In changeset 2248:69b0f4e8a49b: 540 * # hg history -v -r 2248 541 * - https://bugs.orthanc-server.com/show_bug.cgi?id=54 542 * - https://groups.google.com/d/msg/orthanc-users/65zhIM5xbKI/TU5Q1_LhAwAJ 543 **/ 544 std::string tmp; 545 if (contentType.find('/') == std::string::npos) 546 { 547 // No forward slash in the content type 548 tmp = contentType; 549 } 550 else 551 { 552 // Quote the content type because of the forward slash 553 tmp = "\"" + contentType + "\""; 554 } 555 556 boundary = Toolbox::GenerateUuid() + "-" + Toolbox::GenerateUuid(); 557 558 /** 559 * Fix for issue #165: "Encapsulation boundaries must not appear 560 * within the encapsulations, and must be no longer than 70 561 * characters, not counting the two leading hyphens." 562 * https://tools.ietf.org/html/rfc1521 563 * https://bugs.orthanc-server.com/show_bug.cgi?id=165 564 **/ 565 if (boundary.size() != 36 + 1 + 36) // one UUID contains 36 characters 566 { 567 throw OrthancException(ErrorCode_InternalError); 568 } 569 570 boundary = boundary.substr(0, 70); 571 572 contentTypeHeader = ("multipart/" + subType + "; type=" + tmp + "; boundary=" + boundary); 573 } 574 575 StartMultipart(const std::string & subType,const std::string & contentType)576 void HttpOutput::StateMachine::StartMultipart(const std::string& subType, 577 const std::string& contentType) 578 { 579 if (state_ != State_WritingHeader) 580 { 581 throw OrthancException(ErrorCode_BadSequenceOfCalls); 582 } 583 584 if (status_ != HttpStatus_200_Ok) 585 { 586 SendBody(NULL, 0); 587 return; 588 } 589 590 stream_.OnHttpStatusReceived(status_); 591 592 std::string header = "HTTP/1.1 200 OK\r\n"; 593 594 if (keepAlive_) 595 { 596 #if ORTHANC_ENABLE_MONGOOSE == 1 597 throw OrthancException(ErrorCode_NotImplemented, 598 "Multipart answers are not implemented together " 599 "with keep-alive connections if using Mongoose"); 600 601 #elif ORTHANC_ENABLE_CIVETWEB == 1 602 # if CIVETWEB_HAS_DISABLE_KEEP_ALIVE == 1 603 // Turn off Keep-Alive for multipart answers 604 // https://github.com/civetweb/civetweb/issues/727 605 stream_.DisableKeepAlive(); 606 header += "Connection: close\r\n"; 607 # else 608 // The function "mg_disable_keep_alive()" is not available, 609 // let's continue with Keep-Alive. Performance of WADO-RS will 610 // decrease. 611 header += "Connection: keep-alive\r\n"; 612 # endif 613 614 #else 615 # error Please support your embedded Web server here 616 #endif 617 } 618 else 619 { 620 header += "Connection: close\r\n"; 621 } 622 623 // Possibly add the cookies 624 CheckHeadersCompatibilityWithMultipart(); 625 626 for (std::list<std::string>::const_iterator 627 it = headers_.begin(); it != headers_.end(); ++it) 628 { 629 header += *it; 630 } 631 632 std::string contentTypeHeader; 633 PrepareMultipartMainHeader(multipartBoundary_, contentTypeHeader, subType, contentType); 634 multipartContentType_ = contentType; 635 header += ("Content-Type: " + contentTypeHeader + "\r\n\r\n"); 636 637 stream_.Send(true, header.c_str(), header.size()); 638 state_ = State_WritingMultipart; 639 } 640 641 PrepareMultipartItemHeader(std::string & target,size_t length,const std::map<std::string,std::string> & headers,const std::string & boundary,const std::string & contentType)642 static void PrepareMultipartItemHeader(std::string& target, 643 size_t length, 644 const std::map<std::string, std::string>& headers, 645 const std::string& boundary, 646 const std::string& contentType) 647 { 648 target = "--" + boundary + "\r\n"; 649 650 bool hasContentType = false; 651 bool hasContentLength = false; 652 bool hasMimeVersion = false; 653 654 for (std::map<std::string, std::string>::const_iterator 655 it = headers.begin(); it != headers.end(); ++it) 656 { 657 target += it->first + ": " + it->second + "\r\n"; 658 659 std::string tmp; 660 Toolbox::ToLowerCase(tmp, it->first); 661 662 if (tmp == "content-type") 663 { 664 hasContentType = true; 665 } 666 667 if (tmp == "content-length") 668 { 669 hasContentLength = true; 670 } 671 672 if (tmp == "mime-version") 673 { 674 hasMimeVersion = true; 675 } 676 } 677 678 if (!hasContentType) 679 { 680 target += "Content-Type: " + contentType + "\r\n"; 681 } 682 683 if (!hasContentLength) 684 { 685 target += "Content-Length: " + boost::lexical_cast<std::string>(length) + "\r\n"; 686 } 687 688 if (!hasMimeVersion) 689 { 690 target += "MIME-Version: 1.0\r\n\r\n"; 691 } 692 } 693 694 SendMultipartItem(const void * item,size_t length,const std::map<std::string,std::string> & headers)695 void HttpOutput::StateMachine::SendMultipartItem(const void* item, 696 size_t length, 697 const std::map<std::string, std::string>& headers) 698 { 699 if (state_ != State_WritingMultipart) 700 { 701 throw OrthancException(ErrorCode_BadSequenceOfCalls); 702 } 703 704 std::string header; 705 PrepareMultipartItemHeader(header, length, headers, multipartBoundary_, multipartContentType_); 706 stream_.Send(false, header.c_str(), header.size()); 707 708 if (length > 0) 709 { 710 stream_.Send(false, item, length); 711 } 712 713 stream_.Send(false, "\r\n", 2); 714 } 715 716 CloseMultipart()717 void HttpOutput::StateMachine::CloseMultipart() 718 { 719 if (state_ != State_WritingMultipart) 720 { 721 throw OrthancException(ErrorCode_BadSequenceOfCalls); 722 } 723 724 // The two lines below might throw an exception, if the client has 725 // closed the connection. Such an error is ignored. 726 try 727 { 728 std::string header = "--" + multipartBoundary_ + "--\r\n"; 729 stream_.Send(false, header.c_str(), header.size()); 730 } 731 catch (OrthancException&) 732 { 733 } 734 735 state_ = State_Done; 736 } 737 738 AnswerStreamAsBuffer(HttpOutput & output,IHttpStreamAnswer & stream)739 static void AnswerStreamAsBuffer(HttpOutput& output, 740 IHttpStreamAnswer& stream) 741 { 742 ChunkedBuffer buffer; 743 744 while (stream.ReadNextChunk()) 745 { 746 if (stream.GetChunkSize() > 0) 747 { 748 buffer.AddChunk(stream.GetChunkContent(), stream.GetChunkSize()); 749 } 750 } 751 752 std::string s; 753 buffer.Flatten(s); 754 755 output.SetContentType(stream.GetContentType()); 756 757 std::string filename; 758 if (stream.HasContentFilename(filename)) 759 { 760 output.SetContentFilename(filename.c_str()); 761 } 762 763 output.Answer(s); 764 } 765 766 Answer(IHttpStreamAnswer & stream)767 void HttpOutput::Answer(IHttpStreamAnswer& stream) 768 { 769 HttpCompression compression = stream.SetupHttpCompression(isGzipAllowed_, isDeflateAllowed_); 770 771 switch (compression) 772 { 773 case HttpCompression_None: 774 { 775 if (isGzipAllowed_ || isDeflateAllowed_) 776 { 777 // New in Orthanc 1.5.7: Compress streams without built-in 778 // compression, if requested by the "Accept-Encoding" HTTP 779 // header 780 AnswerStreamAsBuffer(*this, stream); 781 return; 782 } 783 784 break; 785 } 786 787 case HttpCompression_Gzip: 788 stateMachine_.AddHeader("Content-Encoding", "gzip"); 789 break; 790 791 case HttpCompression_Deflate: 792 stateMachine_.AddHeader("Content-Encoding", "deflate"); 793 break; 794 795 default: 796 throw OrthancException(ErrorCode_ParameterOutOfRange); 797 } 798 799 stateMachine_.SetContentLength(stream.GetContentLength()); 800 801 std::string contentType = stream.GetContentType(); 802 if (contentType.empty()) 803 { 804 contentType = MIME_BINARY; 805 } 806 807 stateMachine_.SetContentType(contentType.c_str()); 808 809 std::string filename; 810 if (stream.HasContentFilename(filename)) 811 { 812 SetContentFilename(filename.c_str()); 813 } 814 815 while (stream.ReadNextChunk()) 816 { 817 stateMachine_.SendBody(stream.GetChunkContent(), 818 stream.GetChunkSize()); 819 } 820 821 stateMachine_.CloseBody(); 822 } 823 824 AnswerMultipartWithoutChunkedTransfer(const std::string & subType,const std::string & contentType,const std::vector<const void * > & parts,const std::vector<size_t> & sizes,const std::vector<const std::map<std::string,std::string> * > & headers)825 void HttpOutput::AnswerMultipartWithoutChunkedTransfer( 826 const std::string& subType, 827 const std::string& contentType, 828 const std::vector<const void*>& parts, 829 const std::vector<size_t>& sizes, 830 const std::vector<const std::map<std::string, std::string>*>& headers) 831 { 832 if (parts.size() != sizes.size()) 833 { 834 throw OrthancException(ErrorCode_ParameterOutOfRange); 835 } 836 837 stateMachine_.CheckHeadersCompatibilityWithMultipart(); 838 839 std::string boundary, contentTypeHeader; 840 PrepareMultipartMainHeader(boundary, contentTypeHeader, subType, contentType); 841 SetContentType(contentTypeHeader); 842 843 std::map<std::string, std::string> empty; 844 845 ChunkedBuffer chunked; 846 for (size_t i = 0; i < parts.size(); i++) 847 { 848 std::string partHeader; 849 PrepareMultipartItemHeader(partHeader, sizes[i], headers[i] == NULL ? empty : *headers[i], 850 boundary, contentType); 851 852 chunked.AddChunk(partHeader); 853 chunked.AddChunk(parts[i], sizes[i]); 854 chunked.AddChunk("\r\n"); 855 } 856 857 chunked.AddChunk("--" + boundary + "--\r\n"); 858 859 std::string body; 860 chunked.Flatten(body); 861 Answer(body); 862 } 863 } 864