comparison OrthancFramework/UnitTestsSources/RestApiTests.cpp @ 4044:d25f4c0fa160 framework

splitting code into OrthancFramework and OrthancServer
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 10 Jun 2020 20:30:34 +0200
parents UnitTestsSources/RestApiTests.cpp@27628b0f6ada
children 05b8fd21089c
comparison
equal deleted inserted replaced
4043:6c6239aec462 4044:d25f4c0fa160
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-2020 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 General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
11 *
12 * In addition, as a special exception, the copyright holders of this
13 * program give permission to link the code of its release with the
14 * OpenSSL project's "OpenSSL" library (or with modified versions of it
15 * that use the same license as the "OpenSSL" library), and distribute
16 * the linked executables. You must obey the GNU General Public License
17 * in all respects for all of the code used other than "OpenSSL". If you
18 * modify file(s) with this exception, you may extend this exception to
19 * your version of the file(s), but you are not obligated to do so. If
20 * you do not wish to do so, delete this exception statement from your
21 * version. If you delete this exception statement from all source files
22 * in the program, then also delete it here.
23 *
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27 * General Public License for more details.
28 *
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31 **/
32
33
34 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
35 # include <OrthancFramework.h>
36 #endif
37
38 #include "PrecompiledHeadersUnitTests.h"
39 #include "gtest/gtest.h"
40
41 #include <ctype.h>
42 #include <boost/lexical_cast.hpp>
43 #include <algorithm>
44
45 #include "../Core/ChunkedBuffer.h"
46 #include "../Core/HttpClient.h"
47 #include "../Core/Logging.h"
48 #include "../Core/SystemToolbox.h"
49 #include "../Core/RestApi/RestApi.h"
50 #include "../Core/OrthancException.h"
51 #include "../Core/Compression/ZlibCompressor.h"
52 #include "../Core/RestApi/RestApiHierarchy.h"
53 #include "../Core/HttpServer/HttpContentNegociation.h"
54 #include "../Core/HttpServer/MultipartStreamReader.h"
55
56
57 using namespace Orthanc;
58
59 #if !defined(UNIT_TESTS_WITH_HTTP_CONNEXIONS)
60 #error "Please set UNIT_TESTS_WITH_HTTP_CONNEXIONS"
61 #endif
62
63
64
65 TEST(HttpClient, Basic)
66 {
67 HttpClient c;
68 ASSERT_FALSE(c.IsVerbose());
69 c.SetVerbose(true);
70 ASSERT_TRUE(c.IsVerbose());
71 c.SetVerbose(false);
72 ASSERT_FALSE(c.IsVerbose());
73
74 #if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1
75 // The "http://www.orthanc-server.com/downloads/third-party/" does
76 // not automatically redirect to HTTPS, so we cas use it even if the
77 // OpenSSL/HTTPS support is disabled in curl
78 const std::string BASE = "http://www.orthanc-server.com/downloads/third-party/";
79
80 Json::Value v;
81 c.SetUrl(BASE + "Product.json");
82
83 c.Apply(v);
84 ASSERT_TRUE(v.type() == Json::objectValue);
85 ASSERT_TRUE(v.isMember("Description"));
86 #endif
87 }
88
89
90 #if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1 && ORTHANC_ENABLE_SSL == 1
91
92 /**
93 The HTTPS CA certificates for BitBucket were extracted as follows:
94
95 (1) We retrieve the certification chain of BitBucket:
96
97 # echo | openssl s_client -showcerts -connect www.bitbucket.org:443
98
99 (2) We see that the certification authority (CA) is
100 "www.digicert.com", and the root certificate is "DigiCert High
101 Assurance EV Root CA". As a consequence, we navigate to DigiCert to
102 find the URL to this CA certificate:
103
104 firefox https://www.digicert.com/digicert-root-certificates.htm
105
106 (3) Once we get the URL to the CA certificate, we convert it to a C
107 macro that can be used by libcurl:
108
109 # cd UnitTestsSources
110 # ../Resources/RetrieveCACertificates.py BITBUCKET_CERTIFICATES https://www.digicert.com/CACerts/DigiCertHighAssuranceEVRootCA.crt > BitbucketCACertificates.h
111 **/
112
113 #include "BitbucketCACertificates.h"
114
115 TEST(HttpClient, Ssl)
116 {
117 SystemToolbox::WriteFile(BITBUCKET_CERTIFICATES, "UnitTestsResults/bitbucket.cert");
118
119 /*{
120 std::string s;
121 SystemToolbox::ReadFile(s, "/usr/share/ca-certificates/mozilla/WoSign.crt");
122 SystemToolbox::WriteFile(s, "UnitTestsResults/bitbucket.cert");
123 }*/
124
125 HttpClient c;
126 c.SetHttpsVerifyPeers(true);
127 c.SetHttpsCACertificates("UnitTestsResults/bitbucket.cert");
128
129 // Test file modified on 2020-04-20, in order to use a git
130 // repository on BitBucket instead of a Mercurial repository
131 // (because Mercurial support disappears on 2020-05-31)
132 c.SetUrl("https://bitbucket.org/osimis/orthanc-setup-samples/raw/master/docker/serve-folders/orthanc/serve-folders.json");
133
134 Json::Value v;
135 c.Apply(v);
136 ASSERT_TRUE(v.isMember("ServeFolders"));
137 }
138
139 TEST(HttpClient, SslNoVerification)
140 {
141 HttpClient c;
142 c.SetHttpsVerifyPeers(false);
143 c.SetUrl("https://bitbucket.org/osimis/orthanc-setup-samples/raw/master/docker/serve-folders/orthanc/serve-folders.json");
144
145 Json::Value v;
146 c.Apply(v);
147 ASSERT_TRUE(v.isMember("ServeFolders"));
148 }
149
150 #endif
151
152
153 TEST(RestApi, ChunkedBuffer)
154 {
155 ChunkedBuffer b;
156 ASSERT_EQ(0u, b.GetNumBytes());
157
158 b.AddChunk("hello", 5);
159 ASSERT_EQ(5u, b.GetNumBytes());
160
161 b.AddChunk("world", 5);
162 ASSERT_EQ(10u, b.GetNumBytes());
163
164 std::string s;
165 b.Flatten(s);
166 ASSERT_EQ("helloworld", s);
167 }
168
169 TEST(RestApi, ParseCookies)
170 {
171 IHttpHandler::Arguments headers;
172 IHttpHandler::Arguments cookies;
173
174 headers["cookie"] = "a=b;c=d;;;e=f;;g=h;";
175 HttpToolbox::ParseCookies(cookies, headers);
176 ASSERT_EQ(4u, cookies.size());
177 ASSERT_EQ("b", cookies["a"]);
178 ASSERT_EQ("d", cookies["c"]);
179 ASSERT_EQ("f", cookies["e"]);
180 ASSERT_EQ("h", cookies["g"]);
181
182 headers["cookie"] = " name = value ; name2=value2";
183 HttpToolbox::ParseCookies(cookies, headers);
184 ASSERT_EQ(2u, cookies.size());
185 ASSERT_EQ("value", cookies["name"]);
186 ASSERT_EQ("value2", cookies["name2"]);
187
188 headers["cookie"] = " ;;; ";
189 HttpToolbox::ParseCookies(cookies, headers);
190 ASSERT_EQ(0u, cookies.size());
191
192 headers["cookie"] = " ; n=v ;; ";
193 HttpToolbox::ParseCookies(cookies, headers);
194 ASSERT_EQ(1u, cookies.size());
195 ASSERT_EQ("v", cookies["n"]);
196 }
197
198 TEST(RestApi, RestApiPath)
199 {
200 IHttpHandler::Arguments args;
201 UriComponents trail;
202
203 {
204 RestApiPath uri("/coucou/{abc}/d/*");
205 ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/e/f/g"));
206 ASSERT_EQ(1u, args.size());
207 ASSERT_EQ(3u, trail.size());
208 ASSERT_EQ("moi", args["abc"]);
209 ASSERT_EQ("e", trail[0]);
210 ASSERT_EQ("f", trail[1]);
211 ASSERT_EQ("g", trail[2]);
212
213 ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/f"));
214 ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/"));
215 ASSERT_FALSE(uri.Match(args, trail, "/a/moi/d"));
216 ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi"));
217
218 ASSERT_EQ(3u, uri.GetLevelCount());
219 ASSERT_TRUE(uri.IsUniversalTrailing());
220
221 ASSERT_EQ("coucou", uri.GetLevelName(0));
222 ASSERT_THROW(uri.GetWildcardName(0), OrthancException);
223
224 ASSERT_EQ("abc", uri.GetWildcardName(1));
225 ASSERT_THROW(uri.GetLevelName(1), OrthancException);
226
227 ASSERT_EQ("d", uri.GetLevelName(2));
228 ASSERT_THROW(uri.GetWildcardName(2), OrthancException);
229 }
230
231 {
232 RestApiPath uri("/coucou/{abc}/d");
233 ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/d/e/f/g"));
234 ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d"));
235 ASSERT_EQ(1u, args.size());
236 ASSERT_EQ(0u, trail.size());
237 ASSERT_EQ("moi", args["abc"]);
238
239 ASSERT_EQ(3u, uri.GetLevelCount());
240 ASSERT_FALSE(uri.IsUniversalTrailing());
241
242 ASSERT_EQ("coucou", uri.GetLevelName(0));
243 ASSERT_THROW(uri.GetWildcardName(0), OrthancException);
244
245 ASSERT_EQ("abc", uri.GetWildcardName(1));
246 ASSERT_THROW(uri.GetLevelName(1), OrthancException);
247
248 ASSERT_EQ("d", uri.GetLevelName(2));
249 ASSERT_THROW(uri.GetWildcardName(2), OrthancException);
250 }
251
252 {
253 RestApiPath uri("/*");
254 ASSERT_TRUE(uri.Match(args, trail, "/a/b/c"));
255 ASSERT_EQ(0u, args.size());
256 ASSERT_EQ(3u, trail.size());
257 ASSERT_EQ("a", trail[0]);
258 ASSERT_EQ("b", trail[1]);
259 ASSERT_EQ("c", trail[2]);
260
261 ASSERT_EQ(0u, uri.GetLevelCount());
262 ASSERT_TRUE(uri.IsUniversalTrailing());
263 }
264 }
265
266
267
268
269
270
271 static int testValue;
272
273 template <int value>
274 static void SetValue(RestApiGetCall& get)
275 {
276 testValue = value;
277 }
278
279
280 static bool GetDirectory(Json::Value& target,
281 RestApiHierarchy& hierarchy,
282 const std::string& uri)
283 {
284 UriComponents p;
285 Toolbox::SplitUriComponents(p, uri);
286 return hierarchy.GetDirectory(target, p);
287 }
288
289
290
291 namespace
292 {
293 class MyVisitor : public RestApiHierarchy::IVisitor
294 {
295 public:
296 virtual bool Visit(const RestApiHierarchy::Resource& resource,
297 const UriComponents& uri,
298 const IHttpHandler::Arguments& components,
299 const UriComponents& trailing) ORTHANC_OVERRIDE
300 {
301 return resource.Handle(*(RestApiGetCall*) NULL);
302 }
303 };
304 }
305
306
307 static bool HandleGet(RestApiHierarchy& hierarchy,
308 const std::string& uri)
309 {
310 UriComponents p;
311 Toolbox::SplitUriComponents(p, uri);
312 MyVisitor visitor;
313 return hierarchy.LookupResource(p, visitor);
314 }
315
316
317 TEST(RestApi, RestApiHierarchy)
318 {
319 RestApiHierarchy root;
320 root.Register("/hello/world/test", SetValue<1>);
321 root.Register("/hello/world/test2", SetValue<2>);
322 root.Register("/hello/{world}/test3/test4", SetValue<3>);
323 root.Register("/hello2/*", SetValue<4>);
324
325 Json::Value m;
326 root.CreateSiteMap(m);
327 std::cout << m;
328
329 Json::Value d;
330 ASSERT_FALSE(GetDirectory(d, root, "/hello"));
331
332 ASSERT_TRUE(GetDirectory(d, root, "/hello/a"));
333 ASSERT_EQ(1u, d.size());
334 ASSERT_EQ("test3", d[0].asString());
335
336 ASSERT_TRUE(GetDirectory(d, root, "/hello/world"));
337 ASSERT_EQ(2u, d.size());
338
339 ASSERT_TRUE(GetDirectory(d, root, "/hello/a/test3"));
340 ASSERT_EQ(1u, d.size());
341 ASSERT_EQ("test4", d[0].asString());
342
343 ASSERT_TRUE(GetDirectory(d, root, "/hello/world/test"));
344 ASSERT_TRUE(GetDirectory(d, root, "/hello/world/test2"));
345 ASSERT_FALSE(GetDirectory(d, root, "/hello2"));
346
347 testValue = 0;
348 ASSERT_TRUE(HandleGet(root, "/hello/world/test"));
349 ASSERT_EQ(testValue, 1);
350 ASSERT_TRUE(HandleGet(root, "/hello/world/test2"));
351 ASSERT_EQ(testValue, 2);
352 ASSERT_TRUE(HandleGet(root, "/hello/b/test3/test4"));
353 ASSERT_EQ(testValue, 3);
354 ASSERT_FALSE(HandleGet(root, "/hello/b/test3/test"));
355 ASSERT_EQ(testValue, 3);
356 ASSERT_TRUE(HandleGet(root, "/hello2/a/b"));
357 ASSERT_EQ(testValue, 4);
358 }
359
360
361
362
363
364 namespace
365 {
366 class AcceptHandler : public HttpContentNegociation::IHandler
367 {
368 private:
369 std::string type_;
370 std::string subtype_;
371
372 public:
373 AcceptHandler()
374 {
375 Reset();
376 }
377
378 void Reset()
379 {
380 Handle("nope", "nope");
381 }
382
383 const std::string& GetType() const
384 {
385 return type_;
386 }
387
388 const std::string& GetSubType() const
389 {
390 return subtype_;
391 }
392
393 virtual void Handle(const std::string& type,
394 const std::string& subtype) ORTHANC_OVERRIDE
395 {
396 type_ = type;
397 subtype_ = subtype;
398 }
399 };
400 }
401
402
403 TEST(RestApi, HttpContentNegociation)
404 {
405 // Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
406
407 AcceptHandler h;
408
409 {
410 HttpContentNegociation d;
411 d.Register("audio/mp3", h);
412 d.Register("audio/basic", h);
413
414 ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/basic"));
415 ASSERT_EQ("audio", h.GetType());
416 ASSERT_EQ("basic", h.GetSubType());
417
418 ASSERT_TRUE(d.Apply("audio/*; q=0.2, audio/nope"));
419 ASSERT_EQ("audio", h.GetType());
420 ASSERT_EQ("mp3", h.GetSubType());
421
422 ASSERT_FALSE(d.Apply("application/*; q=0.2, application/pdf"));
423
424 ASSERT_TRUE(d.Apply("*/*; application/*; q=0.2, application/pdf"));
425 ASSERT_EQ("audio", h.GetType());
426 }
427
428 // "This would be interpreted as "text/html and text/x-c are the
429 // preferred media types, but if they do not exist, then send the
430 // text/x-dvi entity, and if that does not exist, send the
431 // text/plain entity.""
432 const std::string T1 = "text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c";
433
434 {
435 HttpContentNegociation d;
436 d.Register("text/plain", h);
437 d.Register("text/html", h);
438 d.Register("text/x-dvi", h);
439 ASSERT_TRUE(d.Apply(T1));
440 ASSERT_EQ("text", h.GetType());
441 ASSERT_EQ("html", h.GetSubType());
442 }
443
444 {
445 HttpContentNegociation d;
446 d.Register("text/plain", h);
447 d.Register("text/x-dvi", h);
448 d.Register("text/x-c", h);
449 ASSERT_TRUE(d.Apply(T1));
450 ASSERT_EQ("text", h.GetType());
451 ASSERT_EQ("x-c", h.GetSubType());
452 }
453
454 {
455 HttpContentNegociation d;
456 d.Register("text/plain", h);
457 d.Register("text/x-dvi", h);
458 d.Register("text/x-c", h);
459 d.Register("text/html", h);
460 ASSERT_TRUE(d.Apply(T1));
461 ASSERT_EQ("text", h.GetType());
462 ASSERT_TRUE(h.GetSubType() == "x-c" || h.GetSubType() == "html");
463 }
464
465 {
466 HttpContentNegociation d;
467 d.Register("text/plain", h);
468 d.Register("text/x-dvi", h);
469 ASSERT_TRUE(d.Apply(T1));
470 ASSERT_EQ("text", h.GetType());
471 ASSERT_EQ("x-dvi", h.GetSubType());
472 }
473
474 {
475 HttpContentNegociation d;
476 d.Register("text/plain", h);
477 ASSERT_TRUE(d.Apply(T1));
478 ASSERT_EQ("text", h.GetType());
479 ASSERT_EQ("plain", h.GetSubType());
480 }
481 }
482
483
484 TEST(WebServiceParameters, Serialization)
485 {
486 {
487 Json::Value v = Json::arrayValue;
488 v.append("http://localhost:8042/");
489
490 WebServiceParameters p(v);
491 ASSERT_FALSE(p.IsAdvancedFormatNeeded());
492
493 Json::Value v2;
494 p.Serialize(v2, false, true);
495 ASSERT_EQ(v, v2);
496
497 WebServiceParameters p2(v2);
498 ASSERT_EQ("http://localhost:8042/", p2.GetUrl());
499 ASSERT_TRUE(p2.GetUsername().empty());
500 ASSERT_TRUE(p2.GetPassword().empty());
501 ASSERT_TRUE(p2.GetCertificateFile().empty());
502 ASSERT_TRUE(p2.GetCertificateKeyFile().empty());
503 ASSERT_TRUE(p2.GetCertificateKeyPassword().empty());
504 ASSERT_FALSE(p2.IsPkcs11Enabled());
505 }
506
507 {
508 Json::Value v = Json::arrayValue;
509 v.append("http://localhost:8042/");
510 v.append("user");
511 v.append("pass");
512
513 WebServiceParameters p(v);
514 ASSERT_FALSE(p.IsAdvancedFormatNeeded());
515 ASSERT_EQ("http://localhost:8042/", p.GetUrl());
516 ASSERT_EQ("user", p.GetUsername());
517 ASSERT_EQ("pass", p.GetPassword());
518 ASSERT_TRUE(p.GetCertificateFile().empty());
519 ASSERT_TRUE(p.GetCertificateKeyFile().empty());
520 ASSERT_TRUE(p.GetCertificateKeyPassword().empty());
521 ASSERT_FALSE(p.IsPkcs11Enabled());
522
523 Json::Value v2;
524 p.Serialize(v2, false, true);
525 ASSERT_EQ(v, v2);
526
527 p.Serialize(v2, false, false /* no password */);
528 WebServiceParameters p2(v2);
529 ASSERT_EQ(Json::arrayValue, v2.type());
530 ASSERT_EQ(3u, v2.size());
531 ASSERT_EQ("http://localhost:8042/", v2[0u].asString());
532 ASSERT_EQ("user", v2[1u].asString());
533 ASSERT_TRUE(v2[2u].asString().empty());
534 }
535
536 {
537 Json::Value v = Json::arrayValue;
538 v.append("http://localhost:8042/");
539
540 WebServiceParameters p(v);
541 ASSERT_FALSE(p.IsAdvancedFormatNeeded());
542 p.SetPkcs11Enabled(true);
543 ASSERT_TRUE(p.IsAdvancedFormatNeeded());
544
545 Json::Value v2;
546 p.Serialize(v2, false, true);
547 WebServiceParameters p2(v2);
548
549 ASSERT_EQ(Json::objectValue, v2.type());
550 ASSERT_EQ(3u, v2.size());
551 ASSERT_EQ("http://localhost:8042/", v2["Url"].asString());
552 ASSERT_TRUE(v2["Pkcs11"].asBool());
553 ASSERT_EQ(Json::objectValue, v2["HttpHeaders"].type());
554 ASSERT_EQ(0u, v2["HttpHeaders"].size());
555 }
556
557 {
558 Json::Value v = Json::arrayValue;
559 v.append("http://localhost:8042/");
560
561 WebServiceParameters p(v);
562 ASSERT_FALSE(p.IsAdvancedFormatNeeded());
563 p.SetClientCertificate("a", "b", "c");
564 ASSERT_TRUE(p.IsAdvancedFormatNeeded());
565
566 Json::Value v2;
567 p.Serialize(v2, false, true);
568 WebServiceParameters p2(v2);
569
570 ASSERT_EQ(Json::objectValue, v2.type());
571 ASSERT_EQ(6u, v2.size());
572 ASSERT_EQ("http://localhost:8042/", v2["Url"].asString());
573 ASSERT_EQ("a", v2["CertificateFile"].asString());
574 ASSERT_EQ("b", v2["CertificateKeyFile"].asString());
575 ASSERT_EQ("c", v2["CertificateKeyPassword"].asString());
576 ASSERT_FALSE(v2["Pkcs11"].asBool());
577 ASSERT_EQ(Json::objectValue, v2["HttpHeaders"].type());
578 ASSERT_EQ(0u, v2["HttpHeaders"].size());
579 }
580
581 {
582 Json::Value v = Json::arrayValue;
583 v.append("http://localhost:8042/");
584
585 WebServiceParameters p(v);
586 ASSERT_FALSE(p.IsAdvancedFormatNeeded());
587 p.AddHttpHeader("a", "b");
588 p.AddHttpHeader("c", "d");
589 ASSERT_TRUE(p.IsAdvancedFormatNeeded());
590
591 Json::Value v2;
592 p.Serialize(v2, false, true);
593 WebServiceParameters p2(v2);
594
595 ASSERT_EQ(Json::objectValue, v2.type());
596 ASSERT_EQ(3u, v2.size());
597 ASSERT_EQ("http://localhost:8042/", v2["Url"].asString());
598 ASSERT_FALSE(v2["Pkcs11"].asBool());
599 ASSERT_EQ(Json::objectValue, v2["HttpHeaders"].type());
600 ASSERT_EQ(2u, v2["HttpHeaders"].size());
601 ASSERT_EQ("b", v2["HttpHeaders"]["a"].asString());
602 ASSERT_EQ("d", v2["HttpHeaders"]["c"].asString());
603
604 std::set<std::string> a;
605 p2.ListHttpHeaders(a);
606 ASSERT_EQ(2u, a.size());
607 ASSERT_TRUE(a.find("a") != a.end());
608 ASSERT_TRUE(a.find("c") != a.end());
609
610 std::string s;
611 ASSERT_TRUE(p2.LookupHttpHeader(s, "a")); ASSERT_EQ("b", s);
612 ASSERT_TRUE(p2.LookupHttpHeader(s, "c")); ASSERT_EQ("d", s);
613 ASSERT_FALSE(p2.LookupHttpHeader(s, "nope"));
614 }
615 }
616
617
618 TEST(WebServiceParameters, UserProperties)
619 {
620 Json::Value v = Json::nullValue;
621
622 {
623 WebServiceParameters p;
624 p.SetUrl("http://localhost:8042/");
625 ASSERT_FALSE(p.IsAdvancedFormatNeeded());
626
627 ASSERT_THROW(p.AddUserProperty("Url", "nope"), OrthancException);
628 p.AddUserProperty("Hello", "world");
629 p.AddUserProperty("a", "b");
630 ASSERT_TRUE(p.IsAdvancedFormatNeeded());
631
632 p.Serialize(v, false, true);
633
634 p.ClearUserProperties();
635 ASSERT_FALSE(p.IsAdvancedFormatNeeded());
636 }
637
638 {
639 WebServiceParameters p(v);
640 ASSERT_TRUE(p.IsAdvancedFormatNeeded());
641 ASSERT_TRUE(p.GetHttpHeaders().empty());
642
643 std::set<std::string> tmp;
644 p.ListUserProperties(tmp);
645 ASSERT_EQ(2u, tmp.size());
646 ASSERT_TRUE(tmp.find("a") != tmp.end());
647 ASSERT_TRUE(tmp.find("Hello") != tmp.end());
648 ASSERT_TRUE(tmp.find("hello") == tmp.end());
649
650 std::string s;
651 ASSERT_TRUE(p.LookupUserProperty(s, "a")); ASSERT_TRUE(s == "b");
652 ASSERT_TRUE(p.LookupUserProperty(s, "Hello")); ASSERT_TRUE(s == "world");
653 ASSERT_FALSE(p.LookupUserProperty(s, "hello"));
654 }
655 }
656
657
658 TEST(StringMatcher, Basic)
659 {
660 StringMatcher matcher("---");
661
662 ASSERT_THROW(matcher.GetMatchBegin(), OrthancException);
663
664 {
665 const std::string s = "";
666 ASSERT_FALSE(matcher.Apply(s));
667 }
668
669 {
670 const std::string s = "abc----def";
671 ASSERT_TRUE(matcher.Apply(s));
672 ASSERT_EQ(3, std::distance(s.begin(), matcher.GetMatchBegin()));
673 ASSERT_EQ("---", std::string(matcher.GetMatchBegin(), matcher.GetMatchEnd()));
674 }
675
676 {
677 const std::string s = "abc---";
678 ASSERT_TRUE(matcher.Apply(s));
679 ASSERT_EQ(3, std::distance(s.begin(), matcher.GetMatchBegin()));
680 ASSERT_EQ(s.end(), matcher.GetMatchEnd());
681 ASSERT_EQ("---", std::string(matcher.GetMatchBegin(), matcher.GetMatchEnd()));
682 ASSERT_EQ("", std::string(matcher.GetMatchEnd(), s.end()));
683 }
684
685 {
686 const std::string s = "abc--def";
687 ASSERT_FALSE(matcher.Apply(s));
688 ASSERT_THROW(matcher.GetMatchBegin(), OrthancException);
689 ASSERT_THROW(matcher.GetMatchEnd(), OrthancException);
690 }
691
692 {
693 std::string s(10u, '\0'); // String with null values
694 ASSERT_EQ(10u, s.size());
695 ASSERT_EQ(10u, s.size());
696 ASSERT_FALSE(matcher.Apply(s));
697
698 s[9] = '-';
699 ASSERT_FALSE(matcher.Apply(s));
700
701 s[8] = '-';
702 ASSERT_FALSE(matcher.Apply(s));
703
704 s[7] = '-';
705 ASSERT_TRUE(matcher.Apply(s));
706 ASSERT_EQ(s.c_str() + 7, matcher.GetPointerBegin());
707 ASSERT_EQ(s.c_str() + 10, matcher.GetPointerEnd());
708 ASSERT_EQ(s.end() - 3, matcher.GetMatchBegin());
709 ASSERT_EQ(s.end(), matcher.GetMatchEnd());
710 }
711 }
712
713
714
715 class MultipartTester : public MultipartStreamReader::IHandler
716 {
717 private:
718 struct Part
719 {
720 MultipartStreamReader::HttpHeaders headers_;
721 std::string data_;
722
723 Part(const MultipartStreamReader::HttpHeaders& headers,
724 const void* part,
725 size_t size) :
726 headers_(headers),
727 data_(reinterpret_cast<const char*>(part), size)
728 {
729 }
730 };
731
732 std::vector<Part> parts_;
733
734 public:
735 virtual void HandlePart(const MultipartStreamReader::HttpHeaders& headers,
736 const void* part,
737 size_t size)
738 {
739 parts_.push_back(Part(headers, part, size));
740 }
741
742 unsigned int GetCount() const
743 {
744 return parts_.size();
745 }
746
747 MultipartStreamReader::HttpHeaders& GetHeaders(size_t i)
748 {
749 return parts_[i].headers_;
750 }
751
752 const std::string& GetData(size_t i) const
753 {
754 return parts_[i].data_;
755 }
756 };
757
758
759 TEST(MultipartStreamReader, ParseHeaders)
760 {
761 std::string ct, b, st, header;
762
763 {
764 MultipartStreamReader::HttpHeaders h;
765 h["hello"] = "world";
766 h["Content-Type"] = "world"; // Should be in lower-case
767 h["CONTENT-type"] = "world"; // Should be in lower-case
768 ASSERT_FALSE(MultipartStreamReader::GetMainContentType(header, h));
769 }
770
771 {
772 MultipartStreamReader::HttpHeaders h;
773 h["content-type"] = "world";
774 ASSERT_TRUE(MultipartStreamReader::GetMainContentType(header, h));
775 ASSERT_EQ(header, "world");
776 ASSERT_FALSE(MultipartStreamReader::ParseMultipartContentType(ct, st, b, header));
777 }
778
779 {
780 MultipartStreamReader::HttpHeaders h;
781 h["content-type"] = "multipart/related; dummy=value; boundary=1234; hello=world";
782 ASSERT_TRUE(MultipartStreamReader::GetMainContentType(header, h));
783 ASSERT_EQ(header, h["content-type"]);
784 ASSERT_TRUE(MultipartStreamReader::ParseMultipartContentType(ct, st, b, header));
785 ASSERT_EQ(ct, "multipart/related");
786 ASSERT_EQ(b, "1234");
787 ASSERT_TRUE(st.empty());
788 }
789
790 {
791 ASSERT_FALSE(MultipartStreamReader::ParseMultipartContentType
792 (ct, st, b, "multipart/related; boundary=")); // Empty boundary
793 }
794
795 {
796 ASSERT_TRUE(MultipartStreamReader::ParseMultipartContentType
797 (ct, st, b, "Multipart/Related; TYPE=Application/Dicom; Boundary=heLLO"));
798 ASSERT_EQ(ct, "multipart/related");
799 ASSERT_EQ(b, "heLLO");
800 ASSERT_EQ(st, "application/dicom");
801 }
802
803 {
804 ASSERT_TRUE(MultipartStreamReader::ParseMultipartContentType
805 (ct, st, b, "Multipart/Related; type=\"application/DICOM\"; Boundary=a"));
806 ASSERT_EQ(ct, "multipart/related");
807 ASSERT_EQ(b, "a");
808 ASSERT_EQ(st, "application/dicom");
809 }
810 }
811
812
813 TEST(MultipartStreamReader, BytePerByte)
814 {
815 std::string stream = "GARBAGE";
816
817 std::string boundary = "123456789123456789";
818
819 {
820 for (size_t i = 0; i < 10; i++)
821 {
822 std::string f = "hello " + boost::lexical_cast<std::string>(i);
823
824 stream += "\r\n--" + boundary + "\r\n";
825 if (i % 2 == 0)
826 stream += "Content-Length: " + boost::lexical_cast<std::string>(f.size()) + "\r\n";
827 stream += "Content-Type: toto " + boost::lexical_cast<std::string>(i) + "\r\n\r\n";
828 stream += f;
829 }
830
831 stream += "\r\n--" + boundary + "--";
832 stream += "GARBAGE";
833 }
834
835 for (unsigned int k = 0; k < 2; k++)
836 {
837 MultipartTester decoded;
838
839 MultipartStreamReader reader(boundary);
840 reader.SetBlockSize(1);
841 reader.SetHandler(decoded);
842
843 if (k == 0)
844 {
845 for (size_t i = 0; i < stream.size(); i++)
846 {
847 reader.AddChunk(&stream[i], 1);
848 }
849 }
850 else
851 {
852 reader.AddChunk(stream);
853 }
854
855 reader.CloseStream();
856
857 ASSERT_EQ(10u, decoded.GetCount());
858
859 for (size_t i = 0; i < 10; i++)
860 {
861 ASSERT_EQ("hello " + boost::lexical_cast<std::string>(i), decoded.GetData(i));
862 ASSERT_EQ("toto " + boost::lexical_cast<std::string>(i), decoded.GetHeaders(i)["content-type"]);
863
864 if (i % 2 == 0)
865 {
866 ASSERT_EQ(2u, decoded.GetHeaders(i).size());
867 ASSERT_TRUE(decoded.GetHeaders(i).find("content-length") != decoded.GetHeaders(i).end());
868 }
869 }
870 }
871 }