comparison Core/HttpServer/HttpOutput.cpp @ 3528:f6fe095f7130

don't open a multipart stream if plugin only sends one part
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 26 Sep 2019 13:40:49 +0200
parents 2f6dcb9c8cc1
children b6a569e6e85b
comparison
equal deleted inserted replaced
3527:40c80049fac7 3528:f6fe095f7130
408 { 408 {
409 stateMachine_.CloseBody(); 409 stateMachine_.CloseBody();
410 } 410 }
411 411
412 412
413 void HttpOutput::StateMachine::CheckHeadersCompatibilityWithMultipart() const
414 {
415 for (std::list<std::string>::const_iterator
416 it = headers_.begin(); it != headers_.end(); ++it)
417 {
418 if (!Toolbox::StartsWith(*it, "Set-Cookie: "))
419 {
420 throw OrthancException(ErrorCode_BadSequenceOfCalls,
421 "The only headers that can be set in multipart answers "
422 "are Set-Cookie (here: " + *it + " is set)");
423 }
424 }
425 }
426
427
428 static void PrepareMultipartMainHeader(std::string& boundary,
429 std::string& contentTypeHeader,
430 const std::string& subType,
431 const std::string& contentType)
432 {
433 if (subType != "mixed" &&
434 subType != "related")
435 {
436 throw OrthancException(ErrorCode_ParameterOutOfRange);
437 }
438
439 /**
440 * Fix for issue 54 ("Decide what to do wrt. quoting of multipart
441 * answers"). The "type" parameter in the "Content-Type" HTTP
442 * header must be quoted if it contains a forward slash "/". This
443 * is necessary for DICOMweb compatibility with OsiriX, but breaks
444 * compatibility with old releases of the client in the Orthanc
445 * DICOMweb plugin <= 0.3 (releases >= 0.4 work fine).
446 *
447 * Full history is available at the following locations:
448 * - In changeset 2248:69b0f4e8a49b:
449 * # hg history -v -r 2248
450 * - https://bitbucket.org/sjodogne/orthanc/issues/54/
451 * - https://groups.google.com/d/msg/orthanc-users/65zhIM5xbKI/TU5Q1_LhAwAJ
452 **/
453 std::string tmp;
454 if (contentType.find('/') == std::string::npos)
455 {
456 // No forward slash in the content type
457 tmp = contentType;
458 }
459 else
460 {
461 // Quote the content type because of the forward slash
462 tmp = "\"" + contentType + "\"";
463 }
464
465 boundary = Toolbox::GenerateUuid() + "-" + Toolbox::GenerateUuid();
466 contentTypeHeader = ("multipart/" + subType + "; type=" + tmp + "; boundary=" + boundary);
467 }
468
469
413 void HttpOutput::StateMachine::StartMultipart(const std::string& subType, 470 void HttpOutput::StateMachine::StartMultipart(const std::string& subType,
414 const std::string& contentType) 471 const std::string& contentType)
415 { 472 {
416 if (subType != "mixed" &&
417 subType != "related")
418 {
419 throw OrthancException(ErrorCode_ParameterOutOfRange);
420 }
421
422 if (state_ != State_WritingHeader) 473 if (state_ != State_WritingHeader)
423 { 474 {
424 throw OrthancException(ErrorCode_BadSequenceOfCalls); 475 throw OrthancException(ErrorCode_BadSequenceOfCalls);
425 } 476 }
426 477
462 { 513 {
463 header += "Connection: close\r\n"; 514 header += "Connection: close\r\n";
464 } 515 }
465 516
466 // Possibly add the cookies 517 // Possibly add the cookies
518 CheckHeadersCompatibilityWithMultipart();
519
467 for (std::list<std::string>::const_iterator 520 for (std::list<std::string>::const_iterator
468 it = headers_.begin(); it != headers_.end(); ++it) 521 it = headers_.begin(); it != headers_.end(); ++it)
469 { 522 {
470 if (!Toolbox::StartsWith(*it, "Set-Cookie: "))
471 {
472 throw OrthancException(ErrorCode_BadSequenceOfCalls,
473 "The only headers that can be set in multipart answers "
474 "are Set-Cookie (here: " + *it + " is set)");
475 }
476
477 header += *it; 523 header += *it;
478 } 524 }
479 525
480 /** 526 std::string contentTypeHeader;
481 * Fix for issue 54 ("Decide what to do wrt. quoting of multipart 527 PrepareMultipartMainHeader(multipartBoundary_, contentTypeHeader, subType, contentType);
482 * answers"). The "type" parameter in the "Content-Type" HTTP
483 * header must be quoted if it contains a forward slash "/". This
484 * is necessary for DICOMweb compatibility with OsiriX, but breaks
485 * compatibility with old releases of the client in the Orthanc
486 * DICOMweb plugin <= 0.3 (releases >= 0.4 work fine).
487 *
488 * Full history is available at the following locations:
489 * - In changeset 2248:69b0f4e8a49b:
490 * # hg history -v -r 2248
491 * - https://bitbucket.org/sjodogne/orthanc/issues/54/
492 * - https://groups.google.com/d/msg/orthanc-users/65zhIM5xbKI/TU5Q1_LhAwAJ
493 **/
494 std::string tmp;
495 if (contentType.find('/') == std::string::npos)
496 {
497 // No forward slash in the content type
498 tmp = contentType;
499 }
500 else
501 {
502 // Quote the content type because of the forward slash
503 tmp = "\"" + contentType + "\"";
504 }
505
506 multipartBoundary_ = Toolbox::GenerateUuid() + "-" + Toolbox::GenerateUuid();
507 multipartContentType_ = contentType; 528 multipartContentType_ = contentType;
508 header += ("Content-Type: multipart/" + subType + "; type=" + 529 header += ("Content-Type: " + contentTypeHeader + "\r\n\r\n");
509 tmp + "; boundary=" + multipartBoundary_ + "\r\n\r\n");
510 530
511 stream_.Send(true, header.c_str(), header.size()); 531 stream_.Send(true, header.c_str(), header.size());
512 state_ = State_WritingMultipart; 532 state_ = State_WritingMultipart;
513 } 533 }
514 534
515 535
516 void HttpOutput::StateMachine::SendMultipartItem(const void* item, 536 static void PrepareMultipartItemHeader(std::string& target,
517 size_t length, 537 size_t length,
518 const std::map<std::string, std::string>& headers) 538 const std::map<std::string, std::string>& headers,
519 { 539 const std::string& boundary,
520 if (state_ != State_WritingMultipart) 540 const std::string& contentType)
521 { 541 {
522 throw OrthancException(ErrorCode_BadSequenceOfCalls); 542 target = "--" + boundary + "\r\n";
523 }
524
525 std::string header = "--" + multipartBoundary_ + "\r\n";
526 543
527 bool hasContentType = false; 544 bool hasContentType = false;
528 bool hasContentLength = false; 545 bool hasContentLength = false;
529 bool hasMimeVersion = false; 546 bool hasMimeVersion = false;
530 547
531 for (std::map<std::string, std::string>::const_iterator 548 for (std::map<std::string, std::string>::const_iterator
532 it = headers.begin(); it != headers.end(); ++it) 549 it = headers.begin(); it != headers.end(); ++it)
533 { 550 {
534 header += it->first + ": " + it->second + "\r\n"; 551 target += it->first + ": " + it->second + "\r\n";
535 552
536 std::string tmp; 553 std::string tmp;
537 Toolbox::ToLowerCase(tmp, it->first); 554 Toolbox::ToLowerCase(tmp, it->first);
538 555
539 if (tmp == "content-type") 556 if (tmp == "content-type")
552 } 569 }
553 } 570 }
554 571
555 if (!hasContentType) 572 if (!hasContentType)
556 { 573 {
557 header += "Content-Type: " + multipartContentType_ + "\r\n"; 574 target += "Content-Type: " + contentType + "\r\n";
558 } 575 }
559 576
560 if (!hasContentLength) 577 if (!hasContentLength)
561 { 578 {
562 header += "Content-Length: " + boost::lexical_cast<std::string>(length) + "\r\n"; 579 target += "Content-Length: " + boost::lexical_cast<std::string>(length) + "\r\n";
563 } 580 }
564 581
565 if (!hasMimeVersion) 582 if (!hasMimeVersion)
566 { 583 {
567 header += "MIME-Version: 1.0\r\n\r\n"; 584 target += "MIME-Version: 1.0\r\n\r\n";
568 } 585 }
569 586 }
587
588
589 void HttpOutput::StateMachine::SendMultipartItem(const void* item,
590 size_t length,
591 const std::map<std::string, std::string>& headers)
592 {
593 if (state_ != State_WritingMultipart)
594 {
595 throw OrthancException(ErrorCode_BadSequenceOfCalls);
596 }
597
598 std::string header;
599 PrepareMultipartItemHeader(header, length, headers, multipartBoundary_, multipartContentType_);
570 stream_.Send(false, header.c_str(), header.size()); 600 stream_.Send(false, header.c_str(), header.size());
571 601
572 if (length > 0) 602 if (length > 0)
573 { 603 {
574 stream_.Send(false, item, length); 604 stream_.Send(false, item, length);
683 } 713 }
684 714
685 stateMachine_.CloseBody(); 715 stateMachine_.CloseBody();
686 } 716 }
687 717
718
719 void HttpOutput::AnswerMultipartWithoutChunkedTransfer(
720 const std::string& subType,
721 const std::string& contentType,
722 const std::vector<const void*>& parts,
723 const std::vector<size_t>& sizes,
724 const std::vector<const std::map<std::string, std::string>*>& headers)
725 {
726 if (parts.size() != sizes.size())
727 {
728 throw OrthancException(ErrorCode_ParameterOutOfRange);
729 }
730
731 stateMachine_.CheckHeadersCompatibilityWithMultipart();
732
733 std::string boundary, contentTypeHeader;
734 PrepareMultipartMainHeader(boundary, contentTypeHeader, subType, contentType);
735 SetContentType(contentTypeHeader);
736
737 std::map<std::string, std::string> empty;
738
739 ChunkedBuffer chunked;
740 for (size_t i = 0; i < parts.size(); i++)
741 {
742 std::string partHeader;
743 PrepareMultipartItemHeader(partHeader, sizes[i], headers[i] == NULL ? empty : *headers[i],
744 boundary, contentType);
745
746 chunked.AddChunk(partHeader);
747 chunked.AddChunk(parts[i], sizes[i]);
748 chunked.AddChunk("\r\n");
749 }
750
751 chunked.AddChunk("--" + boundary + "--\r\n");
752
753 std::string body;
754 chunked.Flatten(body);
755 Answer(body);
756 }
688 } 757 }