Skip to content
Snippets Groups Projects
InternetHelper.cpp 12.7 KiB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
#include "MantidKernel/InternetHelper.h"
#include "MantidKernel/ConfigService.h"
#include "MantidKernel/DateAndTime.h"
#include "MantidKernel/Exception.h"
#include "MantidKernel/Logger.h"

// Poco
#include <Poco/Net/AcceptCertificateHandler.h>
#include <Poco/Net/NetException.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
#include <Poco/Net/HTTPSClientSession.h>
#include <Poco/Net/PrivateKeyPassphraseHandler.h>
#include <Poco/Net/SecureStreamSocket.h>
#include <Poco/Net/SSLManager.h>
#include <Poco/StreamCopier.h>
#include <Poco/TemporaryFile.h>
#include <Poco/URI.h>
// Visual Studio complains with the inclusion of Poco/FileStream
// disabling this warning.
#if defined(_WIN32) || defined(_WIN64)
#pragma warning( push )
#pragma warning( disable : 4250 )
 #include <Poco/FileStream.h>
 #include <Poco/NullStream.h>
 #include <Winhttp.h>
#pragma warning( pop )
#else
 #include <Poco/FileStream.h>
 #include <Poco/NullStream.h>
 #include <stdlib.h>
#endif

// std
#include <fstream>

namespace Mantid
{
namespace Kernel
{

using namespace Poco::Net;

namespace 
{ 
  // anonymous namespace for some utility functions

  /// static Logger object
  Logger g_log("InternetHelper");
}

//----------------------------------------------------------------------------------------------
/** Constructor
*/
InternetHelper::InternetHelper() : m_proxyInfo(), m_isProxySet(false),m_timeout(30)
{
}

//----------------------------------------------------------------------------------------------
/** Constructor
*/
InternetHelper::InternetHelper(const Kernel::ProxyInfo& proxy) : m_proxyInfo(proxy), m_isProxySet(true),m_timeout(30)
{
}

//----------------------------------------------------------------------------------------------
/** Destructor
*/
InternetHelper::~InternetHelper()
{
}

/** Performs a request using http or https depending on the url
* @param url the address to the network resource
* @param responseStream The stream to fill with the reply on success
* @param headers [optional] : A key value pair map of any additional headers to include in the request.
**/
int InternetHelper::sendRequest(const std::string& url, 
                    std::ostream& responseStream,
                    const StringToStringMap& headers)
{
  Poco::URI uri(url);
  if ((uri.getScheme() == "https") || (uri.getPort() ==443))
  {
    return sendHTTPSRequest(url,responseStream,headers);
  }
  else
  {
    return sendHTTPRequest(url,responseStream,headers);
  }

}

/** Performs a request using http
* @param url the address to the network resource
* @param responseStream The stream to fill with the reply on success
* @param headers [optional] : A key value pair map of any additional headers to include in the request.
**/
int InternetHelper::sendHTTPRequest(const std::string& url, 
                            std::ostream& responseStream,
                            const StringToStringMap& headers)
{
  int retStatus = 0;
  g_log.debug() << "SendingRequest : " << url  << std::endl;

  Poco::URI uri(url);
  //Configure Poco HTTP Client Session
  try
  {
    Poco::Net::HTTPClientSession session(uri.getHost(), uri.getPort());
    session.setTimeout(Poco::Timespan(m_timeout, 0)); // m_timeout seconds

    // configure proxy
    if (!getProxy(url).emptyProxy())
    {
      session.setProxyHost(getProxy(url).host());
      session.setProxyPort(static_cast<Poco::UInt16>(getProxy(url).port()));
    }

    Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_GET, uri.getPathAndQuery(),
        Poco::Net::HTTPMessage::HTTP_1_1);

    session.sendRequest(request);

    HTTPResponse res;
    std::istream & rs = session.receiveResponse(res);
    retStatus = res.getStatus();
    g_log.debug() << "Answer from web: " << res.getStatus() << " "
                  << res.getReason() << std::endl;
    
    if (res.getStatus() == HTTPResponse::HTTP_OK)
    {
      Poco::StreamCopier::copyStream(rs, responseStream);
      return res.getStatus();
    }  
    else if ((retStatus == HTTPResponse::HTTP_FOUND) ||  
      (retStatus == HTTPResponse::HTTP_MOVED_PERMANENTLY) ||
      (retStatus == HTTPResponse::HTTP_TEMPORARY_REDIRECT) ||
      (retStatus == HTTPResponse::HTTP_SEE_OTHER))
    {
      //extract the new location
      std::string newLocation = res.get("location","");
      if (newLocation != "")
      {
        return sendRequest(newLocation,responseStream,headers);
      }
    }
    else
    {
      retStatus = processErrorStates(res,rs,url);
    }

  } catch (HostNotFoundException & ex)
  {
    // this exception occurs when the pc is not connected to the internet
    std::stringstream info;
    info << "Failed to download " << url << " because there is no connection to the host "
      << ex.message() << ".\nHint: Check your connection following this link: <a href=\""
      << url << "\">" << url << "</a> ";
    throw Exception::InternetError(info.str() + ex.displayText());

  } catch (Poco::Exception & ex)
  {
    throw Exception::InternetError("Connection and request failed " + ex.displayText());
  }
  return retStatus;
}

/** Performs a request using https
* @param url the address to the network resource
* @param responseStream The stream to fill with the reply on success
* @param headers [optional] : A key value pair map of any additional headers to include in the request.
**/
int InternetHelper::sendHTTPSRequest(const std::string& url, 
                            std::ostream& responseStream,
                            const StringToStringMap& headers)
{
  int retStatus = 0;
  g_log.debug() << "SendingRequest : " << url  << std::endl;

  Poco::URI uri(url);
  try {
    // initialize ssl
    Poco::SharedPtr<InvalidCertificateHandler> certificateHandler = \
      new AcceptCertificateHandler(true);
    // Currently do not use any means of authentication. This should be updated IDS has signed certificate.
    const Context::Ptr context = \
      new Context(Context::CLIENT_USE, "", "", "", Context::VERIFY_NONE);
    // Create a singleton for holding the default context.
    // e.g. any future requests to publish are made to this certificate and context.
    SSLManager::instance().initializeClient(NULL, certificateHandler, context);
    // Create the session
    HTTPSClientSession session(uri.getHost(), static_cast<Poco::UInt16>(uri.getPort()));
    session.setTimeout(Poco::Timespan(m_timeout, 0)); // m_timeout seconds

    if (!getProxy(url).emptyProxy())
    {
      session.setProxyHost(getProxy(url).host());
      session.setProxyPort(static_cast<Poco::UInt16>(getProxy(url).port()));
    }

    // create a request
    HTTPRequest req(HTTPRequest::HTTP_GET, uri.getPathAndQuery(),
                    HTTPMessage::HTTP_1_1);
    req.set("User-Agent","MANTID");
    for (auto itHeaders = headers.begin(); itHeaders != headers.end(); ++itHeaders)
    {
      req.set(itHeaders->first,itHeaders->second);
    }
    session.sendRequest(req);

    HTTPResponse res;
    std::istream & rs = session.receiveResponse(res);
    retStatus = res.getStatus();
    g_log.debug() << "Answer from web: " << res.getStatus() << " "
                  << res.getReason() << std::endl;
        
    if (res.getStatus() == HTTPResponse::HTTP_OK)
    {
      Poco::StreamCopier::copyStream(rs, responseStream);
      return res.getStatus();
    }  
    else if ((retStatus == HTTPResponse::HTTP_FOUND) ||  
      (retStatus == HTTPResponse::HTTP_MOVED_PERMANENTLY) ||
      (retStatus == HTTPResponse::HTTP_TEMPORARY_REDIRECT) ||
      (retStatus == HTTPResponse::HTTP_SEE_OTHER))
    {
      //extract the new location
      std::string newLocation = res.get("location","");
      if (newLocation != "")
      {
        return sendRequest(newLocation,responseStream,headers);
      }
    }
    else
    {
      retStatus = processErrorStates(res,rs,url);
    }
  } 
  catch (HostNotFoundException & ex)
  {
    // this exception occurs when the pc is not connected to the internet
    std::stringstream info;
    info << "Failed to download " << url << " because there is no connection to the host "
      << ex.message() << ".\nHint: Check your connection following this link: <a href=\""
      << url << "\">" << url << "</a> ";
    throw Exception::InternetError(info.str() + ex.displayText());

  } catch (Poco::Exception & ex)
  {
    throw Exception::InternetError("Connection and request failed " + ex.displayText());
  }
  return retStatus;
}

/** Gets proxy details for a system and a url.
@param url : The url to be called
*/
Kernel::ProxyInfo& InternetHelper::getProxy(const std::string& url)
{
  //set the proxy
  if (!m_isProxySet)
  {  
    setProxy(ConfigService::Instance().getProxy(url));
  }
  return m_proxyInfo;
}

/** Clears cached proxy details.
*/
void InternetHelper::clearProxy()
{
  m_isProxySet = false;
}

/** sets the proxy details.
@param proxy the proxy information to use
*/
void InternetHelper::setProxy(const Kernel::ProxyInfo& proxy)
{
  m_proxyInfo = proxy;
  m_isProxySet = true;
}

/** Process any HTTP errors states.

@param res : The http response
@param rs : The iutput stream from the response
@param url : The url originally called

@exception Mantid::Kernel::Exception::InternetError : Coded for the failure state.
*/
int InternetHelper::processErrorStates(const Poco::Net::HTTPResponse& res, std::istream & rs,const std::string& url)
{
  int retStatus = res.getStatus();
  g_log.debug() << "Answer from web: " << res.getStatus() << " "
                << res.getReason() << std::endl;
        
  //get github api rate limit information if available;
  int rateLimitRemaining;
  DateAndTime rateLimitReset;
  try 
  {
    rateLimitRemaining = boost::lexical_cast<int>( res.get("X-RateLimit-Remaining","-1") );
    rateLimitReset.set_from_time_t(boost::lexical_cast<int>( res.get("X-RateLimit-Reset","0")));
  }
  catch( boost::bad_lexical_cast const& ) 
  {
    rateLimitRemaining = -1;
  }

  if (retStatus == HTTPResponse::HTTP_OK)
  {
    throw Exception::InternetError("Response was ok, processing should never have entered processErrorStates",retStatus);
  }
  else if (retStatus == HTTPResponse::HTTP_FOUND)
  {
    throw Exception::InternetError("Response was HTTP_FOUND, processing should never have entered processErrorStates",retStatus);
  }  
  else if (retStatus == HTTPResponse::HTTP_MOVED_PERMANENTLY)
  {
    throw Exception::InternetError("Response was HTTP_MOVED_PERMANENTLY, processing should never have entered processErrorStates",retStatus);
  }
  else if (retStatus == HTTPResponse::HTTP_NOT_MODIFIED)
  {
    throw Exception::InternetError("Not modified since provided date" + 
                                    rateLimitReset.toSimpleString(),retStatus);
  }        
  else if ((retStatus == HTTPResponse::HTTP_FORBIDDEN) && (rateLimitRemaining == 0))
  {
    throw Exception::InternetError("The Github API rate limit has been reached, try again after " + 
                                    rateLimitReset.toSimpleString(),retStatus);
  }
  else
  {
    std::stringstream info;
    std::stringstream ss;
    Poco::StreamCopier::copyStream(rs, ss);
    if (retStatus == HTTPResponse::HTTP_NOT_FOUND)
      info << "Failed to download " << url
      << " with the link " << "<a href=\"" << url
      << "\">.\n" << "Hint. Check that link is correct</a>";
    else
    {
      // show the error
      info << res.getReason();
      info << ss.str();
    }
    throw Exception::InternetError(info.str() + ss.str(),retStatus);
  }

}


/** Download a url and fetch it inside the local path given.

@param urlFile: Define a valid URL for the file to be downloaded. Eventually, it may give
any valid https path. For example:

url_file = "http://www.google.com"

url_file = "https://mantidweb/repository/README.md"

The result is to connect to the http server, and request the path given.

The answer, will be inserted at the local_file_path.

@param localFilePath : Provide the destination of the file downloaded at the url_file.

@param headers [optional] : A key value pair map of any additional headers to include in the request.

@exception Mantid::Kernel::Exception::InternetError : For any unexpected behaviour.
*/
int InternetHelper::downloadFile(const std::string & urlFile,
  const std::string & localFilePath,
  const StringToStringMap & headers)
{
  int retStatus = 0;
  g_log.debug() << "DownloadFile : " << urlFile << " to file: " << localFilePath << std::endl;

  Poco::TemporaryFile tempFile;
  Poco::FileStream tempFileStream(tempFile.path());
  retStatus = sendRequest(urlFile,tempFileStream,headers);
  tempFileStream.close();

  //if there have been no errors move it to the final location, and turn off automatic deletion.
  tempFile.moveTo(localFilePath);
  tempFile.keep();

  return retStatus;
}

/** Sets the timeout in seconds
* @param seconds The value in seconds for the timeout
**/
void InternetHelper::setTimeout(int seconds)
{
  m_timeout = seconds;
}


} // namespace Kernel
} // namespace Mantid