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