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 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
24 // Must be the first to be sure to use the Orthanc framework shared library
25 #  include <OrthancFramework.h>
26 #endif
27 
28 #include <gtest/gtest.h>
29 
30 #include "../Sources/ChunkedBuffer.h"
31 #include "../Sources/Compression/ZlibCompressor.h"
32 #include "../Sources/HttpServer/HttpContentNegociation.h"
33 #include "../Sources/HttpServer/MultipartStreamReader.h"
34 #include "../Sources/HttpServer/StringMatcher.h"
35 #include "../Sources/Logging.h"
36 #include "../Sources/OrthancException.h"
37 #include "../Sources/RestApi/RestApiHierarchy.h"
38 #include "../Sources/WebServiceParameters.h"
39 
40 #include <ctype.h>
41 #include <boost/lexical_cast.hpp>
42 #include <algorithm>
43 
44 #if ORTHANC_SANDBOXED != 1
45 #  include "../Sources/RestApi/RestApi.h"
46 #endif
47 
48 
49 using namespace Orthanc;
50 
51 #if !defined(UNIT_TESTS_WITH_HTTP_CONNEXIONS) && (ORTHANC_SANDBOXED != 1)
52 #  error UNIT_TESTS_WITH_HTTP_CONNEXIONS is not defined
53 #endif
54 
55 #if !defined(ORTHANC_ENABLE_SSL)
56 #  error ORTHANC_ENABLE_SSL is not defined
57 #endif
58 
59 #if ORTHANC_SANDBOXED != 1
60 #  include "../Sources/HttpClient.h"
61 #  include "../Sources/SystemToolbox.h"
62 #endif
63 
64 
65 #if ORTHANC_SANDBOXED != 1
TEST(HttpClient,Basic)66 TEST(HttpClient, Basic)
67 {
68   HttpClient c;
69   ASSERT_FALSE(c.IsVerbose());
70   c.SetVerbose(true);
71   ASSERT_TRUE(c.IsVerbose());
72   c.SetVerbose(false);
73   ASSERT_FALSE(c.IsVerbose());
74 
75 #if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1
76   // The "http://www.orthanc-server.com/downloads/third-party/" does
77   // not automatically redirect to HTTPS, so we cas use it even if the
78   // OpenSSL/HTTPS support is disabled in curl
79   const std::string BASE = "http://www.orthanc-server.com/downloads/third-party/";
80 
81   Json::Value v;
82   c.SetUrl(BASE + "Product.json");
83 
84   c.Apply(v);
85   ASSERT_TRUE(v.type() == Json::objectValue);
86   ASSERT_TRUE(v.isMember("Description"));
87 #endif
88 }
89 #endif
90 
91 
92 #if (UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1) && (ORTHANC_ENABLE_SSL == 1) && (ORTHANC_SANDBOXED != 1)
93 
94 /**
95    The HTTPS CA certificates for BitBucket were extracted as follows:
96 
97    (1) We retrieve the certification chain of BitBucket:
98 
99    # echo | openssl s_client -showcerts -connect www.bitbucket.org:443
100 
101    (2) We see that the certification authority (CA) is
102    "www.digicert.com", and the root certificate is "DigiCert High
103    Assurance EV Root CA". As a consequence, we navigate to DigiCert to
104    find the URL to this CA certificate:
105 
106    firefox https://www.digicert.com/digicert-root-certificates.htm
107 
108    (3) Once we get the URL to the CA certificate, we convert it to a C
109    macro that can be used by libcurl:
110 
111    # cd UnitTestsSources
112    # ../Resources/RetrieveCACertificates.py BITBUCKET_CERTIFICATES https://www.digicert.com/CACerts/DigiCertHighAssuranceEVRootCA.crt > BitbucketCACertificates.h
113 **/
114 
115 #include "BitbucketCACertificates.h"
116 
TEST(HttpClient,Ssl)117 TEST(HttpClient, Ssl)
118 {
119   SystemToolbox::WriteFile(BITBUCKET_CERTIFICATES, "UnitTestsResults/bitbucket.cert");
120 
121   /*{
122     std::string s;
123     SystemToolbox::ReadFile(s, "/usr/share/ca-certificates/mozilla/WoSign.crt");
124     SystemToolbox::WriteFile(s, "UnitTestsResults/bitbucket.cert");
125     }*/
126 
127   HttpClient c;
128   c.SetHttpsVerifyPeers(true);
129   c.SetHttpsCACertificates("UnitTestsResults/bitbucket.cert");
130 
131   // Test file modified on 2020-04-20, in order to use a git
132   // repository on BitBucket instead of a Mercurial repository
133   // (because Mercurial support disappears on 2020-05-31)
134   c.SetUrl("https://bitbucket.org/osimis/orthanc-setup-samples/raw/master/docker/serve-folders/orthanc/serve-folders.json");
135 
136   Json::Value v;
137   c.Apply(v);
138   ASSERT_TRUE(v.isMember("ServeFolders"));
139 }
140 
TEST(HttpClient,SslNoVerification)141 TEST(HttpClient, SslNoVerification)
142 {
143   HttpClient c;
144   c.SetHttpsVerifyPeers(false);
145   c.SetUrl("https://bitbucket.org/osimis/orthanc-setup-samples/raw/master/docker/serve-folders/orthanc/serve-folders.json");
146 
147   Json::Value v;
148   c.Apply(v);
149   ASSERT_TRUE(v.isMember("ServeFolders"));
150 }
151 
152 #endif
153 
154 
TEST(ChunkedBuffer,Basic)155 TEST(ChunkedBuffer, Basic)
156 {
157   for (unsigned int i = 0; i < 2; i++)
158   {
159     ChunkedBuffer b;
160 
161     if (i == 0)
162     {
163       b.SetPendingBufferSize(0);
164       ASSERT_EQ(0u, b.GetPendingBufferSize());
165     }
166     else
167     {
168       ASSERT_EQ(16u * 1024u, b.GetPendingBufferSize());
169     }
170 
171     ASSERT_EQ(0u, b.GetNumBytes());
172 
173     b.AddChunk("hello", 5);
174     ASSERT_EQ(5u, b.GetNumBytes());
175 
176     b.AddChunk("world", 5);
177     ASSERT_EQ(10u, b.GetNumBytes());
178 
179     std::string s;
180     b.Flatten(s);
181     ASSERT_EQ("helloworld", s);
182   }
183 }
184 
185 
TEST(RestApi,ParseCookies)186 TEST(RestApi, ParseCookies)
187 {
188   HttpToolbox::Arguments headers;
189   HttpToolbox::Arguments cookies;
190 
191   headers["cookie"] = "a=b;c=d;;;e=f;;g=h;";
192   HttpToolbox::ParseCookies(cookies, headers);
193   ASSERT_EQ(4u, cookies.size());
194   ASSERT_EQ("b", cookies["a"]);
195   ASSERT_EQ("d", cookies["c"]);
196   ASSERT_EQ("f", cookies["e"]);
197   ASSERT_EQ("h", cookies["g"]);
198 
199   headers["cookie"] = "  name =  value  ; name2=value2";
200   HttpToolbox::ParseCookies(cookies, headers);
201   ASSERT_EQ(2u, cookies.size());
202   ASSERT_EQ("value", cookies["name"]);
203   ASSERT_EQ("value2", cookies["name2"]);
204 
205   headers["cookie"] = "  ;;;    ";
206   HttpToolbox::ParseCookies(cookies, headers);
207   ASSERT_EQ(0u, cookies.size());
208 
209   headers["cookie"] = "  ;   n=v  ;;    ";
210   HttpToolbox::ParseCookies(cookies, headers);
211   ASSERT_EQ(1u, cookies.size());
212   ASSERT_EQ("v", cookies["n"]);
213 }
214 
215 
TEST(RestApi,RestApiPath)216 TEST(RestApi, RestApiPath)
217 {
218   HttpToolbox::Arguments args;
219   UriComponents trail;
220 
221   {
222     RestApiPath uri("/coucou/{abc}/d/*");
223     ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/e/f/g"));
224     ASSERT_EQ(1u, args.size());
225     ASSERT_EQ(3u, trail.size());
226     ASSERT_EQ("moi", args["abc"]);
227     ASSERT_EQ("e", trail[0]);
228     ASSERT_EQ("f", trail[1]);
229     ASSERT_EQ("g", trail[2]);
230 
231     ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/f"));
232     ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/"));
233     ASSERT_FALSE(uri.Match(args, trail, "/a/moi/d"));
234     ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi"));
235 
236     ASSERT_EQ(3u, uri.GetLevelCount());
237     ASSERT_TRUE(uri.IsUniversalTrailing());
238 
239     ASSERT_EQ("coucou", uri.GetLevelName(0));
240     ASSERT_THROW(uri.GetWildcardName(0), OrthancException);
241 
242     ASSERT_EQ("abc", uri.GetWildcardName(1));
243     ASSERT_THROW(uri.GetLevelName(1), OrthancException);
244 
245     ASSERT_EQ("d", uri.GetLevelName(2));
246     ASSERT_THROW(uri.GetWildcardName(2), OrthancException);
247   }
248 
249   {
250     RestApiPath uri("/coucou/{abc}/d");
251     ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/d/e/f/g"));
252     ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d"));
253     ASSERT_EQ(1u, args.size());
254     ASSERT_EQ(0u, trail.size());
255     ASSERT_EQ("moi", args["abc"]);
256 
257     ASSERT_EQ(3u, uri.GetLevelCount());
258     ASSERT_FALSE(uri.IsUniversalTrailing());
259 
260     ASSERT_EQ("coucou", uri.GetLevelName(0));
261     ASSERT_THROW(uri.GetWildcardName(0), OrthancException);
262 
263     ASSERT_EQ("abc", uri.GetWildcardName(1));
264     ASSERT_THROW(uri.GetLevelName(1), OrthancException);
265 
266     ASSERT_EQ("d", uri.GetLevelName(2));
267     ASSERT_THROW(uri.GetWildcardName(2), OrthancException);
268   }
269 
270   {
271     RestApiPath uri("/*");
272     ASSERT_TRUE(uri.Match(args, trail, "/a/b/c"));
273     ASSERT_EQ(0u, args.size());
274     ASSERT_EQ(3u, trail.size());
275     ASSERT_EQ("a", trail[0]);
276     ASSERT_EQ("b", trail[1]);
277     ASSERT_EQ("c", trail[2]);
278 
279     ASSERT_EQ(0u, uri.GetLevelCount());
280     ASSERT_TRUE(uri.IsUniversalTrailing());
281   }
282 }
283 
284 
285 
286 
287 
288 
289 static int testValue;
290 
291 template <int value>
SetValue(RestApiGetCall & get)292 static void SetValue(RestApiGetCall& get)
293 {
294   testValue = value;
295 }
296 
297 
GetDirectory(Json::Value & target,RestApiHierarchy & hierarchy,const std::string & uri)298 static bool GetDirectory(Json::Value& target,
299                          RestApiHierarchy& hierarchy,
300                          const std::string& uri)
301 {
302   UriComponents p;
303   Toolbox::SplitUriComponents(p, uri);
304   return hierarchy.GetDirectory(target, p);
305 }
306 
307 
308 
309 namespace
310 {
311   class MyVisitor : public RestApiHierarchy::IVisitor
312   {
313   public:
Visit(const RestApiHierarchy::Resource & resource,const UriComponents & uri,bool hasTrailing,const HttpToolbox::Arguments & components,const UriComponents & trailing)314     virtual bool Visit(const RestApiHierarchy::Resource& resource,
315                        const UriComponents& uri,
316                        bool hasTrailing,
317                        const HttpToolbox::Arguments& components,
318                        const UriComponents& trailing) ORTHANC_OVERRIDE
319     {
320       return resource.Handle(*(RestApiGetCall*) NULL);
321     }
322   };
323 }
324 
325 
HandleGet(RestApiHierarchy & hierarchy,const std::string & uri)326 static bool HandleGet(RestApiHierarchy& hierarchy,
327                       const std::string& uri)
328 {
329   UriComponents p;
330   Toolbox::SplitUriComponents(p, uri);
331   MyVisitor visitor;
332   return hierarchy.LookupResource(p, visitor);
333 }
334 
335 
TEST(RestApi,RestApiHierarchy)336 TEST(RestApi, RestApiHierarchy)
337 {
338   RestApiHierarchy root;
339   root.Register("/hello/world/test", SetValue<1>);
340   root.Register("/hello/world/test2", SetValue<2>);
341   root.Register("/hello/{world}/test3/test4", SetValue<3>);
342   root.Register("/hello2/*", SetValue<4>);
343 
344   Json::Value m;
345   root.CreateSiteMap(m);
346 
347   std::string s;
348   Toolbox::WriteStyledJson(s, m);
349 
350   Json::Value d;
351   ASSERT_FALSE(GetDirectory(d, root, "/hello"));
352 
353   ASSERT_TRUE(GetDirectory(d, root, "/hello/a"));
354   ASSERT_EQ(1u, d.size());
355   ASSERT_EQ("test3", d[0].asString());
356 
357   ASSERT_TRUE(GetDirectory(d, root, "/hello/world"));
358   ASSERT_EQ(2u, d.size());
359 
360   ASSERT_TRUE(GetDirectory(d, root, "/hello/a/test3"));
361   ASSERT_EQ(1u, d.size());
362   ASSERT_EQ("test4", d[0].asString());
363 
364   ASSERT_TRUE(GetDirectory(d, root, "/hello/world/test"));
365   ASSERT_TRUE(GetDirectory(d, root, "/hello/world/test2"));
366   ASSERT_FALSE(GetDirectory(d, root, "/hello2"));
367 
368   testValue = 0;
369   ASSERT_TRUE(HandleGet(root, "/hello/world/test"));
370   ASSERT_EQ(testValue, 1);
371   ASSERT_TRUE(HandleGet(root, "/hello/world/test2"));
372   ASSERT_EQ(testValue, 2);
373   ASSERT_TRUE(HandleGet(root, "/hello/b/test3/test4"));
374   ASSERT_EQ(testValue, 3);
375   ASSERT_FALSE(HandleGet(root, "/hello/b/test3/test"));
376   ASSERT_EQ(testValue, 3);
377   ASSERT_TRUE(HandleGet(root, "/hello2/a/b"));
378   ASSERT_EQ(testValue, 4);
379 }
380 
381 
382 
383 
384 
385 namespace
386 {
387   class AcceptHandler : public HttpContentNegociation::IHandler
388   {
389   private:
390     std::string type_;
391     std::string subtype_;
392 
393   public:
AcceptHandler()394     AcceptHandler()
395     {
396       Reset();
397     }
398 
Reset()399     void Reset()
400     {
401       Handle("nope", "nope");
402     }
403 
GetType() const404     const std::string& GetType() const
405     {
406       return type_;
407     }
408 
GetSubType() const409     const std::string& GetSubType() const
410     {
411       return subtype_;
412     }
413 
Handle(const std::string & type,const std::string & subtype)414     virtual void Handle(const std::string& type,
415                         const std::string& subtype) ORTHANC_OVERRIDE
416     {
417       type_ = type;
418       subtype_ = subtype;
419     }
420   };
421 }
422 
423 
TEST(RestApi,HttpContentNegociation)424 TEST(RestApi, HttpContentNegociation)
425 {
426   // Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
427 
428   AcceptHandler h;
429 
430   {
431     HttpContentNegociation d;
432     d.Register("audio/mp3", h);
433     d.Register("audio/basic", h);
434 
435     ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/basic"));
436     ASSERT_EQ("audio", h.GetType());
437     ASSERT_EQ("basic", h.GetSubType());
438 
439     ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/nope"));
440     ASSERT_EQ("audio", h.GetType());
441     ASSERT_EQ("mp3", h.GetSubType());
442 
443     ASSERT_FALSE(d.Apply("application/*; q=0.2, application/pdf"));
444 
445     ASSERT_TRUE(d.Apply("*/*; application/*; q=0.2, application/pdf"));
446     ASSERT_EQ("audio", h.GetType());
447   }
448 
449   // "This would be interpreted as "text/html and text/x-c are the
450   // preferred media types, but if they do not exist, then send the
451   // text/x-dvi entity, and if that does not exist, send the
452   // text/plain entity.""
453   const std::string T1 = "text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c";
454 
455   {
456     HttpContentNegociation d;
457     d.Register("text/plain", h);
458     d.Register("text/html", h);
459     d.Register("text/x-dvi", h);
460     ASSERT_TRUE(d.Apply(T1));
461     ASSERT_EQ("text", h.GetType());
462     ASSERT_EQ("html", h.GetSubType());
463   }
464 
465   {
466     HttpContentNegociation d;
467     d.Register("text/plain", h);
468     d.Register("text/x-dvi", h);
469     d.Register("text/x-c", h);
470     ASSERT_TRUE(d.Apply(T1));
471     ASSERT_EQ("text", h.GetType());
472     ASSERT_EQ("x-c", h.GetSubType());
473   }
474 
475   {
476     HttpContentNegociation d;
477     d.Register("text/plain", h);
478     d.Register("text/x-dvi", h);
479     d.Register("text/x-c", h);
480     d.Register("text/html", h);
481     ASSERT_TRUE(d.Apply(T1));
482     ASSERT_EQ("text", h.GetType());
483     ASSERT_TRUE(h.GetSubType() == "x-c" || h.GetSubType() == "html");
484   }
485 
486   {
487     HttpContentNegociation d;
488     d.Register("text/plain", h);
489     d.Register("text/x-dvi", h);
490     ASSERT_TRUE(d.Apply(T1));
491     ASSERT_EQ("text", h.GetType());
492     ASSERT_EQ("x-dvi", h.GetSubType());
493   }
494 
495   {
496     HttpContentNegociation d;
497     d.Register("text/plain", h);
498     ASSERT_TRUE(d.Apply(T1));
499     ASSERT_EQ("text", h.GetType());
500     ASSERT_EQ("plain", h.GetSubType());
501   }
502 }
503 
504 
TEST(WebServiceParameters,Serialization)505 TEST(WebServiceParameters, Serialization)
506 {
507   {
508     Json::Value v = Json::arrayValue;
509     v.append("http://localhost:8042/");
510 
511     WebServiceParameters p(v);
512     ASSERT_FALSE(p.IsAdvancedFormatNeeded());
513 
514     Json::Value v2;
515     p.Serialize(v2, false, true);
516     ASSERT_EQ(v, v2);
517 
518     WebServiceParameters p2(v2);
519     ASSERT_EQ("http://localhost:8042/", p2.GetUrl());
520     ASSERT_TRUE(p2.GetUsername().empty());
521     ASSERT_TRUE(p2.GetPassword().empty());
522     ASSERT_TRUE(p2.GetCertificateFile().empty());
523     ASSERT_TRUE(p2.GetCertificateKeyFile().empty());
524     ASSERT_TRUE(p2.GetCertificateKeyPassword().empty());
525     ASSERT_FALSE(p2.IsPkcs11Enabled());
526   }
527 
528   {
529     Json::Value v = Json::arrayValue;
530     v.append("http://localhost:8042/");
531     v.append("user");
532     v.append("pass");
533 
534     WebServiceParameters p(v);
535     ASSERT_FALSE(p.IsAdvancedFormatNeeded());
536     ASSERT_EQ("http://localhost:8042/", p.GetUrl());
537     ASSERT_EQ("user", p.GetUsername());
538     ASSERT_EQ("pass", p.GetPassword());
539     ASSERT_TRUE(p.GetCertificateFile().empty());
540     ASSERT_TRUE(p.GetCertificateKeyFile().empty());
541     ASSERT_TRUE(p.GetCertificateKeyPassword().empty());
542     ASSERT_FALSE(p.IsPkcs11Enabled());
543 
544     Json::Value v2;
545     p.Serialize(v2, false, true);
546     ASSERT_EQ(v, v2);
547 
548     p.Serialize(v2, false, false /* no password */);
549     ASSERT_EQ(Json::arrayValue, v2.type());
550     ASSERT_EQ(3u, v2.size());
551     ASSERT_EQ("http://localhost:8042/", v2[0u].asString());
552     ASSERT_EQ("user", v2[1u].asString());
553     ASSERT_TRUE(v2[2u].asString().empty());
554 
555     WebServiceParameters p2(v2);  // Test decoding
556     ASSERT_EQ("http://localhost:8042/", p2.GetUrl());
557   }
558 
559   {
560     Json::Value v = Json::arrayValue;
561     v.append("http://localhost:8042/");
562 
563     WebServiceParameters p(v);
564     ASSERT_FALSE(p.IsAdvancedFormatNeeded());
565     p.SetPkcs11Enabled(true);
566     ASSERT_TRUE(p.IsAdvancedFormatNeeded());
567 
568     Json::Value v2;
569     p.Serialize(v2, false, true);
570 
571     ASSERT_EQ(Json::objectValue, v2.type());
572     ASSERT_EQ(4u, v2.size());
573     ASSERT_EQ("http://localhost:8042/", v2["Url"].asString());
574     ASSERT_TRUE(v2["Pkcs11"].asBool());
575     ASSERT_EQ(Json::objectValue, v2["HttpHeaders"].type());
576     ASSERT_EQ(0u, v2["HttpHeaders"].size());
577     ASSERT_EQ(0, v2["Timeout"].asInt());
578 
579     WebServiceParameters p2(v2);  // Test decoding
580     ASSERT_EQ("http://localhost:8042/", p2.GetUrl());
581   }
582 
583   {
584     Json::Value v = Json::arrayValue;
585     v.append("http://localhost:8042/");
586 
587     WebServiceParameters p(v);
588     ASSERT_FALSE(p.IsAdvancedFormatNeeded());
589     p.SetClientCertificate("a", "b", "c");
590     ASSERT_TRUE(p.IsAdvancedFormatNeeded());
591 
592     Json::Value v2;
593     p.Serialize(v2, false, true);
594 
595     ASSERT_EQ(Json::objectValue, v2.type());
596     ASSERT_EQ(7u, v2.size());
597     ASSERT_EQ("http://localhost:8042/", v2["Url"].asString());
598     ASSERT_EQ("a", v2["CertificateFile"].asString());
599     ASSERT_EQ("b", v2["CertificateKeyFile"].asString());
600     ASSERT_EQ("c", v2["CertificateKeyPassword"].asString());
601     ASSERT_FALSE(v2["Pkcs11"].asBool());
602     ASSERT_EQ(Json::objectValue, v2["HttpHeaders"].type());
603     ASSERT_EQ(0u, v2["HttpHeaders"].size());
604     ASSERT_EQ(0, v2["Timeout"].asInt());
605 
606     WebServiceParameters p2(v2);  // Test decoding
607     ASSERT_EQ("http://localhost:8042/", p2.GetUrl());
608   }
609 
610   {
611     Json::Value v = Json::arrayValue;
612     v.append("http://localhost:8042/");
613 
614     WebServiceParameters p(v);
615     ASSERT_FALSE(p.IsAdvancedFormatNeeded());
616     p.AddHttpHeader("a", "b");
617     p.AddHttpHeader("c", "d");
618     p.SetTimeout(42);
619     ASSERT_TRUE(p.IsAdvancedFormatNeeded());
620 
621     Json::Value v2;
622     p.Serialize(v2, false, true);
623     WebServiceParameters p2(v2);
624 
625     ASSERT_EQ(Json::objectValue, v2.type());
626     ASSERT_EQ(4u, v2.size());
627     ASSERT_EQ("http://localhost:8042/", v2["Url"].asString());
628     ASSERT_FALSE(v2["Pkcs11"].asBool());
629     ASSERT_EQ(Json::objectValue, v2["HttpHeaders"].type());
630     ASSERT_EQ(2u, v2["HttpHeaders"].size());
631     ASSERT_EQ("b", v2["HttpHeaders"]["a"].asString());
632     ASSERT_EQ("d", v2["HttpHeaders"]["c"].asString());
633     ASSERT_EQ(42, v2["Timeout"].asInt());
634 
635     std::set<std::string> a;
636     p2.ListHttpHeaders(a);
637     ASSERT_EQ(2u, a.size());
638     ASSERT_TRUE(a.find("a") != a.end());
639     ASSERT_TRUE(a.find("c") != a.end());
640 
641     std::string s;
642     ASSERT_TRUE(p2.LookupHttpHeader(s, "a")); ASSERT_EQ("b", s);
643     ASSERT_TRUE(p2.LookupHttpHeader(s, "c")); ASSERT_EQ("d", s);
644     ASSERT_FALSE(p2.LookupHttpHeader(s, "nope"));
645   }
646 }
647 
648 
TEST(WebServiceParameters,UserProperties)649 TEST(WebServiceParameters, UserProperties)
650 {
651   Json::Value v = Json::nullValue;
652 
653   {
654     WebServiceParameters p;
655     p.SetUrl("http://localhost:8042/");
656     ASSERT_FALSE(p.IsAdvancedFormatNeeded());
657 
658     ASSERT_THROW(p.AddUserProperty("Url", "nope"), OrthancException);
659     p.AddUserProperty("Hello", "world");
660     p.AddUserProperty("a", "b");
661     ASSERT_TRUE(p.IsAdvancedFormatNeeded());
662 
663     p.Serialize(v, false, true);
664 
665     p.ClearUserProperties();
666     ASSERT_FALSE(p.IsAdvancedFormatNeeded());
667   }
668 
669   {
670     WebServiceParameters p(v);
671     ASSERT_TRUE(p.IsAdvancedFormatNeeded());
672     ASSERT_TRUE(p.GetHttpHeaders().empty());
673 
674     std::set<std::string> tmp;
675     p.ListUserProperties(tmp);
676     ASSERT_EQ(2u, tmp.size());
677     ASSERT_TRUE(tmp.find("a")     != tmp.end());
678     ASSERT_TRUE(tmp.find("Hello") != tmp.end());
679     ASSERT_TRUE(tmp.find("hello") == tmp.end());
680 
681     std::string s;
682     ASSERT_TRUE(p.LookupUserProperty(s, "a"));      ASSERT_TRUE(s == "b");
683     ASSERT_TRUE(p.LookupUserProperty(s, "Hello"));  ASSERT_TRUE(s == "world");
684     ASSERT_FALSE(p.LookupUserProperty(s, "hello"));
685   }
686 }
687 
688 
TEST(StringMatcher,Basic)689 TEST(StringMatcher, Basic)
690 {
691   StringMatcher matcher("---");
692 
693   ASSERT_THROW(matcher.GetMatchBegin(), OrthancException);
694 
695   {
696     const std::string s = "";
697     ASSERT_FALSE(matcher.Apply(s));
698   }
699 
700   {
701     const std::string s = "abc----def";
702     ASSERT_TRUE(matcher.Apply(s));
703     ASSERT_EQ(3, std::distance(s.begin(), matcher.GetMatchBegin()));
704     ASSERT_EQ("---", std::string(matcher.GetMatchBegin(), matcher.GetMatchEnd()));
705   }
706 
707   {
708     const std::string s = "abc---";
709     ASSERT_TRUE(matcher.Apply(s));
710     ASSERT_EQ(3, std::distance(s.begin(), matcher.GetMatchBegin()));
711     ASSERT_EQ(s.end(), matcher.GetMatchEnd());
712     ASSERT_EQ("---", std::string(matcher.GetMatchBegin(), matcher.GetMatchEnd()));
713     ASSERT_EQ("", std::string(matcher.GetMatchEnd(), s.end()));
714   }
715 
716   {
717     const std::string s = "abc--def";
718     ASSERT_FALSE(matcher.Apply(s));
719     ASSERT_THROW(matcher.GetMatchBegin(), OrthancException);
720     ASSERT_THROW(matcher.GetMatchEnd(), OrthancException);
721   }
722 
723   {
724     std::string s(10u, '\0');  // String with null values
725     ASSERT_EQ(10u, s.size());
726     ASSERT_FALSE(matcher.Apply(s));
727 
728     s[9] = '-';
729     ASSERT_FALSE(matcher.Apply(s));
730 
731     s[8] = '-';
732     ASSERT_FALSE(matcher.Apply(s));
733 
734     s[7] = '-';
735     ASSERT_TRUE(matcher.Apply(s));
736     ASSERT_EQ(s.c_str() + 7, matcher.GetPointerBegin());
737     ASSERT_EQ(s.c_str() + 10, matcher.GetPointerEnd());
738     ASSERT_EQ(s.end() - 3, matcher.GetMatchBegin());
739     ASSERT_EQ(s.end(), matcher.GetMatchEnd());
740   }
741 }
742 
743 
TEST(CStringMatcher,Basic)744 TEST(CStringMatcher, Basic)
745 {
746   CStringMatcher matcher("---");
747 
748   ASSERT_THROW(matcher.GetMatchBegin(), OrthancException);
749 
750   {
751     ASSERT_FALSE(matcher.Apply(NULL, 0));
752 
753     const std::string s = "";
754     ASSERT_FALSE(matcher.Apply(s));
755   }
756 
757   {
758     const char* s = "abc---def";
759     ASSERT_TRUE(matcher.Apply(s, s + 9));
760 
761     ASSERT_EQ('a', matcher.GetMatchBegin()[-3]);
762     ASSERT_EQ('b', matcher.GetMatchBegin()[-2]);
763     ASSERT_EQ('c', matcher.GetMatchBegin()[-1]);
764     ASSERT_EQ('-', matcher.GetMatchBegin()[0]);
765     ASSERT_EQ('-', matcher.GetMatchBegin()[1]);
766     ASSERT_EQ('-', matcher.GetMatchBegin()[2]);
767     ASSERT_EQ('d', matcher.GetMatchBegin()[3]);
768     ASSERT_EQ('e', matcher.GetMatchBegin()[4]);
769     ASSERT_EQ('f', matcher.GetMatchBegin()[5]);
770     ASSERT_EQ('\0', matcher.GetMatchBegin()[6]);
771 
772     ASSERT_EQ('a', matcher.GetMatchEnd()[-6]);
773     ASSERT_EQ('b', matcher.GetMatchEnd()[-5]);
774     ASSERT_EQ('c', matcher.GetMatchEnd()[-4]);
775     ASSERT_EQ('-', matcher.GetMatchEnd()[-3]);
776     ASSERT_EQ('-', matcher.GetMatchEnd()[-2]);
777     ASSERT_EQ('-', matcher.GetMatchEnd()[-1]);
778     ASSERT_EQ('d', matcher.GetMatchEnd()[0]);
779     ASSERT_EQ('e', matcher.GetMatchEnd()[1]);
780     ASSERT_EQ('f', matcher.GetMatchEnd()[2]);
781     ASSERT_EQ('\0', matcher.GetMatchEnd()[3]);
782   }
783 
784   {
785     const std::string s = "abc----def";
786     ASSERT_TRUE(matcher.Apply(s));
787     ASSERT_EQ(3, std::distance(s.c_str(), matcher.GetMatchBegin()));
788     ASSERT_EQ("---", std::string(matcher.GetMatchBegin(), matcher.GetMatchEnd()));
789   }
790 
791   {
792     const std::string s = "abc---";
793     ASSERT_TRUE(matcher.Apply(s));
794     ASSERT_EQ(3, std::distance(s.c_str(), matcher.GetMatchBegin()));
795     ASSERT_EQ(s.c_str() + s.size(), matcher.GetMatchEnd());
796     ASSERT_EQ("---", std::string(matcher.GetMatchBegin(), matcher.GetMatchEnd()));
797     ASSERT_EQ("", std::string(matcher.GetMatchEnd(), s.c_str() + s.size()));
798   }
799 
800   {
801     const std::string s = "abc--def";
802     ASSERT_FALSE(matcher.Apply(s));
803     ASSERT_THROW(matcher.GetMatchBegin(), OrthancException);
804     ASSERT_THROW(matcher.GetMatchEnd(), OrthancException);
805   }
806 
807   {
808     std::string s(10u, '\0');  // String with null values
809     ASSERT_EQ(10u, s.size());
810     ASSERT_FALSE(matcher.Apply(s));
811 
812     s[9] = '-';
813     ASSERT_FALSE(matcher.Apply(s));
814 
815     s[8] = '-';
816     ASSERT_FALSE(matcher.Apply(s));
817 
818     s[7] = '-';
819     ASSERT_TRUE(matcher.Apply(s));
820     ASSERT_EQ(s.c_str() + 7, matcher.GetMatchBegin());
821     ASSERT_EQ(s.c_str() + 10, matcher.GetMatchEnd());
822     ASSERT_EQ(s.c_str() + s.size() - 3, matcher.GetMatchBegin());
823     ASSERT_EQ(s.c_str() + s.size(), matcher.GetMatchEnd());
824   }
825 }
826 
827 
828 class MultipartTester : public MultipartStreamReader::IHandler
829 {
830 private:
831   struct Part
832   {
833     MultipartStreamReader::HttpHeaders   headers_;
834     std::string  data_;
835 
PartMultipartTester::Part836     Part(const MultipartStreamReader::HttpHeaders& headers,
837          const void* part,
838          size_t size) :
839       headers_(headers),
840       data_(reinterpret_cast<const char*>(part), size)
841     {
842     }
843   };
844 
845   std::vector<Part> parts_;
846 
847 public:
HandlePart(const MultipartStreamReader::HttpHeaders & headers,const void * part,size_t size)848   virtual void HandlePart(const MultipartStreamReader::HttpHeaders& headers,
849                           const void* part,
850                           size_t size)
851   {
852     parts_.push_back(Part(headers, part, size));
853   }
854 
GetCount() const855   unsigned int GetCount() const
856   {
857     return parts_.size();
858   }
859 
GetHeaders(size_t i)860   MultipartStreamReader::HttpHeaders& GetHeaders(size_t i)
861   {
862     return parts_[i].headers_;
863   }
864 
GetData(size_t i) const865   const std::string& GetData(size_t i) const
866   {
867     return parts_[i].data_;
868   }
869 };
870 
871 
TEST(MultipartStreamReader,ParseHeaders)872 TEST(MultipartStreamReader, ParseHeaders)
873 {
874   std::string ct, b, st, header;
875 
876   {
877     MultipartStreamReader::HttpHeaders h;
878     h["hello"] = "world";
879     h["Content-Type"] = "world";  // Should be in lower-case
880     h["CONTENT-type"] = "world";  // Should be in lower-case
881     ASSERT_FALSE(MultipartStreamReader::GetMainContentType(header, h));
882   }
883 
884   {
885     MultipartStreamReader::HttpHeaders h;
886     h["content-type"] = "world";
887     ASSERT_TRUE(MultipartStreamReader::GetMainContentType(header, h));
888     ASSERT_EQ(header, "world");
889     ASSERT_FALSE(MultipartStreamReader::ParseMultipartContentType(ct, st, b, header));
890   }
891 
892   {
893     MultipartStreamReader::HttpHeaders h;
894     h["content-type"] = "multipart/related; dummy=value; boundary=1234; hello=world";
895     ASSERT_TRUE(MultipartStreamReader::GetMainContentType(header, h));
896     ASSERT_EQ(header, h["content-type"]);
897     ASSERT_TRUE(MultipartStreamReader::ParseMultipartContentType(ct, st, b, header));
898     ASSERT_EQ(ct, "multipart/related");
899     ASSERT_EQ(b, "1234");
900     ASSERT_TRUE(st.empty());
901   }
902 
903   {
904     ASSERT_FALSE(MultipartStreamReader::ParseMultipartContentType
905                  (ct, st, b, "multipart/related; boundary="));  // Empty boundary
906   }
907 
908   {
909     ASSERT_TRUE(MultipartStreamReader::ParseMultipartContentType
910                 (ct, st, b, "Multipart/Related; TYPE=Application/Dicom; Boundary=heLLO"));
911     ASSERT_EQ(ct, "multipart/related");
912     ASSERT_EQ(b, "heLLO");
913     ASSERT_EQ(st, "application/dicom");
914   }
915 
916   {
917     ASSERT_TRUE(MultipartStreamReader::ParseMultipartContentType
918                 (ct, st, b, "Multipart/Related; type=\"application/DICOM\"; Boundary=a"));
919     ASSERT_EQ(ct, "multipart/related");
920     ASSERT_EQ(b, "a");
921     ASSERT_EQ(st, "application/dicom");
922   }
923 }
924 
925 
TEST(MultipartStreamReader,ParseHeaders2)926 TEST(MultipartStreamReader, ParseHeaders2)
927 {
928   std::string main;
929   std::map<std::string, std::string> args;
930 
931   ASSERT_FALSE(MultipartStreamReader::ParseHeaderArguments(main, args, ""));
932   ASSERT_FALSE(MultipartStreamReader::ParseHeaderArguments(main, args, "     "));
933   ASSERT_FALSE(MultipartStreamReader::ParseHeaderArguments(main, args, "  ;   "));
934 
935   ASSERT_TRUE(MultipartStreamReader::ParseHeaderArguments(main, args, "hello"));
936   ASSERT_EQ("hello", main);
937   ASSERT_TRUE(args.empty());
938 
939   ASSERT_TRUE(MultipartStreamReader::ParseHeaderArguments(main, args, "hello  ;  a  = \"  b  \";c=d  ;  e=f;"));
940   ASSERT_EQ("hello", main);
941   ASSERT_EQ(3u, args.size());
942   ASSERT_EQ("  b  ", args["a"]);
943   ASSERT_EQ("d", args["c"]);
944   ASSERT_EQ("f", args["e"]);
945 
946   ASSERT_TRUE(MultipartStreamReader::ParseHeaderArguments(main, args, "    hello  ;;;;  ;  "));
947   ASSERT_EQ("hello", main);
948   ASSERT_TRUE(args.empty());
949 
950   ASSERT_FALSE(MultipartStreamReader::ParseHeaderArguments(main, args, "hello;a=b;c=d;a=f"));
951 
952   ASSERT_TRUE(MultipartStreamReader::ParseHeaderArguments(main, args, "multipart/related; dummy=value; boundary=1234; hello=world"));
953   ASSERT_EQ("multipart/related", main);
954   ASSERT_EQ(3u, args.size());
955   ASSERT_EQ("value", args["dummy"]);
956   ASSERT_EQ("1234", args["boundary"]);
957   ASSERT_EQ("world", args["hello"]);
958 
959   ASSERT_TRUE(MultipartStreamReader::ParseHeaderArguments(main, args, "multipart/related; boundary="));
960   ASSERT_EQ("multipart/related", main);
961   ASSERT_EQ(1u, args.size());
962   ASSERT_EQ("", args["boundary"]);
963 
964   ASSERT_TRUE(MultipartStreamReader::ParseHeaderArguments(main, args, "multipart/related; boundary"));
965   ASSERT_EQ("multipart/related", main);
966   ASSERT_EQ(1u, args.size());
967   ASSERT_EQ("", args["boundary"]);
968 
969   ASSERT_TRUE(MultipartStreamReader::ParseHeaderArguments(main, args, "Multipart/Related; TYPE=Application/Dicom; Boundary=heLLO"));
970   ASSERT_EQ("multipart/related", main);
971   ASSERT_EQ(2u, args.size());
972   ASSERT_EQ("Application/Dicom", args["type"]);
973   ASSERT_EQ("heLLO", args["boundary"]);
974 
975   ASSERT_TRUE(MultipartStreamReader::ParseHeaderArguments(main, args, "Multipart/Related; type=\"application/DICOM\"; Boundary=a"));
976   ASSERT_EQ("multipart/related", main);
977   ASSERT_EQ(2u, args.size());
978   ASSERT_EQ("application/DICOM", args["type"]);
979   ASSERT_EQ("a", args["boundary"]);
980 }
981 
982 
TEST(MultipartStreamReader,BytePerByte)983 TEST(MultipartStreamReader, BytePerByte)
984 {
985   std::string stream = "GARBAGE";
986 
987   std::string boundary = "123456789123456789";
988 
989   {
990     for (size_t i = 0; i < 10; i++)
991     {
992       std::string f = "hello " + boost::lexical_cast<std::string>(i);
993 
994       stream += "\r\n--" + boundary + "\r\n";
995       if (i % 2 == 0)
996         stream += "Content-Length: " + boost::lexical_cast<std::string>(f.size()) + "\r\n";
997       stream += "Content-Type: toto " + boost::lexical_cast<std::string>(i) + "\r\n\r\n";
998       stream += f;
999     }
1000 
1001     stream += "\r\n--" + boundary + "--";
1002     stream += "GARBAGE";
1003   }
1004 
1005   for (unsigned int k = 0; k < 2; k++)
1006   {
1007     MultipartTester decoded;
1008 
1009     MultipartStreamReader reader(boundary);
1010     reader.SetBlockSize(1);
1011     reader.SetHandler(decoded);
1012 
1013     if (k == 0)
1014     {
1015       for (size_t i = 0; i < stream.size(); i++)
1016       {
1017         reader.AddChunk(&stream[i], 1);
1018       }
1019     }
1020     else
1021     {
1022       reader.AddChunk(stream);
1023     }
1024 
1025     reader.CloseStream();
1026 
1027     ASSERT_EQ(10u, decoded.GetCount());
1028 
1029     for (size_t i = 0; i < 10; i++)
1030     {
1031       ASSERT_EQ("hello " + boost::lexical_cast<std::string>(i), decoded.GetData(i));
1032       ASSERT_EQ("toto " + boost::lexical_cast<std::string>(i), decoded.GetHeaders(i)["content-type"]);
1033 
1034       if (i % 2 == 0)
1035       {
1036         ASSERT_EQ(2u, decoded.GetHeaders(i).size());
1037         ASSERT_TRUE(decoded.GetHeaders(i).find("content-length") != decoded.GetHeaders(i).end());
1038       }
1039     }
1040   }
1041 }
1042 
1043 
TEST(MultipartStreamReader,Issue190)1044 TEST(MultipartStreamReader, Issue190)
1045 {
1046   // https://bugs.orthanc-server.com/show_bug.cgi?id=190
1047   // https://hg.orthanc-server.com/orthanc-dicomweb/rev/6dc2f79b5579
1048 
1049   std::map<std::string, std::string> headers;
1050   headers["content-type"] = "multipart/related; type=application/dicom; boundary=0f3cf5c0-70e0-41ef-baef-c6f9f65ec3e1";
1051 
1052   {
1053     std::string tmp, contentType, subType, boundary;
1054     ASSERT_TRUE(Orthanc::MultipartStreamReader::GetMainContentType(tmp, headers));
1055     ASSERT_TRUE(Orthanc::MultipartStreamReader::ParseMultipartContentType(contentType, subType, boundary, tmp));
1056     ASSERT_EQ("multipart/related", contentType);
1057     ASSERT_EQ("application/dicom", subType);
1058     ASSERT_EQ("0f3cf5c0-70e0-41ef-baef-c6f9f65ec3e1", boundary);
1059   }
1060 
1061   headers["content-type"] = "multipart/related; type=\"application/dicom\"; boundary=\"0f3cf5c0-70e0-41ef-baef-c6f9f65ec3e1\"";
1062 
1063   {
1064     std::string tmp, contentType, subType, boundary;
1065     ASSERT_TRUE(Orthanc::MultipartStreamReader::GetMainContentType(tmp, headers));
1066     ASSERT_TRUE(Orthanc::MultipartStreamReader::ParseMultipartContentType(contentType, subType, boundary, tmp));
1067     ASSERT_EQ("multipart/related", contentType);
1068     ASSERT_EQ("application/dicom", subType);
1069     ASSERT_EQ("0f3cf5c0-70e0-41ef-baef-c6f9f65ec3e1", boundary);
1070   }
1071 }
1072 
1073 
TEST(WebServiceParameters,Url)1074 TEST(WebServiceParameters, Url)
1075 {
1076   WebServiceParameters w;
1077 
1078   ASSERT_THROW(w.SetUrl("ssh://coucou"), OrthancException);
1079   w.SetUrl("http://coucou");
1080   w.SetUrl("https://coucou");
1081   ASSERT_THROW(w.SetUrl("httpss://coucou"), OrthancException);
1082   ASSERT_THROW(w.SetUrl(""), OrthancException);
1083 
1084   // New in Orthanc 1.7.2: Allow relative URLs (for DICOMweb in Stone)
1085   w.SetUrl("coucou");
1086   w.SetUrl("/coucou");
1087 }
1088 
1089 
TEST(ChunkedBuffer,DISABLED_Large)1090 TEST(ChunkedBuffer, DISABLED_Large)
1091 {
1092   const size_t LARGE = 60 * 1024 * 1024;
1093 
1094   ChunkedBuffer b;
1095   for (size_t i = 0; i < LARGE; i++)
1096   {
1097     b.AddChunk(boost::lexical_cast<std::string>(i % 10));
1098   }
1099 
1100   std::string s;
1101   b.Flatten(s);
1102   ASSERT_EQ(LARGE, s.size());
1103   ASSERT_EQ(0u, b.GetNumBytes());
1104 
1105   for (size_t i = 0; i < LARGE; i++)
1106   {
1107     ASSERT_EQ(static_cast<char>('0' + (i % 10)), s[i]);
1108   }
1109 
1110   b.Flatten(s);
1111   ASSERT_EQ(0u, s.size());
1112 }
1113 
1114 
TEST(ChunkedBuffer,Pending)1115 TEST(ChunkedBuffer, Pending)
1116 {
1117   ChunkedBuffer b;
1118 
1119   for (size_t pendingSize = 0; pendingSize < 16; pendingSize++)
1120   {
1121     b.SetPendingBufferSize(pendingSize);
1122     ASSERT_EQ(pendingSize, b.GetPendingBufferSize());
1123 
1124     unsigned int pos = 0;
1125     unsigned int iteration = 0;
1126 
1127     while (pos < 1024)
1128     {
1129       size_t chunkSize = (iteration % 17);
1130 
1131       std::string chunk;
1132       chunk.resize(chunkSize);
1133       for (size_t i = 0; i < chunkSize; i++)
1134       {
1135         chunk[i] = '0' + (pos % 10);
1136         pos++;
1137       }
1138 
1139       b.AddChunk(chunk);
1140 
1141       iteration ++;
1142     }
1143 
1144     std::string s;
1145     b.Flatten(s);
1146     ASSERT_EQ(0u, b.GetNumBytes());
1147     ASSERT_EQ(pos, s.size());
1148 
1149     for (size_t i = 0; i < s.size(); i++)
1150     {
1151       ASSERT_EQ(static_cast<char>('0' + (i % 10)), s[i]);
1152     }
1153   }
1154 }
1155 
1156 
1157 
1158 #if ORTHANC_SANDBOXED != 1
1159 
1160 
1161 namespace
1162 {
1163   class TotoBody : public HttpClient::IRequestBody
1164   {
1165   private:
1166     size_t size_;
1167     size_t chunkSize_;
1168     size_t pos_;
1169 
1170   public:
TotoBody(size_t size,size_t chunkSize)1171     TotoBody(size_t size,
1172              size_t chunkSize) :
1173       size_(size),
1174       chunkSize_(chunkSize),
1175       pos_(0)
1176     {
1177     }
1178 
ReadNextChunk(std::string & chunk)1179     virtual bool ReadNextChunk(std::string& chunk) ORTHANC_OVERRIDE
1180     {
1181       if (pos_ == size_)
1182       {
1183         return false;
1184       }
1185 
1186       chunk.clear();
1187       chunk.resize(chunkSize_);
1188 
1189       size_t i = 0;
1190       while (pos_ < size_ &&
1191              i < chunk.size())
1192       {
1193         chunk[i] = '0' + (pos_ % 7);
1194         pos_++;
1195         i++;
1196       }
1197 
1198       if (i < chunk.size())
1199       {
1200         chunk.erase(i, chunk.size());
1201       }
1202 
1203       return true;
1204     }
1205   };
1206 
1207   class TotoServer : public IHttpHandler
1208   {
1209   public:
CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader> & target,RequestOrigin origin,const char * remoteIp,const char * username,HttpMethod method,const UriComponents & uri,const HttpToolbox::Arguments & headers)1210     virtual bool CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target,
1211                                             RequestOrigin origin,
1212                                             const char* remoteIp,
1213                                             const char* username,
1214                                             HttpMethod method,
1215                                             const UriComponents& uri,
1216                                             const HttpToolbox::Arguments& headers) ORTHANC_OVERRIDE
1217     {
1218       return false;
1219     }
1220 
Handle(HttpOutput & output,RequestOrigin origin,const char * remoteIp,const char * username,HttpMethod method,const UriComponents & uri,const HttpToolbox::Arguments & headers,const HttpToolbox::GetArguments & getArguments,const void * bodyData,size_t bodySize)1221     virtual bool Handle(HttpOutput& output,
1222                         RequestOrigin origin,
1223                         const char* remoteIp,
1224                         const char* username,
1225                         HttpMethod method,
1226                         const UriComponents& uri,
1227                         const HttpToolbox::Arguments& headers,
1228                         const HttpToolbox::GetArguments& getArguments,
1229                         const void* bodyData,
1230                         size_t bodySize) ORTHANC_OVERRIDE
1231     {
1232       printf("received %d\n", static_cast<int>(bodySize));
1233 
1234       const uint8_t* b = reinterpret_cast<const uint8_t*>(bodyData);
1235 
1236       for (size_t i = 0; i < bodySize; i++)
1237       {
1238         if (b[i] != ('0' + i % 7))
1239         {
1240           throw;
1241         }
1242       }
1243 
1244       output.Answer("ok");
1245       return true;
1246     }
1247   };
1248 }
1249 
1250 
1251 
1252 #include "../Sources/HttpServer/HttpServer.h"
1253 
TEST(HttpClient,DISABLED_Issue156_Slow)1254 TEST(HttpClient, DISABLED_Issue156_Slow)
1255 {
1256   // https://bugs.orthanc-server.com/show_bug.cgi?id=156
1257 
1258   TotoServer handler;
1259   HttpServer server;
1260   server.SetPortNumber(5000);
1261   server.Register(handler);
1262   server.Start();
1263 
1264   WebServiceParameters w;
1265   w.SetUrl("http://localhost:5000");
1266 
1267   // This is slow in Orthanc <= 1.5.8 (issue 156)
1268   TotoBody body(600 * 1024 * 1024, 6 * 1024 * 1024 - 17);
1269 
1270   HttpClient c(w, "toto");
1271   c.SetMethod(HttpMethod_Post);
1272   c.AddHeader("Expect", "");
1273   c.AddHeader("Transfer-Encoding", "chunked");
1274   c.SetBody(body);
1275 
1276   std::string s;
1277   ASSERT_TRUE(c.Apply(s));
1278   ASSERT_EQ("ok", s);
1279 
1280   server.Stop();
1281 }
1282 
1283 
TEST(HttpClient,DISABLED_Issue156_Crash)1284 TEST(HttpClient, DISABLED_Issue156_Crash)
1285 {
1286   TotoServer handler;
1287   HttpServer server;
1288   server.SetPortNumber(5000);
1289   server.Register(handler);
1290   server.Start();
1291 
1292   WebServiceParameters w;
1293   w.SetUrl("http://localhost:5000");
1294 
1295   // This crashes Orthanc 1.6.0 to 1.7.2
1296   TotoBody body(32 * 1024, 1);
1297 
1298   HttpClient c(w, "toto");
1299   c.SetMethod(HttpMethod_Post);
1300   c.AddHeader("Expect", "");
1301   c.AddHeader("Transfer-Encoding", "chunked");
1302   c.SetBody(body);
1303 
1304   std::string s;
1305   ASSERT_TRUE(c.Apply(s));
1306   ASSERT_EQ("ok", s);
1307 
1308   server.Stop();
1309 }
1310 #endif
1311