Mercurial > hg > orthanc-stone
comparison Framework/Orthanc/Core/HttpClient.cpp @ 1:2dbe613f6c93
add orthanc core
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 14 Oct 2016 15:39:01 +0200 |
parents | |
children | 4b7e0244881f |
comparison
equal
deleted
inserted
replaced
0:351ab0da0150 | 1:2dbe613f6c93 |
---|---|
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 * | |
6 * This program is free software: you can redistribute it and/or | |
7 * modify it under the terms of the GNU General Public License as | |
8 * published by the Free Software Foundation, either version 3 of the | |
9 * License, or (at your option) any later version. | |
10 * | |
11 * In addition, as a special exception, the copyright holders of this | |
12 * program give permission to link the code of its release with the | |
13 * OpenSSL project's "OpenSSL" library (or with modified versions of it | |
14 * that use the same license as the "OpenSSL" library), and distribute | |
15 * the linked executables. You must obey the GNU General Public License | |
16 * in all respects for all of the code used other than "OpenSSL". If you | |
17 * modify file(s) with this exception, you may extend this exception to | |
18 * your version of the file(s), but you are not obligated to do so. If | |
19 * you do not wish to do so, delete this exception statement from your | |
20 * version. If you delete this exception statement from all source files | |
21 * in the program, then also delete it here. | |
22 * | |
23 * This program is distributed in the hope that it will be useful, but | |
24 * WITHOUT ANY WARRANTY; without even the implied warranty of | |
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
26 * General Public License for more details. | |
27 * | |
28 * You should have received a copy of the GNU General Public License | |
29 * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
30 **/ | |
31 | |
32 | |
33 #include "PrecompiledHeaders.h" | |
34 #include "HttpClient.h" | |
35 | |
36 #include "Toolbox.h" | |
37 #include "OrthancException.h" | |
38 #include "Logging.h" | |
39 #include "ChunkedBuffer.h" | |
40 | |
41 #include <string.h> | |
42 #include <curl/curl.h> | |
43 #include <boost/algorithm/string/predicate.hpp> | |
44 #include <boost/thread/mutex.hpp> | |
45 | |
46 | |
47 #if ORTHANC_SSL_ENABLED == 1 | |
48 // For OpenSSL initialization and finalization | |
49 # include <openssl/conf.h> | |
50 # include <openssl/engine.h> | |
51 # include <openssl/err.h> | |
52 # include <openssl/evp.h> | |
53 # include <openssl/ssl.h> | |
54 #endif | |
55 | |
56 | |
57 #if ORTHANC_PKCS11_ENABLED == 1 | |
58 # include "Pkcs11.h" | |
59 #endif | |
60 | |
61 | |
62 extern "C" | |
63 { | |
64 static CURLcode GetHttpStatus(CURLcode code, CURL* curl, long* status) | |
65 { | |
66 if (code == CURLE_OK) | |
67 { | |
68 code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, status); | |
69 return code; | |
70 } | |
71 else | |
72 { | |
73 *status = 0; | |
74 return code; | |
75 } | |
76 } | |
77 | |
78 // This is a dummy wrapper function to suppress any OpenSSL-related | |
79 // problem in valgrind. Inlining is prevented. | |
80 #if defined(__GNUC__) || defined(__clang__) | |
81 __attribute__((noinline)) | |
82 #endif | |
83 static CURLcode OrthancHttpClientPerformSSL(CURL* curl, long* status) | |
84 { | |
85 return GetHttpStatus(curl_easy_perform(curl), curl, status); | |
86 } | |
87 } | |
88 | |
89 | |
90 | |
91 namespace Orthanc | |
92 { | |
93 class HttpClient::GlobalParameters | |
94 { | |
95 private: | |
96 boost::mutex mutex_; | |
97 bool httpsVerifyPeers_; | |
98 std::string httpsCACertificates_; | |
99 std::string proxy_; | |
100 long timeout_; | |
101 | |
102 GlobalParameters() : | |
103 httpsVerifyPeers_(true), | |
104 timeout_(0) | |
105 { | |
106 } | |
107 | |
108 public: | |
109 // Singleton pattern | |
110 static GlobalParameters& GetInstance() | |
111 { | |
112 static GlobalParameters parameters; | |
113 return parameters; | |
114 } | |
115 | |
116 void ConfigureSsl(bool httpsVerifyPeers, | |
117 const std::string& httpsCACertificates) | |
118 { | |
119 boost::mutex::scoped_lock lock(mutex_); | |
120 httpsVerifyPeers_ = httpsVerifyPeers; | |
121 httpsCACertificates_ = httpsCACertificates; | |
122 } | |
123 | |
124 void GetSslConfiguration(bool& httpsVerifyPeers, | |
125 std::string& httpsCACertificates) | |
126 { | |
127 boost::mutex::scoped_lock lock(mutex_); | |
128 httpsVerifyPeers = httpsVerifyPeers_; | |
129 httpsCACertificates = httpsCACertificates_; | |
130 } | |
131 | |
132 void SetDefaultProxy(const std::string& proxy) | |
133 { | |
134 LOG(INFO) << "Setting the default proxy for HTTP client connections: " << proxy; | |
135 | |
136 { | |
137 boost::mutex::scoped_lock lock(mutex_); | |
138 proxy_ = proxy; | |
139 } | |
140 } | |
141 | |
142 void GetDefaultProxy(std::string& target) | |
143 { | |
144 boost::mutex::scoped_lock lock(mutex_); | |
145 target = proxy_; | |
146 } | |
147 | |
148 void SetDefaultTimeout(long seconds) | |
149 { | |
150 LOG(INFO) << "Setting the default timeout for HTTP client connections: " << seconds << " seconds"; | |
151 | |
152 { | |
153 boost::mutex::scoped_lock lock(mutex_); | |
154 timeout_ = seconds; | |
155 } | |
156 } | |
157 | |
158 long GetDefaultTimeout() | |
159 { | |
160 boost::mutex::scoped_lock lock(mutex_); | |
161 return timeout_; | |
162 } | |
163 | |
164 #if ORTHANC_PKCS11_ENABLED == 1 | |
165 bool IsPkcs11Initialized() | |
166 { | |
167 boost::mutex::scoped_lock lock(mutex_); | |
168 return Pkcs11::IsInitialized(); | |
169 } | |
170 | |
171 void InitializePkcs11(const std::string& module, | |
172 const std::string& pin, | |
173 bool verbose) | |
174 { | |
175 boost::mutex::scoped_lock lock(mutex_); | |
176 Pkcs11::Initialize(module, pin, verbose); | |
177 } | |
178 #endif | |
179 }; | |
180 | |
181 | |
182 struct HttpClient::PImpl | |
183 { | |
184 CURL* curl_; | |
185 struct curl_slist *defaultPostHeaders_; | |
186 struct curl_slist *userHeaders_; | |
187 }; | |
188 | |
189 | |
190 static void ThrowException(HttpStatus status) | |
191 { | |
192 switch (status) | |
193 { | |
194 case HttpStatus_400_BadRequest: | |
195 throw OrthancException(ErrorCode_BadRequest); | |
196 | |
197 case HttpStatus_401_Unauthorized: | |
198 case HttpStatus_403_Forbidden: | |
199 throw OrthancException(ErrorCode_Unauthorized); | |
200 | |
201 case HttpStatus_404_NotFound: | |
202 throw OrthancException(ErrorCode_UnknownResource); | |
203 | |
204 default: | |
205 throw OrthancException(ErrorCode_NetworkProtocol); | |
206 } | |
207 } | |
208 | |
209 | |
210 static CURLcode CheckCode(CURLcode code) | |
211 { | |
212 if (code == CURLE_NOT_BUILT_IN) | |
213 { | |
214 LOG(ERROR) << "Your libcurl does not contain a required feature, " | |
215 << "please recompile Orthanc with -DUSE_SYSTEM_CURL=OFF"; | |
216 throw OrthancException(ErrorCode_InternalError); | |
217 } | |
218 | |
219 if (code != CURLE_OK) | |
220 { | |
221 LOG(ERROR) << "libCURL error: " + std::string(curl_easy_strerror(code)); | |
222 throw OrthancException(ErrorCode_NetworkProtocol); | |
223 } | |
224 | |
225 return code; | |
226 } | |
227 | |
228 | |
229 static size_t CurlBodyCallback(void *buffer, size_t size, size_t nmemb, void *payload) | |
230 { | |
231 ChunkedBuffer& target = *(static_cast<ChunkedBuffer*>(payload)); | |
232 | |
233 size_t length = size * nmemb; | |
234 if (length == 0) | |
235 { | |
236 return 0; | |
237 } | |
238 else | |
239 { | |
240 target.AddChunk(buffer, length); | |
241 return length; | |
242 } | |
243 } | |
244 | |
245 | |
246 struct CurlHeaderParameters | |
247 { | |
248 bool lowerCase_; | |
249 HttpClient::HttpHeaders* headers_; | |
250 }; | |
251 | |
252 | |
253 static size_t CurlHeaderCallback(void *buffer, size_t size, size_t nmemb, void *payload) | |
254 { | |
255 CurlHeaderParameters& parameters = *(static_cast<CurlHeaderParameters*>(payload)); | |
256 assert(parameters.headers_ != NULL); | |
257 | |
258 size_t length = size * nmemb; | |
259 if (length == 0) | |
260 { | |
261 return 0; | |
262 } | |
263 else | |
264 { | |
265 std::string s(reinterpret_cast<const char*>(buffer), length); | |
266 std::size_t colon = s.find(':'); | |
267 std::size_t eol = s.find("\r\n"); | |
268 if (colon != std::string::npos && | |
269 eol != std::string::npos) | |
270 { | |
271 std::string tmp(s.substr(0, colon)); | |
272 | |
273 if (parameters.lowerCase_) | |
274 { | |
275 Toolbox::ToLowerCase(tmp); | |
276 } | |
277 | |
278 std::string key = Toolbox::StripSpaces(tmp); | |
279 | |
280 if (!key.empty()) | |
281 { | |
282 std::string value = Toolbox::StripSpaces(s.substr(colon + 1, eol)); | |
283 (*parameters.headers_) [key] = value; | |
284 } | |
285 } | |
286 | |
287 return length; | |
288 } | |
289 } | |
290 | |
291 | |
292 void HttpClient::Setup() | |
293 { | |
294 pimpl_->userHeaders_ = NULL; | |
295 pimpl_->defaultPostHeaders_ = NULL; | |
296 if ((pimpl_->defaultPostHeaders_ = curl_slist_append(pimpl_->defaultPostHeaders_, "Expect:")) == NULL) | |
297 { | |
298 throw OrthancException(ErrorCode_NotEnoughMemory); | |
299 } | |
300 | |
301 pimpl_->curl_ = curl_easy_init(); | |
302 if (!pimpl_->curl_) | |
303 { | |
304 curl_slist_free_all(pimpl_->defaultPostHeaders_); | |
305 throw OrthancException(ErrorCode_NotEnoughMemory); | |
306 } | |
307 | |
308 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEFUNCTION, &CurlBodyCallback)); | |
309 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADER, 0)); | |
310 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1)); | |
311 | |
312 // This fixes the "longjmp causes uninitialized stack frame" crash | |
313 // that happens on modern Linux versions. | |
314 // http://stackoverflow.com/questions/9191668/error-longjmp-causes-uninitialized-stack-frame | |
315 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOSIGNAL, 1)); | |
316 | |
317 url_ = ""; | |
318 method_ = HttpMethod_Get; | |
319 lastStatus_ = HttpStatus_200_Ok; | |
320 isVerbose_ = false; | |
321 timeout_ = GlobalParameters::GetInstance().GetDefaultTimeout(); | |
322 GlobalParameters::GetInstance().GetDefaultProxy(proxy_); | |
323 GlobalParameters::GetInstance().GetSslConfiguration(verifyPeers_, caCertificates_); | |
324 } | |
325 | |
326 | |
327 HttpClient::HttpClient() : | |
328 pimpl_(new PImpl), | |
329 verifyPeers_(true), | |
330 pkcs11Enabled_(false), | |
331 headersToLowerCase_(true) | |
332 { | |
333 Setup(); | |
334 } | |
335 | |
336 | |
337 HttpClient::HttpClient(const WebServiceParameters& service, | |
338 const std::string& uri) : | |
339 pimpl_(new PImpl), | |
340 verifyPeers_(true), | |
341 headersToLowerCase_(true) | |
342 { | |
343 Setup(); | |
344 | |
345 if (service.GetUsername().size() != 0 && | |
346 service.GetPassword().size() != 0) | |
347 { | |
348 SetCredentials(service.GetUsername().c_str(), | |
349 service.GetPassword().c_str()); | |
350 } | |
351 | |
352 if (!service.GetCertificateFile().empty()) | |
353 { | |
354 SetClientCertificate(service.GetCertificateFile(), | |
355 service.GetCertificateKeyFile(), | |
356 service.GetCertificateKeyPassword()); | |
357 } | |
358 | |
359 SetPkcs11Enabled(service.IsPkcs11Enabled()); | |
360 | |
361 SetUrl(service.GetUrl() + uri); | |
362 } | |
363 | |
364 | |
365 HttpClient::~HttpClient() | |
366 { | |
367 curl_easy_cleanup(pimpl_->curl_); | |
368 curl_slist_free_all(pimpl_->defaultPostHeaders_); | |
369 ClearHeaders(); | |
370 } | |
371 | |
372 | |
373 void HttpClient::SetVerbose(bool isVerbose) | |
374 { | |
375 isVerbose_ = isVerbose; | |
376 | |
377 if (isVerbose_) | |
378 { | |
379 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 1)); | |
380 } | |
381 else | |
382 { | |
383 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 0)); | |
384 } | |
385 } | |
386 | |
387 | |
388 void HttpClient::AddHeader(const std::string& key, | |
389 const std::string& value) | |
390 { | |
391 if (key.empty()) | |
392 { | |
393 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
394 } | |
395 | |
396 std::string s = key + ": " + value; | |
397 | |
398 if ((pimpl_->userHeaders_ = curl_slist_append(pimpl_->userHeaders_, s.c_str())) == NULL) | |
399 { | |
400 throw OrthancException(ErrorCode_NotEnoughMemory); | |
401 } | |
402 } | |
403 | |
404 | |
405 void HttpClient::ClearHeaders() | |
406 { | |
407 if (pimpl_->userHeaders_ != NULL) | |
408 { | |
409 curl_slist_free_all(pimpl_->userHeaders_); | |
410 pimpl_->userHeaders_ = NULL; | |
411 } | |
412 } | |
413 | |
414 | |
415 bool HttpClient::ApplyInternal(std::string& answerBody, | |
416 HttpHeaders* answerHeaders) | |
417 { | |
418 answerBody.clear(); | |
419 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str())); | |
420 | |
421 CurlHeaderParameters headerParameters; | |
422 | |
423 if (answerHeaders == NULL) | |
424 { | |
425 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERFUNCTION, NULL)); | |
426 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERDATA, NULL)); | |
427 } | |
428 else | |
429 { | |
430 headerParameters.lowerCase_ = headersToLowerCase_; | |
431 headerParameters.headers_ = answerHeaders; | |
432 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERFUNCTION, &CurlHeaderCallback)); | |
433 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERDATA, &headerParameters)); | |
434 } | |
435 | |
436 #if ORTHANC_SSL_ENABLED == 1 | |
437 // Setup HTTPS-related options | |
438 | |
439 if (verifyPeers_) | |
440 { | |
441 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CAINFO, caCertificates_.c_str())); | |
442 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYHOST, 2)); // libcurl default is strict verifyhost | |
443 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 1)); | |
444 } | |
445 else | |
446 { | |
447 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYHOST, 0)); | |
448 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 0)); | |
449 } | |
450 #endif | |
451 | |
452 // Setup the HTTPS client certificate | |
453 if (!clientCertificateFile_.empty() && | |
454 pkcs11Enabled_) | |
455 { | |
456 LOG(ERROR) << "Cannot enable both client certificates and PKCS#11 authentication"; | |
457 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
458 } | |
459 | |
460 if (pkcs11Enabled_) | |
461 { | |
462 #if ORTHANC_PKCS11_ENABLED == 1 | |
463 if (GlobalParameters::GetInstance().IsPkcs11Initialized()) | |
464 { | |
465 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLENGINE, Pkcs11::GetEngineIdentifier())); | |
466 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEYTYPE, "ENG")); | |
467 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "ENG")); | |
468 } | |
469 else | |
470 { | |
471 LOG(ERROR) << "Cannot use PKCS#11 for a HTTPS request, because it has not been initialized"; | |
472 throw OrthancException(ErrorCode_BadSequenceOfCalls); | |
473 } | |
474 #else | |
475 LOG(ERROR) << "This version of Orthanc is compiled without support for PKCS#11"; | |
476 throw OrthancException(ErrorCode_InternalError); | |
477 #endif | |
478 } | |
479 else if (!clientCertificateFile_.empty()) | |
480 { | |
481 #if ORTHANC_SSL_ENABLED == 1 | |
482 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "PEM")); | |
483 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERT, clientCertificateFile_.c_str())); | |
484 | |
485 if (!clientCertificateKeyPassword_.empty()) | |
486 { | |
487 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_KEYPASSWD, clientCertificateKeyPassword_.c_str())); | |
488 } | |
489 | |
490 // NB: If no "clientKeyFile_" is provided, the key must be | |
491 // prepended to the certificate file | |
492 if (!clientCertificateKeyFile_.empty()) | |
493 { | |
494 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEYTYPE, "PEM")); | |
495 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEY, clientCertificateKeyFile_.c_str())); | |
496 } | |
497 #else | |
498 LOG(ERROR) << "This version of Orthanc is compiled without OpenSSL support, cannot use HTTPS client authentication"; | |
499 throw OrthancException(ErrorCode_InternalError); | |
500 #endif | |
501 } | |
502 | |
503 // Reset the parameters from previous calls to Apply() | |
504 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->userHeaders_)); | |
505 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 0L)); | |
506 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 0L)); | |
507 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 0L)); | |
508 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, NULL)); | |
509 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL)); | |
510 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0L)); | |
511 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PROXY, NULL)); | |
512 | |
513 if (redirectionFollowed_) | |
514 { | |
515 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1L)); | |
516 } | |
517 else | |
518 { | |
519 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 0L)); | |
520 } | |
521 | |
522 // Set timeouts | |
523 if (timeout_ <= 0) | |
524 { | |
525 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_TIMEOUT, 10)); /* default: 10 seconds */ | |
526 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CONNECTTIMEOUT, 10)); /* default: 10 seconds */ | |
527 } | |
528 else | |
529 { | |
530 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_TIMEOUT, timeout_)); | |
531 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CONNECTTIMEOUT, timeout_)); | |
532 } | |
533 | |
534 if (credentials_.size() != 0) | |
535 { | |
536 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_USERPWD, credentials_.c_str())); | |
537 } | |
538 | |
539 if (proxy_.size() != 0) | |
540 { | |
541 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PROXY, proxy_.c_str())); | |
542 } | |
543 | |
544 switch (method_) | |
545 { | |
546 case HttpMethod_Get: | |
547 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 1L)); | |
548 break; | |
549 | |
550 case HttpMethod_Post: | |
551 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L)); | |
552 | |
553 if (pimpl_->userHeaders_ == NULL) | |
554 { | |
555 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->defaultPostHeaders_)); | |
556 } | |
557 | |
558 break; | |
559 | |
560 case HttpMethod_Delete: | |
561 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 1L)); | |
562 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "DELETE")); | |
563 break; | |
564 | |
565 case HttpMethod_Put: | |
566 // http://stackoverflow.com/a/7570281/881731: Don't use | |
567 // CURLOPT_PUT if there is a body | |
568 | |
569 // CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PUT, 1L)); | |
570 | |
571 curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "PUT"); /* !!! */ | |
572 | |
573 if (pimpl_->userHeaders_ == NULL) | |
574 { | |
575 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->defaultPostHeaders_)); | |
576 } | |
577 | |
578 break; | |
579 | |
580 default: | |
581 throw OrthancException(ErrorCode_InternalError); | |
582 } | |
583 | |
584 | |
585 if (method_ == HttpMethod_Post || | |
586 method_ == HttpMethod_Put) | |
587 { | |
588 if (body_.size() > 0) | |
589 { | |
590 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, body_.c_str())); | |
591 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, body_.size())); | |
592 } | |
593 else | |
594 { | |
595 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL)); | |
596 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0)); | |
597 } | |
598 } | |
599 | |
600 | |
601 // Do the actual request | |
602 CURLcode code; | |
603 long status = 0; | |
604 | |
605 ChunkedBuffer buffer; | |
606 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &buffer)); | |
607 | |
608 if (boost::starts_with(url_, "https://")) | |
609 { | |
610 code = OrthancHttpClientPerformSSL(pimpl_->curl_, &status); | |
611 } | |
612 else | |
613 { | |
614 code = GetHttpStatus(curl_easy_perform(pimpl_->curl_), pimpl_->curl_, &status); | |
615 } | |
616 | |
617 CheckCode(code); | |
618 | |
619 if (status == 0) | |
620 { | |
621 // This corresponds to a call to an inexistent host | |
622 lastStatus_ = HttpStatus_500_InternalServerError; | |
623 } | |
624 else | |
625 { | |
626 lastStatus_ = static_cast<HttpStatus>(status); | |
627 } | |
628 | |
629 bool success = (status >= 200 && status < 300); | |
630 | |
631 if (success) | |
632 { | |
633 buffer.Flatten(answerBody); | |
634 } | |
635 else | |
636 { | |
637 answerBody.clear(); | |
638 LOG(INFO) << "Error in HTTP request, received HTTP status " << status | |
639 << " (" << EnumerationToString(lastStatus_) << ")"; | |
640 } | |
641 | |
642 return success; | |
643 } | |
644 | |
645 | |
646 bool HttpClient::ApplyInternal(Json::Value& answerBody, | |
647 HttpClient::HttpHeaders* answerHeaders) | |
648 { | |
649 std::string s; | |
650 if (ApplyInternal(s, answerHeaders)) | |
651 { | |
652 Json::Reader reader; | |
653 return reader.parse(s, answerBody); | |
654 } | |
655 else | |
656 { | |
657 return false; | |
658 } | |
659 } | |
660 | |
661 | |
662 void HttpClient::SetCredentials(const char* username, | |
663 const char* password) | |
664 { | |
665 credentials_ = std::string(username) + ":" + std::string(password); | |
666 } | |
667 | |
668 | |
669 void HttpClient::ConfigureSsl(bool httpsVerifyPeers, | |
670 const std::string& httpsVerifyCertificates) | |
671 { | |
672 #if ORTHANC_SSL_ENABLED == 1 | |
673 if (httpsVerifyPeers) | |
674 { | |
675 if (httpsVerifyCertificates.empty()) | |
676 { | |
677 LOG(WARNING) << "No certificates are provided to validate peers, " | |
678 << "set \"HttpsCACertificates\" if you need to do HTTPS requests"; | |
679 } | |
680 else | |
681 { | |
682 LOG(WARNING) << "HTTPS will use the CA certificates from this file: " << httpsVerifyCertificates; | |
683 } | |
684 } | |
685 else | |
686 { | |
687 LOG(WARNING) << "The verification of the peers in HTTPS requests is disabled"; | |
688 } | |
689 #endif | |
690 | |
691 GlobalParameters::GetInstance().ConfigureSsl(httpsVerifyPeers, httpsVerifyCertificates); | |
692 } | |
693 | |
694 | |
695 void HttpClient::GlobalInitialize() | |
696 { | |
697 #if ORTHANC_SSL_ENABLED == 1 | |
698 CheckCode(curl_global_init(CURL_GLOBAL_ALL)); | |
699 #else | |
700 CheckCode(curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_SSL)); | |
701 #endif | |
702 } | |
703 | |
704 | |
705 void HttpClient::GlobalFinalize() | |
706 { | |
707 curl_global_cleanup(); | |
708 | |
709 #if ORTHANC_PKCS11_ENABLED == 1 | |
710 Pkcs11::Finalize(); | |
711 #endif | |
712 } | |
713 | |
714 | |
715 void HttpClient::SetDefaultProxy(const std::string& proxy) | |
716 { | |
717 GlobalParameters::GetInstance().SetDefaultProxy(proxy); | |
718 } | |
719 | |
720 | |
721 void HttpClient::SetDefaultTimeout(long timeout) | |
722 { | |
723 GlobalParameters::GetInstance().SetDefaultTimeout(timeout); | |
724 } | |
725 | |
726 | |
727 void HttpClient::ApplyAndThrowException(std::string& answerBody) | |
728 { | |
729 if (!Apply(answerBody)) | |
730 { | |
731 ThrowException(GetLastStatus()); | |
732 } | |
733 } | |
734 | |
735 | |
736 void HttpClient::ApplyAndThrowException(Json::Value& answerBody) | |
737 { | |
738 if (!Apply(answerBody)) | |
739 { | |
740 ThrowException(GetLastStatus()); | |
741 } | |
742 } | |
743 | |
744 | |
745 void HttpClient::ApplyAndThrowException(std::string& answerBody, | |
746 HttpHeaders& answerHeaders) | |
747 { | |
748 if (!Apply(answerBody, answerHeaders)) | |
749 { | |
750 ThrowException(GetLastStatus()); | |
751 } | |
752 } | |
753 | |
754 | |
755 void HttpClient::ApplyAndThrowException(Json::Value& answerBody, | |
756 HttpHeaders& answerHeaders) | |
757 { | |
758 if (!Apply(answerBody, answerHeaders)) | |
759 { | |
760 ThrowException(GetLastStatus()); | |
761 } | |
762 } | |
763 | |
764 | |
765 void HttpClient::SetClientCertificate(const std::string& certificateFile, | |
766 const std::string& certificateKeyFile, | |
767 const std::string& certificateKeyPassword) | |
768 { | |
769 if (certificateFile.empty()) | |
770 { | |
771 throw OrthancException(ErrorCode_ParameterOutOfRange); | |
772 } | |
773 | |
774 if (!Toolbox::IsRegularFile(certificateFile)) | |
775 { | |
776 LOG(ERROR) << "Cannot open certificate file: " << certificateFile; | |
777 throw OrthancException(ErrorCode_InexistentFile); | |
778 } | |
779 | |
780 if (!certificateKeyFile.empty() && | |
781 !Toolbox::IsRegularFile(certificateKeyFile)) | |
782 { | |
783 LOG(ERROR) << "Cannot open key file: " << certificateKeyFile; | |
784 throw OrthancException(ErrorCode_InexistentFile); | |
785 } | |
786 | |
787 clientCertificateFile_ = certificateFile; | |
788 clientCertificateKeyFile_ = certificateKeyFile; | |
789 clientCertificateKeyPassword_ = certificateKeyPassword; | |
790 } | |
791 | |
792 | |
793 void HttpClient::InitializePkcs11(const std::string& module, | |
794 const std::string& pin, | |
795 bool verbose) | |
796 { | |
797 #if ORTHANC_PKCS11_ENABLED == 1 | |
798 LOG(INFO) << "Initializing PKCS#11 using " << module | |
799 << (pin.empty() ? " (no PIN provided)" : " (PIN is provided)"); | |
800 GlobalParameters::GetInstance().InitializePkcs11(module, pin, verbose); | |
801 #else | |
802 LOG(ERROR) << "This version of Orthanc is compiled without support for PKCS#11"; | |
803 throw OrthancException(ErrorCode_InternalError); | |
804 #endif | |
805 } | |
806 | |
807 | |
808 void HttpClient::InitializeOpenSsl() | |
809 { | |
810 #if ORTHANC_SSL_ENABLED == 1 | |
811 // https://wiki.openssl.org/index.php/Library_Initialization | |
812 SSL_library_init(); | |
813 SSL_load_error_strings(); | |
814 OpenSSL_add_all_algorithms(); | |
815 ERR_load_crypto_strings(); | |
816 #endif | |
817 } | |
818 | |
819 | |
820 void HttpClient::FinalizeOpenSsl() | |
821 { | |
822 #if ORTHANC_SSL_ENABLED == 1 | |
823 // Finalize OpenSSL | |
824 // https://wiki.openssl.org/index.php/Library_Initialization#Cleanup | |
825 FIPS_mode_set(0); | |
826 ENGINE_cleanup(); | |
827 CONF_modules_unload(1); | |
828 EVP_cleanup(); | |
829 CRYPTO_cleanup_all_ex_data(); | |
830 ERR_remove_state(0); | |
831 ERR_free_strings(); | |
832 #endif | |
833 } | |
834 } |