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