comparison UnitTestsSources/RestApiTests.cpp @ 3398:4acd1431e603

new classes: StringMatcher and MultipartStreamReader
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 07 Jun 2019 13:36:43 +0200
parents 4e43e67f8ecf
children 4e8205871967
comparison
equal deleted inserted replaced
3397:9019279dbfd7 3398:4acd1431e603
45 #include "../Core/RestApi/RestApi.h" 45 #include "../Core/RestApi/RestApi.h"
46 #include "../Core/OrthancException.h" 46 #include "../Core/OrthancException.h"
47 #include "../Core/Compression/ZlibCompressor.h" 47 #include "../Core/Compression/ZlibCompressor.h"
48 #include "../Core/RestApi/RestApiHierarchy.h" 48 #include "../Core/RestApi/RestApiHierarchy.h"
49 #include "../Core/HttpServer/HttpContentNegociation.h" 49 #include "../Core/HttpServer/HttpContentNegociation.h"
50 #include "../Core/HttpServer/MultipartStreamReader.h"
51
50 52
51 using namespace Orthanc; 53 using namespace Orthanc;
52 54
53 #if !defined(UNIT_TESTS_WITH_HTTP_CONNEXIONS) 55 #if !defined(UNIT_TESTS_WITH_HTTP_CONNEXIONS)
54 #error "Please set UNIT_TESTS_WITH_HTTP_CONNEXIONS" 56 #error "Please set UNIT_TESTS_WITH_HTTP_CONNEXIONS"
351 353
352 354
353 355
354 namespace 356 namespace
355 { 357 {
356 class AcceptHandler : public Orthanc::HttpContentNegociation::IHandler 358 class AcceptHandler : public HttpContentNegociation::IHandler
357 { 359 {
358 private: 360 private:
359 std::string type_; 361 std::string type_;
360 std::string subtype_; 362 std::string subtype_;
361 363
395 // Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 397 // Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
396 398
397 AcceptHandler h; 399 AcceptHandler h;
398 400
399 { 401 {
400 Orthanc::HttpContentNegociation d; 402 HttpContentNegociation d;
401 d.Register("audio/mp3", h); 403 d.Register("audio/mp3", h);
402 d.Register("audio/basic", h); 404 d.Register("audio/basic", h);
403 405
404 ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/basic")); 406 ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/basic"));
405 ASSERT_EQ("audio", h.GetType()); 407 ASSERT_EQ("audio", h.GetType());
420 // text/x-dvi entity, and if that does not exist, send the 422 // text/x-dvi entity, and if that does not exist, send the
421 // text/plain entity."" 423 // text/plain entity.""
422 const std::string T1 = "text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"; 424 const std::string T1 = "text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c";
423 425
424 { 426 {
425 Orthanc::HttpContentNegociation d; 427 HttpContentNegociation d;
426 d.Register("text/plain", h); 428 d.Register("text/plain", h);
427 d.Register("text/html", h); 429 d.Register("text/html", h);
428 d.Register("text/x-dvi", h); 430 d.Register("text/x-dvi", h);
429 ASSERT_TRUE(d.Apply(T1)); 431 ASSERT_TRUE(d.Apply(T1));
430 ASSERT_EQ("text", h.GetType()); 432 ASSERT_EQ("text", h.GetType());
431 ASSERT_EQ("html", h.GetSubType()); 433 ASSERT_EQ("html", h.GetSubType());
432 } 434 }
433 435
434 { 436 {
435 Orthanc::HttpContentNegociation d; 437 HttpContentNegociation d;
436 d.Register("text/plain", h); 438 d.Register("text/plain", h);
437 d.Register("text/x-dvi", h); 439 d.Register("text/x-dvi", h);
438 d.Register("text/x-c", h); 440 d.Register("text/x-c", h);
439 ASSERT_TRUE(d.Apply(T1)); 441 ASSERT_TRUE(d.Apply(T1));
440 ASSERT_EQ("text", h.GetType()); 442 ASSERT_EQ("text", h.GetType());
441 ASSERT_EQ("x-c", h.GetSubType()); 443 ASSERT_EQ("x-c", h.GetSubType());
442 } 444 }
443 445
444 { 446 {
445 Orthanc::HttpContentNegociation d; 447 HttpContentNegociation d;
446 d.Register("text/plain", h); 448 d.Register("text/plain", h);
447 d.Register("text/x-dvi", h); 449 d.Register("text/x-dvi", h);
448 d.Register("text/x-c", h); 450 d.Register("text/x-c", h);
449 d.Register("text/html", h); 451 d.Register("text/html", h);
450 ASSERT_TRUE(d.Apply(T1)); 452 ASSERT_TRUE(d.Apply(T1));
451 ASSERT_EQ("text", h.GetType()); 453 ASSERT_EQ("text", h.GetType());
452 ASSERT_TRUE(h.GetSubType() == "x-c" || h.GetSubType() == "html"); 454 ASSERT_TRUE(h.GetSubType() == "x-c" || h.GetSubType() == "html");
453 } 455 }
454 456
455 { 457 {
456 Orthanc::HttpContentNegociation d; 458 HttpContentNegociation d;
457 d.Register("text/plain", h); 459 d.Register("text/plain", h);
458 d.Register("text/x-dvi", h); 460 d.Register("text/x-dvi", h);
459 ASSERT_TRUE(d.Apply(T1)); 461 ASSERT_TRUE(d.Apply(T1));
460 ASSERT_EQ("text", h.GetType()); 462 ASSERT_EQ("text", h.GetType());
461 ASSERT_EQ("x-dvi", h.GetSubType()); 463 ASSERT_EQ("x-dvi", h.GetSubType());
462 } 464 }
463 465
464 { 466 {
465 Orthanc::HttpContentNegociation d; 467 HttpContentNegociation d;
466 d.Register("text/plain", h); 468 d.Register("text/plain", h);
467 ASSERT_TRUE(d.Apply(T1)); 469 ASSERT_TRUE(d.Apply(T1));
468 ASSERT_EQ("text", h.GetType()); 470 ASSERT_EQ("text", h.GetType());
469 ASSERT_EQ("plain", h.GetSubType()); 471 ASSERT_EQ("plain", h.GetSubType());
470 } 472 }
641 ASSERT_TRUE(p.LookupUserProperty(s, "a")); ASSERT_TRUE(s == "b"); 643 ASSERT_TRUE(p.LookupUserProperty(s, "a")); ASSERT_TRUE(s == "b");
642 ASSERT_TRUE(p.LookupUserProperty(s, "Hello")); ASSERT_TRUE(s == "world"); 644 ASSERT_TRUE(p.LookupUserProperty(s, "Hello")); ASSERT_TRUE(s == "world");
643 ASSERT_FALSE(p.LookupUserProperty(s, "hello")); 645 ASSERT_FALSE(p.LookupUserProperty(s, "hello"));
644 } 646 }
645 } 647 }
648
649
650 TEST(StringMatcher, Basic)
651 {
652 StringMatcher matcher("---");
653
654 ASSERT_THROW(matcher.GetMatchBegin(), OrthancException);
655
656 {
657 const std::string s = "";
658 ASSERT_FALSE(matcher.Apply(s));
659 }
660
661 {
662 const std::string s = "abc----def";
663 ASSERT_TRUE(matcher.Apply(s));
664 ASSERT_EQ(3, std::distance(s.begin(), matcher.GetMatchBegin()));
665 ASSERT_EQ("---", std::string(matcher.GetMatchBegin(), matcher.GetMatchEnd()));
666 }
667
668 {
669 const std::string s = "abc---";
670 ASSERT_TRUE(matcher.Apply(s));
671 ASSERT_EQ(3, std::distance(s.begin(), matcher.GetMatchBegin()));
672 ASSERT_EQ(s.end(), matcher.GetMatchEnd());
673 ASSERT_EQ("---", std::string(matcher.GetMatchBegin(), matcher.GetMatchEnd()));
674 ASSERT_EQ("", std::string(matcher.GetMatchEnd(), s.end()));
675 }
676
677 {
678 const std::string s = "abc--def";
679 ASSERT_FALSE(matcher.Apply(s));
680 ASSERT_THROW(matcher.GetMatchBegin(), OrthancException);
681 ASSERT_THROW(matcher.GetMatchEnd(), OrthancException);
682 }
683
684 {
685 std::string s(10u, '\0'); // String with null values
686 ASSERT_EQ(10u, s.size());
687 ASSERT_EQ(10u, s.size());
688 ASSERT_FALSE(matcher.Apply(s));
689
690 s[9] = '-';
691 ASSERT_FALSE(matcher.Apply(s));
692
693 s[8] = '-';
694 ASSERT_FALSE(matcher.Apply(s));
695
696 s[7] = '-';
697 ASSERT_TRUE(matcher.Apply(s));
698 ASSERT_EQ(s.c_str() + 7, matcher.GetPointerBegin());
699 ASSERT_EQ(s.c_str() + 10, matcher.GetPointerEnd());
700 ASSERT_EQ(s.end() - 3, matcher.GetMatchBegin());
701 ASSERT_EQ(s.end(), matcher.GetMatchEnd());
702 }
703 }
704
705
706
707 class MultipartTester : public MultipartStreamReader::IHandler
708 {
709 private:
710 struct Part
711 {
712 MultipartStreamReader::HttpHeaders headers_;
713 std::string data_;
714
715 Part(const MultipartStreamReader::HttpHeaders& headers,
716 const void* part,
717 size_t size) :
718 headers_(headers),
719 data_(reinterpret_cast<const char*>(part), size)
720 {
721 }
722 };
723
724 std::vector<Part> parts_;
725
726 public:
727 virtual void Apply(const MultipartStreamReader::HttpHeaders& headers,
728 const void* part,
729 size_t size)
730 {
731 parts_.push_back(Part(headers, part, size));
732 }
733
734 unsigned int GetCount() const
735 {
736 return parts_.size();
737 }
738
739 MultipartStreamReader::HttpHeaders& GetHeaders(size_t i)
740 {
741 return parts_[i].headers_;
742 }
743
744 const std::string& GetData(size_t i) const
745 {
746 return parts_[i].data_;
747 }
748 };
749
750
751 TEST(MultipartStreamReader, ParseHeaders)
752 {
753 std::string ct, b, st;
754
755 {
756 MultipartStreamReader::HttpHeaders h;
757 h["hello"] = "world";
758 h["Content-Type"] = "world"; // Should be in lower-case
759 h["CONTENT-type"] = "world"; // Should be in lower-case
760 ASSERT_FALSE(MultipartStreamReader::GetMainContentType(ct, h));
761 ASSERT_FALSE(MultipartStreamReader::ParseMultipartHeaders(ct, st, b, h));
762 }
763
764 {
765 MultipartStreamReader::HttpHeaders h;
766 h["content-type"] = "world";
767 ASSERT_TRUE(MultipartStreamReader::GetMainContentType(ct, h));
768 ASSERT_EQ(ct, "world");
769 ASSERT_FALSE(MultipartStreamReader::ParseMultipartHeaders(ct, st, b, h));
770 }
771
772 {
773 MultipartStreamReader::HttpHeaders h;
774 h["content-type"] = "multipart/related; dummy=value; boundary=1234; hello=world";
775 ASSERT_TRUE(MultipartStreamReader::GetMainContentType(ct, h));
776 ASSERT_EQ(ct, h["content-type"]);
777 ASSERT_TRUE(MultipartStreamReader::ParseMultipartHeaders(ct, st, b, h));
778 ASSERT_EQ(ct, "multipart/related");
779 ASSERT_EQ(b, "1234");
780 ASSERT_TRUE(st.empty());
781 }
782
783 {
784 MultipartStreamReader::HttpHeaders h;
785 h["content-type"] = "multipart/related; boundary=";
786 ASSERT_TRUE(MultipartStreamReader::GetMainContentType(ct, h));
787 ASSERT_EQ(ct, h["content-type"]);
788 ASSERT_FALSE(MultipartStreamReader::ParseMultipartHeaders(ct, st, b, h)); // Empty boundary
789 }
790
791 {
792 MultipartStreamReader::HttpHeaders h;
793 h["content-type"] = "Multipart/Related; TYPE=Application/Dicom; Boundary=heLLO";
794 ASSERT_TRUE(MultipartStreamReader::ParseMultipartHeaders(ct, st, b, h));
795 ASSERT_EQ(ct, "multipart/related");
796 ASSERT_EQ(b, "heLLO");
797 ASSERT_EQ(st, "application/dicom");
798 }
799
800 {
801 MultipartStreamReader::HttpHeaders h;
802 h["content-type"] = "Multipart/Related; type=\"application/DICOM\"; Boundary=a";
803 ASSERT_TRUE(MultipartStreamReader::ParseMultipartHeaders(ct, st, b, h));
804 ASSERT_EQ(ct, "multipart/related");
805 ASSERT_EQ(b, "a");
806 ASSERT_EQ(st, "application/dicom");
807 }
808 }
809
810
811 TEST(MultipartStreamReader, BytePerByte)
812 {
813 std::string stream = "GARBAGE";
814
815 std::string boundary = "123456789123456789";
816
817 {
818 for (size_t i = 0; i < 10; i++)
819 {
820 std::string f = "hello " + boost::lexical_cast<std::string>(i);
821
822 stream += "\r\n--" + boundary + "\r\n";
823 if (i % 2 == 0)
824 stream += "Content-Length: " + boost::lexical_cast<std::string>(f.size()) + "\r\n";
825 stream += "Content-Type: toto " + boost::lexical_cast<std::string>(i) + "\r\n\r\n";
826 stream += f;
827 }
828
829 stream += "\r\n--" + boundary + "--";
830 stream += "GARBAGE";
831 }
832
833 for (unsigned int k = 0; k < 2; k++)
834 {
835 MultipartTester decoded;
836
837 MultipartStreamReader reader(boundary);
838 reader.SetBlockSize(1);
839 reader.SetHandler(decoded);
840
841 if (k == 0)
842 {
843 for (size_t i = 0; i < stream.size(); i++)
844 {
845 reader.AddChunk(&stream[i], 1);
846 }
847 }
848 else
849 {
850 reader.AddChunk(stream);
851 }
852
853 reader.CloseStream();
854
855 ASSERT_EQ(10u, decoded.GetCount());
856
857 for (size_t i = 0; i < 10; i++)
858 {
859 ASSERT_EQ("hello " + boost::lexical_cast<std::string>(i), decoded.GetData(i));
860 ASSERT_EQ("toto " + boost::lexical_cast<std::string>(i), decoded.GetHeaders(i)["content-type"]);
861
862 if (i % 2 == 0)
863 {
864 ASSERT_EQ(2u, decoded.GetHeaders(i).size());
865 ASSERT_TRUE(decoded.GetHeaders(i).find("content-length") != decoded.GetHeaders(i).end());
866 }
867 }
868 }
869 }