Mantid
Loading...
Searching...
No Matches
InternetHelper.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
11#include "MantidKernel/Logger.h"
13
14// Poco
15#include <Poco/Net/AcceptCertificateHandler.h>
16#include <Poco/Net/HTMLForm.h>
17#include <Poco/Net/HTTPRequest.h>
18#include <Poco/Net/HTTPResponse.h>
19#include <Poco/Net/HTTPSClientSession.h>
20#include <Poco/Net/NetException.h>
21#include <Poco/Net/PrivateKeyPassphraseHandler.h>
22#include <Poco/Net/SSLManager.h>
23#include <Poco/StreamCopier.h>
24#include <Poco/TemporaryFile.h>
25#include <Poco/URI.h>
26
27#include <Poco/Exception.h>
28#include <Poco/Net/Context.h>
29#include <Poco/Net/HTTPClientSession.h>
30#include <Poco/Net/HTTPMessage.h>
31#include <Poco/Net/InvalidCertificateHandler.h>
32#include <Poco/SharedPtr.h>
33#include <Poco/Timespan.h>
34#include <Poco/Types.h>
35
36#if defined(_WIN32) || defined(_WIN64)
37#include <Winhttp.h>
38#endif
39
40#include <boost/lexical_cast.hpp>
41
42// std
43#include <filesystem>
44#include <fstream>
45#include <mutex>
46#include <utility>
47
48namespace Mantid {
49using namespace Types::Core;
50namespace Kernel {
51
52using namespace Poco::Net;
53using std::map;
54using std::string;
55
56namespace {
57// anonymous namespace for some utility functions
59Logger g_log("InternetHelper");
60
62std::once_flag SSL_INIT_FLAG;
63
68void doSSLInit() {
69 // initialize ssl
70 Poco::SharedPtr<InvalidCertificateHandler> certificateHandler = new AcceptCertificateHandler(true);
71 // Currently do not use any means of authentication. This should be updated
72 // IDS has signed certificate.
73 const Context::Ptr context = new Context(Context::CLIENT_USE, "", "", "", Context::VERIFY_NONE);
74 // Create a singleton for holding the default context.
75 // e.g. any future requests to publish are made to this certificate and
76 // context.
77 SSLManager::instance().initializeClient(nullptr, certificateHandler, context);
78}
79
84void initializeSSL() { std::call_once(SSL_INIT_FLAG, doSSLInit); }
85} // namespace
86
87//----------------------------------------------------------------------------------------------
91 : m_proxyInfo(), m_isProxySet(false), m_timeout(30), m_isTimeoutSet(false), m_contentLength(0),
92 m_method(HTTPRequest::HTTP_GET), m_contentType("application/json"), m_body(), m_headers(), m_request(nullptr),
93 m_response(nullptr) {}
94
95//----------------------------------------------------------------------------------------------
99 : m_proxyInfo(proxy), m_isProxySet(true), m_timeout(30), m_isTimeoutSet(false), m_contentLength(0),
100 m_method(HTTPRequest::HTTP_GET), m_contentType("application/json"), m_body(), m_headers(), m_request(nullptr),
101 m_response(nullptr) {}
102
103//----------------------------------------------------------------------------------------------
107
108void InternetHelper::setupProxyOnSession(HTTPClientSession &session, const std::string &proxyUrl) {
109 auto proxy = this->getProxy(proxyUrl);
110 if (!proxy.emptyProxy()) {
111 session.setProxyHost(proxy.host());
112 session.setProxyPort(static_cast<Poco::UInt16>(proxy.port()));
113 }
114}
115
116void InternetHelper::createRequest(Poco::URI &uri) {
117 m_request = std::make_unique<HTTPRequest>(m_method, uri.getPathAndQuery(), HTTPMessage::HTTP_1_1);
118 m_response = std::make_unique<HTTPResponse>();
119 if (!m_contentType.empty()) {
120 m_request->setContentType(m_contentType);
121 }
122
123 m_request->set("User-Agent",
124 // Use standard User-Agent format as per MDN documentation.
125 std::string("Mantid/") + MantidVersion::version());
126 if (m_method == "POST") {
127 // HTTP states that the 'Content-Length' header should not be included
128 // if the 'Transfer-Encoding' header is set. UNKNOWN_CONTENT_LENGTH
129 // indicates to Poco to remove the header field
130 m_request->setContentLength(HTTPMessage::UNKNOWN_CONTENT_LENGTH);
131 m_request->setChunkedTransferEncoding(true);
132 } else if (m_contentLength > 0) {
133 m_request->setContentLength(m_contentLength);
134 }
135
136 for (auto &header : m_headers) {
137 m_request->set(header.first, header.second);
138 }
139}
140
141InternetHelper::HTTPStatus InternetHelper::sendRequestAndProcess(HTTPClientSession &session, Poco::URI &uri,
142 std::ostream &responseStream) {
143 // create a request
144 this->createRequest(uri);
145 session.sendRequest(*m_request) << m_body;
146
147 // do not dereference null pointer below
148 if (!m_response) {
149 g_log.warning("Response is null pointer");
150 return HTTPStatus::BAD_REQUEST; // generic error
151 }
152
153 // process the response
154 std::istream &rs = session.receiveResponse(*m_response);
155 const auto retStatus = static_cast<HTTPStatus>(m_response->getStatus());
156 g_log.debug() << "Answer from web: " << static_cast<int>(retStatus) << " " << m_response->getReason() << '\n';
157 if (retStatus == HTTPStatus::OK || (retStatus == HTTPStatus::CREATED && m_method == HTTPRequest::HTTP_POST)) {
158 Poco::StreamCopier::copyStream(rs, responseStream);
160 return retStatus;
161 } else if (isRelocated(retStatus)) {
162 return this->processRelocation(*m_response, responseStream);
163 } else {
164 Poco::StreamCopier::copyStream(rs, responseStream);
165 return processErrorStates(*m_response, rs, uri.toString());
166 }
167}
168
170 std::ostream &responseStream) {
171 std::string newLocation = response.get("location", "");
172 if (!newLocation.empty()) {
173 g_log.information() << "url relocated to " << newLocation << "\n";
174 return this->sendRequest(newLocation, responseStream);
175 } else {
176 g_log.warning("Apparent relocation did not give new location\n");
177 return static_cast<HTTPStatus>(response.getStatus());
178 }
179}
180
185InternetHelper::HTTPStatus InternetHelper::sendRequest(const std::string &url, std::ostream &responseStream) {
186
187 // send the request
188 Poco::URI uri(url);
189 if (uri.getPath().empty())
190 uri = url + "/";
191 if ((uri.getScheme() == "https") || (uri.getPort() == 443)) {
192 return static_cast<HTTPStatus>(sendHTTPSRequest(uri.toString(), responseStream));
193 } else {
194 return static_cast<HTTPStatus>(sendHTTPRequest(uri.toString(), responseStream));
195 }
196}
197
205void InternetHelper::logDebugRequestSending(const std::string &schemeName, const std::string &url) const {
206 const std::string insecString = "password=";
207 if (std::string::npos == url.find(insecString)) {
208 g_log.debug() << "Sending " << schemeName << " " << m_method << " request to: " << url << "\n";
209 } else {
210 g_log.debug() << "Sending " << schemeName << " " << m_method
211 << " request to an url where the query string seems to contain a "
212 "password! (not shown for security reasons)."
213 << "\n";
214 }
215}
216
221InternetHelper::HTTPStatus InternetHelper::sendHTTPRequest(const std::string &url, std::ostream &responseStream) {
223 logDebugRequestSending("http", url);
224
225 Poco::URI uri(url);
226 // Configure Poco HTTP Client Session
227 try {
228 Poco::Net::HTTPClientSession session(uri.getHost(), uri.getPort());
229 session.setTimeout(Poco::Timespan(getTimeout(), 0));
230
231 // configure proxy
232 setupProxyOnSession(session, url);
233
234 // low level sending the request
235 retStatus = this->sendRequestAndProcess(session, uri, responseStream);
236 } catch (HostNotFoundException &ex) {
237 throwNotConnected(url, ex);
238 } catch (Poco::Exception &ex) {
239 throw Exception::InternetError("Connection and request failed " + ex.displayText());
240 }
241 return retStatus;
242}
243
248InternetHelper::HTTPStatus InternetHelper::sendHTTPSRequest(const std::string &url, std::ostream &responseStream) {
250
251 logDebugRequestSending("https", url);
252
253 Poco::URI uri(url);
254 try {
255 initializeSSL();
256 // Create the session
257 HTTPSClientSession session(uri.getHost(), static_cast<Poco::UInt16>(uri.getPort()));
258 session.setTimeout(Poco::Timespan(getTimeout(), 0));
259
260 // HACK:: Currently the automatic proxy detection only supports http proxy
261 // detection
262 // most locations use the same proxy for http and https, so force it to use
263 // the http proxy
264 std::string urlforProxy = ConfigService::Instance().getString("proxy.httpsTargetUrl");
265 if (urlforProxy.empty()) {
266 urlforProxy = "http://" + uri.getHost();
267 }
268 setupProxyOnSession(session, urlforProxy);
269
270 // low level sending the request
271 retStatus = this->sendRequestAndProcess(session, uri, responseStream);
272 } catch (HostNotFoundException &ex) {
273 throwNotConnected(url, ex);
274 } catch (Poco::Exception &ex) {
275 throw Exception::InternetError("Connection and request failed " + ex.displayText());
276 }
277 return retStatus;
278}
279
284 // set the proxy
285 if (!m_isProxySet) {
286 setProxy(ConfigService::Instance().getProxy(url));
287 }
288 return m_proxyInfo;
289}
290
294
299 m_proxyInfo = proxy;
300 m_isProxySet = true;
301}
302
306void InternetHelper::processResponseHeaders(const HTTPResponse & /*unused*/) {}
307
317InternetHelper::HTTPStatus InternetHelper::processErrorStates(const HTTPResponse &res, std::istream &rs,
318 const std::string &url) {
319 const auto retStatus = static_cast<HTTPStatus>(res.getStatus());
320 g_log.debug() << "Answer from web: " << static_cast<int>(res.getStatus()) << " " << res.getReason() << '\n';
321
322 // get github api rate limit information if available;
323 int rateLimitRemaining;
324 DateAndTime rateLimitReset;
325 try {
326 rateLimitRemaining = boost::lexical_cast<int>(res.get("X-RateLimit-Remaining", "-1"));
327 rateLimitReset.set_from_time_t(boost::lexical_cast<int>(res.get("X-RateLimit-Reset", "0")));
328 } catch (boost::bad_lexical_cast const &) {
329 rateLimitRemaining = -1;
330 }
331
332 if (retStatus == HTTPStatus::OK) {
333 throw Exception::InternetError("Response was ok, processing should never "
334 "have entered processErrorStates",
335 static_cast<int>(retStatus));
336 } else if (retStatus == HTTPStatus::FOUND) {
337 throw Exception::InternetError("Response was HTTP_FOUND, processing should "
338 "never have entered processErrorStates",
339 static_cast<int>(retStatus));
340 } else if (retStatus == HTTPStatus::MOVED_PERMANENTLY) {
341 throw Exception::InternetError("Response was HTTP_MOVED_PERMANENTLY, "
342 "processing should never have entered "
343 "processErrorStates",
344 static_cast<int>(retStatus));
345 } else if (retStatus == HTTPStatus::NOT_MODIFIED) {
346 throw Exception::InternetError("Not modified since provided date" + rateLimitReset.toSimpleString(),
347 static_cast<int>(retStatus));
348 } else if ((retStatus == HTTPStatus::FORBIDDEN) && (rateLimitRemaining == 0)) {
349 throw Exception::InternetError("The Github API rate limit has been reached, try again after " +
350 rateLimitReset.toSimpleString() + " GMT",
351 static_cast<int>(retStatus));
352 } else {
353 std::stringstream info;
354 std::stringstream ss;
355 Poco::StreamCopier::copyStream(rs, ss);
356 if (retStatus == HTTPStatus::NOT_FOUND)
357 info << "Failed to download " << url << " with the link "
358 << "<a href=\"" << url << "\">.\n"
359 << "Hint. Check that link is correct</a>";
360 else {
361 // show the error
362 info << res.getReason();
363 info << ss.str();
364 g_log.debug() << ss.str();
365 }
366 throw Exception::InternetError(info.str() + ss.str(), static_cast<int>(retStatus));
367 }
368 return retStatus; // must return to follow contract
369}
370
391InternetHelper::HTTPStatus InternetHelper::downloadFile(const std::string &urlFile, const std::string &localFilePath) {
392 g_log.debug() << "DownloadFile from \"" << urlFile << "\" to file: \"" << localFilePath << "\"\n";
393
394 Poco::TemporaryFile tempFile;
395 std::ofstream tempFileStream(tempFile.path());
396 const auto retStatus = sendRequest(urlFile, tempFileStream);
397 tempFileStream.close();
398
399 // if there have been no errors move it to the final location, and turn off
400 // automatic deletion.
401 // clear the way if the target file path is already in use
402 if (std::filesystem::exists(localFilePath)) {
403 std::filesystem::remove(localFilePath);
404 }
405
406 tempFile.moveTo(localFilePath);
407 tempFile.keep();
408
409 return retStatus;
410}
411
415void InternetHelper::setTimeout(int seconds) {
416 m_timeout = seconds;
417 m_isTimeoutSet = true;
418}
419
424 return ((response == HTTPStatus::FOUND) || (response == HTTPStatus::MOVED_PERMANENTLY) ||
425 (response == HTTPStatus::TEMPORARY_REDIRECT) || (response == HTTPStatus::SEE_OTHER));
426}
427
432void InternetHelper::throwNotConnected(const std::string &url, const HostNotFoundException &ex) {
433 std::stringstream info;
434 info << "Failed to access " << url << " because there is no connection to the host " << ex.message()
435 << ".\nHint: Check your connection following this link: <a href=\"" << url << "\">" << url << "</a> ";
436 throw Exception::InternetError(info.str() + ex.displayText());
437}
438
443 if (!m_isTimeoutSet) {
444 const auto timeout = ConfigService::Instance().getValue<int>("network.default.timeout");
445 m_timeout = timeout.value_or(30);
446 }
447 return m_timeout;
448}
449
454void InternetHelper::setMethod(const std::string &method) {
455 if (method == "POST") {
456 m_method = method;
457 } else {
458 m_method = "GET";
459 }
460}
461
465const std::string &InternetHelper::getMethod() { return m_method; }
466
470void InternetHelper::setContentType(const std::string &contentType) { m_contentType = contentType; }
471
475const std::string &InternetHelper::getContentType() { return m_contentType; }
476
480void InternetHelper::setContentLength(std::streamsize length) { m_contentLength = length; }
481
486
492void InternetHelper::setBody(const std::string &body) {
493 m_body = body;
494 if (m_body.empty()) {
495 m_method = "GET";
496 } else {
497 m_method = "POST";
498 }
499 setContentLength(m_body.size());
500}
501
507void InternetHelper::setBody(const std::ostringstream &body) { setBody(body.str()); }
508
514void InternetHelper::setBody(Poco::Net::HTMLForm &form) {
515
516 setMethod("POST");
517 if (m_request == nullptr) {
518 Poco::URI uri("http://www.mantidproject.org");
519 createRequest(uri);
520 }
521 form.prepareSubmit(*m_request);
522 setContentType(m_request->getContentType());
523
524 std::ostringstream ss;
525 form.write(ss);
526 m_body = ss.str();
527 setContentLength(m_body.size());
528}
529
533const std::string &InternetHelper::getBody() { return m_body; }
534
541
545const std::string &InternetHelper::getResponseReason() { return m_response->getReason(); }
546
551void InternetHelper::addHeader(const std::string &key, const std::string &value) { m_headers.emplace(key, value); }
552
556void InternetHelper::removeHeader(const std::string &key) { m_headers.erase(key); }
557
562const std::string &InternetHelper::getHeader(const std::string &key) { return m_headers[key]; }
563
567
570std::map<std::string, std::string> &InternetHelper::headers() { return m_headers; }
571
575 m_headers.clear();
576 m_timeout = 30;
577 m_isTimeoutSet = false;
578 m_body = "";
579 m_method = HTTPRequest::HTTP_GET;
580 m_contentType = "application/json";
581 m_request = nullptr;
582}
583
584} // namespace Kernel
585} // namespace Mantid
double value
The value of the point.
Definition FitMW.cpp:51
BuilderMethod< ArgType > m_method
Exception thrown when error occurs accessing an internet resource.
Definition Exception.h:321
const std::string & getResponseReason()
Gets the body set for future requests.
const std::string & getBody()
Gets the body set for future requests.
void createRequest(Poco::URI &uri)
void setContentLength(std::streamsize length)
Sets the content length.
InternetHelper::HTTPStatus processRelocation(const Poco::Net::HTTPResponse &response, std::ostream &responseStream)
void logDebugRequestSending(const std::string &schemeName, const std::string &url) const
Helper to log (debug level) the request being sent (careful not to print blatant passwords,...
void setBody(const std::string &body)
Sets the body & content length for future requests, this will also set the method to POST is the body...
bool isRelocated(const HTTPStatus &response)
Checks the HTTP status to decide if this is a relocation.
std::unique_ptr< Poco::Net::HTTPRequest > m_request
void addHeader(const std::string &key, const std::string &value)
Adds a header.
void setupProxyOnSession(Poco::Net::HTTPClientSession &session, const std::string &proxyUrl)
void setProxy(const Kernel::ProxyInfo &proxy)
sets the proxy details.
void throwNotConnected(const std::string &url, const Poco::Net::HostNotFoundException &ex)
Throw an exception occurs when the computer is not connected to the internet.
std::streamsize getContentLength()
Gets the content length.
void clearProxy()
Clears cached proxy details.
HTTPStatus getResponseStatus()
Gets the body set for future requests.
virtual ~InternetHelper()
Destructor.
const std::string & getHeader(const std::string &key)
Gets the value of a header.
virtual HTTPStatus sendHTTPSRequest(const std::string &url, std::ostream &responseStream)
Performs a request using https.
Kernel::ProxyInfo & getProxy(const std::string &url)
Gets proxy details for a system and a url.
void setTimeout(int seconds)
Sets the timeout in seconds.
virtual HTTPStatus processErrorStates(const Poco::Net::HTTPResponse &res, std::istream &rs, const std::string &url)
Process any HTTP errors states.
virtual HTTPStatus sendHTTPRequest(const std::string &url, std::ostream &responseStream)
Performs a request using http.
virtual HTTPStatus sendRequest(const std::string &url, std::ostream &responseStream)
Performs a request using http or https depending on the url.
const std::string & getMethod()
Gets the method.
StringToStringMap & headers()
Returns a reference to the headers map.
virtual HTTPStatus downloadFile(const std::string &urlFile, const std::string &localFilePath="")
Download a url and fetch it inside the local path given.
int getTimeout()
Gets the timeout in seconds.
virtual HTTPStatus sendRequestAndProcess(Poco::Net::HTTPClientSession &session, Poco::URI &uri, std::ostream &responseStream)
void removeHeader(const std::string &key)
Removes a header.
const std::string & getContentType()
Gets the Content Type.
virtual void reset()
Resets properties to defaults (except the proxy)
std::unique_ptr< Poco::Net::HTTPResponse > m_response
void setContentType(const std::string &contentType)
Sets the Content Type.
void clearHeaders()
Clears all headers.
void setMethod(const std::string &method)
Sets the Method.
virtual void processResponseHeaders(const Poco::Net::HTTPResponse &res)
Process any headers from the response stream Basic implementation does nothing.
void debug(const std::string &msg)
Logs at debug level.
Definition Logger.cpp:145
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
void information(const std::string &msg)
Logs at information level.
Definition Logger.cpp:136
static const std::string & version()
The full version number.
ProxyInfo : Container for carrying around network proxy information.
Definition ProxyInfo.h:17
Helper class which provides the Collimation Length for SANS instruments.