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