From bc77c8b78fc55cd68e0eefd96441d22a997c222f Mon Sep 17 00:00:00 2001 From: Nick Draper <nick.draper@stfc.ac.uk> Date: Fri, 21 Nov 2014 09:53:12 +0000 Subject: [PATCH] re #10549 create DownloadFile and InternetHelper --- .../Framework/DataHandling/CMakeLists.txt | 9 +- .../inc/MantidDataHandling/DownloadFile.h | 67 +++ .../DataHandling/src/DownloadFile.cpp | 93 ++++ .../DataHandling/test/DownloadFileTest.h | 142 ++++++ Code/Mantid/Framework/Kernel/CMakeLists.txt | 3 + .../Kernel/inc/MantidKernel/ConfigService.h | 9 + .../Kernel/inc/MantidKernel/Exception.h | 25 ++ .../Kernel/inc/MantidKernel/InternetHelper.h | 92 ++++ .../Framework/Kernel/src/ConfigService.cpp | 35 +- .../Mantid/Framework/Kernel/src/Exception.cpp | 51 +++ .../Framework/Kernel/src/InternetHelper.cpp | 403 ++++++++++++++++++ .../Kernel/test/InternetHelperTest.h | 125 ++++++ .../src/ScriptRepositoryImpl.cpp | 2 +- .../source/algorithms/DownloadFile-v1.rst | 73 ++++ 14 files changed, 1123 insertions(+), 6 deletions(-) create mode 100644 Code/Mantid/Framework/DataHandling/inc/MantidDataHandling/DownloadFile.h create mode 100644 Code/Mantid/Framework/DataHandling/src/DownloadFile.cpp create mode 100644 Code/Mantid/Framework/DataHandling/test/DownloadFileTest.h create mode 100644 Code/Mantid/Framework/Kernel/inc/MantidKernel/InternetHelper.h create mode 100644 Code/Mantid/Framework/Kernel/src/InternetHelper.cpp create mode 100644 Code/Mantid/Framework/Kernel/test/InternetHelperTest.h create mode 100644 Code/Mantid/docs/source/algorithms/DownloadFile-v1.rst diff --git a/Code/Mantid/Framework/DataHandling/CMakeLists.txt b/Code/Mantid/Framework/DataHandling/CMakeLists.txt index 5df27be94a9..0bbf2bc35bf 100644 --- a/Code/Mantid/Framework/DataHandling/CMakeLists.txt +++ b/Code/Mantid/Framework/DataHandling/CMakeLists.txt @@ -10,6 +10,7 @@ set ( SRC_FILES src/DefineGaugeVolume.cpp src/DeleteTableRows.cpp src/DetermineChunking.cpp + src/DownloadFile.cpp src/ExtractMonitorWorkspace.cpp src/FilterEventsByLogValuePreNexus.cpp src/FindDetectorsInShape.cpp @@ -126,10 +127,9 @@ set ( SRC_FILES src/SaveMask.cpp src/SaveNISTDAT.cpp src/SaveNXSPE.cpp + src/SaveNXTomo.cpp src/SaveNexus.cpp src/SaveNexusProcessed.cpp - src/SaveNXTomo.cpp - src/SaveParameterFile.cpp src/SavePAR.cpp src/SavePDFGui.cpp src/SavePHX.cpp @@ -156,6 +156,7 @@ set ( INC_FILES inc/MantidDataHandling/DefineGaugeVolume.h inc/MantidDataHandling/DeleteTableRows.h inc/MantidDataHandling/DetermineChunking.h + inc/MantidDataHandling/DownloadFile.h inc/MantidDataHandling/ExtractMonitorWorkspace.h inc/MantidDataHandling/FilterEventsByLogValuePreNexus.h inc/MantidDataHandling/FindDetectorsInShape.h @@ -267,10 +268,9 @@ set ( INC_FILES inc/MantidDataHandling/SaveMask.h inc/MantidDataHandling/SaveNISTDAT.h inc/MantidDataHandling/SaveNXSPE.h + inc/MantidDataHandling/SaveNXTomo.h inc/MantidDataHandling/SaveNexus.h inc/MantidDataHandling/SaveNexusProcessed.h - inc/MantidDataHandling/SaveNXTomo.h - inc/MantidDataHandling/SaveParameterFile.h inc/MantidDataHandling/SavePAR.h inc/MantidDataHandling/SavePDFGui.h inc/MantidDataHandling/SavePHX.h @@ -301,6 +301,7 @@ set ( TEST_FILES DefineGaugeVolumeTest.h DeleteTableRowsTest.h DetermineChunkingTest.h + DownloadFileTest.h ExtractMonitorWorkspaceTest.h FilterEventsByLogValuePreNexusTest.h FindDetectorsInShapeTest.h diff --git a/Code/Mantid/Framework/DataHandling/inc/MantidDataHandling/DownloadFile.h b/Code/Mantid/Framework/DataHandling/inc/MantidDataHandling/DownloadFile.h new file mode 100644 index 00000000000..43063c68806 --- /dev/null +++ b/Code/Mantid/Framework/DataHandling/inc/MantidDataHandling/DownloadFile.h @@ -0,0 +1,67 @@ +#ifndef MANTID_DATAHANDLING_DOWNLOADFILE_H_ +#define MANTID_DATAHANDLING_DOWNLOADFILE_H_ + +#include "MantidKernel/System.h" +#include "MantidAPI/Algorithm.h" + +namespace Mantid +{ + +namespace Kernel +{ + //forward Declaration + class InternetHelper; +} + +namespace DataHandling +{ + + /** DownloadFile : Downloads a file from a url to the file system + + Copyright © 2014 ISIS Rutherford Appleton Laboratory & NScD Oak Ridge National Laboratory + + This file is part of Mantid. + + Mantid is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + Mantid is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + File change history is stored at: <https://github.com/mantidproject/mantid> + Code Documentation is available at: <http://doxygen.mantidproject.org> + */ + class DLLExport DownloadFile : public API::Algorithm + { + public: + DownloadFile(); + virtual ~DownloadFile(); + + virtual const std::string name() const; + virtual int version() const; + virtual const std::string category() const; + virtual const std::string summary() const; + + protected: + Kernel::InternetHelper* m_internetHelper; + + private: + void init(); + void exec(); + + + + }; + + +} // namespace DataHandling +} // namespace Mantid + +#endif /* MANTID_DATAHANDLING_DOWNLOADFILE_H_ */ \ No newline at end of file diff --git a/Code/Mantid/Framework/DataHandling/src/DownloadFile.cpp b/Code/Mantid/Framework/DataHandling/src/DownloadFile.cpp new file mode 100644 index 00000000000..37c0c322ad3 --- /dev/null +++ b/Code/Mantid/Framework/DataHandling/src/DownloadFile.cpp @@ -0,0 +1,93 @@ +#include "MantidDataHandling/DownloadFile.h" +#include "MantidKernel/InternetHelper.h" +#include "MantidKernel/ListValidator.h" +#include "MantidKernel/MandatoryValidator.h" +#include "MantidAPI/FileProperty.h" + +#include "Poco/URI.h" + +#include "boost/make_shared.hpp" +#include "boost/algorithm/string/predicate.hpp" + +#include <string> +#include <stdexcept> + +namespace Mantid +{ +namespace DataHandling +{ + + using Mantid::Kernel::Direction; + using Mantid::Kernel::MandatoryValidator; + using Mantid::Kernel::StringListValidator; + using Mantid::API::WorkspaceProperty; + using Mantid::API::FileProperty; + + // Register the algorithm into the AlgorithmFactory + DECLARE_ALGORITHM(DownloadFile) + + + + //---------------------------------------------------------------------------------------------- + /** Constructor + */ + DownloadFile::DownloadFile():m_internetHelper(new Kernel::InternetHelper()) + { + } + + //---------------------------------------------------------------------------------------------- + /** Destructor + */ + DownloadFile::~DownloadFile() + { + delete m_internetHelper; + } + + + //---------------------------------------------------------------------------------------------- + + /// Algorithms name for identification. @see Algorithm::name + const std::string DownloadFile::name() const { return "DownloadFile"; } + + /// Algorithm's version for identification. @see Algorithm::version + int DownloadFile::version() const { return 1;} + + /// Algorithm's category for identification. @see Algorithm::category + const std::string DownloadFile::category() const { return "DataHandling";} + + /// Algorithm's summary for use in the GUI and help. @see Algorithm::summary + const std::string DownloadFile::summary() const { return "Downloads a file from a url to the file system";} + + + //---------------------------------------------------------------------------------------------- + /** Initialize the algorithm's properties. + */ + void DownloadFile::init() + { + declareProperty("Address", "", boost::make_shared<MandatoryValidator<std::string> >(), + "The address of the network resource to download. This should start http:// or https:// .", Direction::InOut); + declareProperty(new FileProperty("Filename", "", FileProperty::Save), + "The filename to save the download to."); + } + + //---------------------------------------------------------------------------------------------- + /** Execute the algorithm. + */ + void DownloadFile::exec() + { + std::string address = getProperty("Address"); + if ((!boost::starts_with(address,"http://")) && (!boost::starts_with(address,"https://"))) + { + address = "http://" + address; + g_log.information("Address must start http:// or https://, http has been assumed to continue: " + address); + } + std::string filename = getProperty("Filename"); + + Poco::URI url(address); + m_internetHelper->downloadFile(url.toString(),filename); + setProperty("Address",address); + } + + +} // namespace DataHandling +} // namespace Mantid \ No newline at end of file diff --git a/Code/Mantid/Framework/DataHandling/test/DownloadFileTest.h b/Code/Mantid/Framework/DataHandling/test/DownloadFileTest.h new file mode 100644 index 00000000000..1c04536f737 --- /dev/null +++ b/Code/Mantid/Framework/DataHandling/test/DownloadFileTest.h @@ -0,0 +1,142 @@ +#ifndef MANTID_DATAHANDLING_DOWNLOADFILETEST_H_ +#define MANTID_DATAHANDLING_DOWNLOADFILETEST_H_ + +#include <cxxtest/TestSuite.h> + +#include "MantidDataHandling/DownloadFile.h" +#include "MantidKernel/InternetHelper.h" + +#include <Poco/TemporaryFile.h> + + +#include <fstream> +#include <sstream> + +using Mantid::Kernel::InternetHelper; +using Mantid::DataHandling::DownloadFile; +using namespace Mantid::Kernel; +using namespace Mantid::API; + + +namespace +{ + /** + * Mock out the internet calls of this helper + */ + class MockedInternetHelper : public InternetHelper + { + protected: + virtual int sendHTTPSRequest(const std::string& url, + std::ostream& responseStream, + const StringToStringMap& headers = StringToStringMap()) + { + responseStream << "HTTPS request succeeded"; + return 200; + } + virtual int sendHTTPRequest(const std::string& url, + std::ostream& responseStream, + const StringToStringMap& headers = StringToStringMap()) + { + responseStream << "HTTP request succeeded"; + return 200; + } + }; + + /** + * Mock out the internet calls of this algorithm + */ + class MockedDownloadFile : public DownloadFile + { + public: + MockedDownloadFile::MockedDownloadFile() + { + delete m_internetHelper; + m_internetHelper = new MockedInternetHelper(); + } + }; +} + + +class DownloadFileTest : public CxxTest::TestSuite +{ +public: + // This pair of boilerplate methods prevent the suite being created statically + // This means the constructor isn't called when running other tests + static DownloadFileTest *createSuite() { return new DownloadFileTest(); } + static void destroySuite( DownloadFileTest *suite ) { delete suite; } + + + void test_Init() + { + DownloadFile alg; + TS_ASSERT_THROWS_NOTHING( alg.initialize() ) + TS_ASSERT( alg.isInitialized() ) + } + + void test_Bad_Address() + { + MockedInternetHelper internetHelper; + std::string url = "www.google.com"; + Poco::TemporaryFile tmpFile; + exec_alg(url,tmpFile.path(), "http://" + url); + } + + void exec_alg(std::string address, std::string filename, std::string newAddress = "") + { + MockedDownloadFile alg; + TS_ASSERT_THROWS_NOTHING( alg.initialize() ) + TS_ASSERT( alg.isInitialized() ) + TS_ASSERT_THROWS_NOTHING( alg.setPropertyValue("Address", address) ); + TS_ASSERT_THROWS_NOTHING( alg.setPropertyValue("Filename", filename) ); + TS_ASSERT_THROWS_NOTHING( alg.execute(); ); + TS_ASSERT( alg.isExecuted() ); + if (newAddress.size() > 0) + { + TS_ASSERT_EQUALS(newAddress, alg.getPropertyValue("Address") ); + } + } + + void test_DownloadFile_HTTP() + { + MockedInternetHelper internetHelper; + std::string url = "http://www.google.com"; + Poco::TemporaryFile tmpFile; + exec_alg(url,tmpFile.path()); + TSM_ASSERT("File has not been created.",tmpFile.exists()); + TSM_ASSERT("File is not a file.",tmpFile.isFile()); + std::fstream fs; + TS_ASSERT_THROWS_NOTHING (fs.open (tmpFile.path(), std::fstream::in )); + + TSM_ASSERT("Cannot open file.",fs.is_open()); + + std::stringstream ss; + ss << fs.rdbuf();//read the file + fs.close(); + + TS_ASSERT_EQUALS ("HTTP request succeeded", ss.str()); + } + + void test_DownloadFile_HTTPS() + { + MockedInternetHelper internetHelper; + std::string httpsUrl = "https://api.github.com/repos/mantidproject/mantid/contents"; + Poco::TemporaryFile tmpFile; + exec_alg(httpsUrl,tmpFile.path()); + TSM_ASSERT("File has not been created.",tmpFile.exists()); + TSM_ASSERT("File is not a file.",tmpFile.isFile()); + std::fstream fs; + TS_ASSERT_THROWS_NOTHING (fs.open (tmpFile.path(), std::fstream::in )); + + TSM_ASSERT("Cannot open file.",fs.is_open()); + + std::stringstream ss; + ss << fs.rdbuf();//read the file + fs.close(); + + TS_ASSERT_EQUALS ("HTTPS request succeeded", ss.str()); + } + +}; + + +#endif /* MANTID_DATAHANDLING_DOWNLOADFILETEST_H_ */ \ No newline at end of file diff --git a/Code/Mantid/Framework/Kernel/CMakeLists.txt b/Code/Mantid/Framework/Kernel/CMakeLists.txt index c76df1829da..897b0ac8c7c 100644 --- a/Code/Mantid/Framework/Kernel/CMakeLists.txt +++ b/Code/Mantid/Framework/Kernel/CMakeLists.txt @@ -32,6 +32,7 @@ set ( SRC_FILES src/IPropertyManager.cpp src/ISaveable.cpp src/InstrumentInfo.cpp + src/InternetHelper.cpp src/Interpolation.cpp src/LibraryManager.cpp src/LibraryWrapper.cpp @@ -160,6 +161,7 @@ set ( INC_FILES inc/MantidKernel/IValidator.h inc/MantidKernel/Instantiator.h inc/MantidKernel/InstrumentInfo.h + inc/MantidKernel/InternetHelper.h inc/MantidKernel/Interpolation.h inc/MantidKernel/LibraryManager.h inc/MantidKernel/LibraryWrapper.h @@ -284,6 +286,7 @@ set ( TEST_FILES ISaveableTest.h IValidatorTest.h InstrumentInfoTest.h + InternetHelperTest.h InterpolationTest.h ListValidatorTest.h LogFilterTest.h diff --git a/Code/Mantid/Framework/Kernel/inc/MantidKernel/ConfigService.h b/Code/Mantid/Framework/Kernel/inc/MantidKernel/ConfigService.h index 498154a9719..baa5a86715f 100644 --- a/Code/Mantid/Framework/Kernel/inc/MantidKernel/ConfigService.h +++ b/Code/Mantid/Framework/Kernel/inc/MantidKernel/ConfigService.h @@ -6,6 +6,7 @@ //---------------------------------------------------------------------- #include "MantidKernel/DllConfig.h" #include "MantidKernel/SingletonHolder.h" +#include "MantidKernel/ProxyInfo.h" #include <vector> #include <map> #include <set> @@ -227,6 +228,8 @@ namespace Mantid /// Quick check to determine if vates has been installed. bool quickVatesCheck() const; + Kernel::ProxyInfo& getProxy(const std::string& url); + private: friend struct Mantid::Kernel::CreateUsingNew<ConfigServiceImpl>; /// Handles distribution of Poco signals. @@ -307,6 +310,12 @@ namespace Mantid std::vector<FacilityInfo*> m_facilities; /// Define a flag value for a removed property const std::string m_removedFlag; + + ///local cache of proxy details + Kernel::ProxyInfo m_proxyInfo; + ///wether the proxy has been populated yet + bool m_isProxySet; + }; /// Forward declaration of a specialisation of SingletonHolder for AlgorithmFactoryImpl (needed for dllexport/dllimport) and a typedef for it. diff --git a/Code/Mantid/Framework/Kernel/inc/MantidKernel/Exception.h b/Code/Mantid/Framework/Kernel/inc/MantidKernel/Exception.h index 3717fdc5220..677f3be39ff 100644 --- a/Code/Mantid/Framework/Kernel/inc/MantidKernel/Exception.h +++ b/Code/Mantid/Framework/Kernel/inc/MantidKernel/Exception.h @@ -345,6 +345,31 @@ class MANTID_KERNEL_DLL OpenGLError: public std::runtime_error const char* what() const throw(); }; + /** Exception thrown when error occurs accessing an internet resource + * + * @author Nick Draper, Tessella + * @date 13/11/2013 + */ + class MANTID_KERNEL_DLL + InternetError : public std::runtime_error + { + private: + /// The message returned by what() + std::string outMessage; + int m_errorCode; ///< The message reported by what() + + public: + InternetError(const std::string& message, const int& errorCode = 0); + InternetError(const InternetError&); + ~InternetError() throw() {} + + InternetError & operator=(const InternetError &); + + /// Overloaded reporting method + const char* what() const throw(); + const int& errorCode() const; + }; + } //namespace Exception } // namespace Kernel } // namespace Mantid diff --git a/Code/Mantid/Framework/Kernel/inc/MantidKernel/InternetHelper.h b/Code/Mantid/Framework/Kernel/inc/MantidKernel/InternetHelper.h new file mode 100644 index 00000000000..b5ad080f184 --- /dev/null +++ b/Code/Mantid/Framework/Kernel/inc/MantidKernel/InternetHelper.h @@ -0,0 +1,92 @@ +#ifndef MANTID_KERNEL_InternetHelper_H_ +#define MANTID_KERNEL_InternetHelper_H_ + +#include "MantidKernel/System.h" +#include "MantidKernel/DllConfig.h" +#include "MantidKernel/ProxyInfo.h" + +#include <map> + +namespace Poco +{ + namespace Net + { + //forward declaration + class HTTPResponse; + } +} + + +namespace Mantid +{ +namespace Kernel +{ + //forward declaration + class Logger; + + /** InternetHelper : A helper class for supporting access to resources through HTTP and HTTPS + + Copyright © 2014 ISIS Rutherford Appleton Laboratory & NScD Oak Ridge National Laboratory + + This file is part of Mantid. + + Mantid is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + Mantid is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + File change history is stored at: <https://github.com/mantidproject/mantid> + Code Documentation is available at: <http://doxygen.mantidproject.org> + */ + class MANTID_KERNEL_DLL InternetHelper + { + public: + InternetHelper(); + InternetHelper(const Kernel::ProxyInfo& proxy); + virtual ~InternetHelper(); + + // Convenience typedef + typedef std::map<std::string, std::string> StringToStringMap; + + virtual int downloadFile(const std::string& urlFile, const std::string& localFilePath = "", + const StringToStringMap& headers = StringToStringMap()); + virtual int sendRequest(const std::string& url, + std::ostream& responseStream, + const StringToStringMap& headers = StringToStringMap()); + + Kernel::ProxyInfo& getProxy(const std::string& url); + void clearProxy(); + void setProxy(const Kernel::ProxyInfo& proxy); + + void setTimeout(int seconds); + + protected: + virtual int sendHTTPSRequest(const std::string& url, + std::ostream& responseStream, + const StringToStringMap& headers = StringToStringMap()); + virtual int sendHTTPRequest(const std::string& url, + std::ostream& responseStream, + const StringToStringMap& headers = StringToStringMap()); + virtual int processErrorStates(const Poco::Net::HTTPResponse& res, std::istream & rs,const std::string& url); + private: + + Kernel::ProxyInfo m_proxyInfo; + bool m_isProxySet; + int m_timeout; + }; + + +} // namespace Kernel +} // namespace Mantid + +#endif /* MANTID_KERNEL_InternetHelper_H_ */ + + diff --git a/Code/Mantid/Framework/Kernel/src/ConfigService.cpp b/Code/Mantid/Framework/Kernel/src/ConfigService.cpp index f81d562cb7a..2f0dd6417d8 100644 --- a/Code/Mantid/Framework/Kernel/src/ConfigService.cpp +++ b/Code/Mantid/Framework/Kernel/src/ConfigService.cpp @@ -11,6 +11,7 @@ #include "MantidKernel/StdoutChannel.h" #include "MantidKernel/Exception.h" #include "MantidKernel/FacilityInfo.h" +#include "MantidKernel/NetworkProxy.h" #include <Poco/Util/LoggingConfigurator.h> #include <Poco/Util/SystemConfiguration.h> @@ -27,6 +28,7 @@ #include <Poco/Environment.h> #include <Poco/Process.h> #include <Poco/String.h> +#include <Poco/URI.h> #ifdef _WIN32 #pragma warning( disable: 4250 ) #endif @@ -182,7 +184,8 @@ ConfigServiceImpl::ConfigServiceImpl() : #else m_user_properties_file_name("Mantid.user.properties"), #endif - m_DataSearchDirs(), m_UserSearchDirs(), m_InstrumentDirs(), m_instr_prefixes(), m_removedFlag("@@REMOVED@@") + m_DataSearchDirs(), m_UserSearchDirs(), m_InstrumentDirs(), m_instr_prefixes(), m_removedFlag("@@REMOVED@@"), + m_proxyInfo(),m_isProxySet(false) { //getting at system details m_pSysConfig = new WrappedObject<Poco::Util::SystemConfiguration> ; @@ -2021,6 +2024,36 @@ bool ConfigServiceImpl::quickVatesCheck() const return found; } +/* +Gets the system proxy information +@url A url to match the proxy to +@return the proxy information. +*/ +Kernel::ProxyInfo& ConfigServiceImpl::getProxy(const std::string& url) +{ + if (!m_isProxySet) + { + //set the proxy + //first check if the proxy is defined in the properties file + std::string proxyHost; + int proxyPort; + if ((getValue("proxy.host",proxyHost) == 1) && (getValue("proxy.port",proxyPort) == 1)) + { + //set it from the config values + m_proxyInfo = ProxyInfo(proxyHost,proxyPort,true); + } + else + { + //get the system proxy + Poco::URI uri(url); + Mantid::Kernel::NetworkProxy proxyHelper; + m_proxyInfo = proxyHelper.getHttpProxy(uri.toString()); + } + m_isProxySet = true; + } + return m_proxyInfo; +} + /// \cond TEMPLATE template DLLExport int ConfigServiceImpl::getValue(const std::string&, double&); template DLLExport int ConfigServiceImpl::getValue(const std::string&, std::string&); diff --git a/Code/Mantid/Framework/Kernel/src/Exception.cpp b/Code/Mantid/Framework/Kernel/src/Exception.cpp index d5330df3d98..f2784d76054 100644 --- a/Code/Mantid/Framework/Kernel/src/Exception.cpp +++ b/Code/Mantid/Framework/Kernel/src/Exception.cpp @@ -378,6 +378,57 @@ const char* NullPointerException::what() const throw() return outMessage.c_str(); } +//------------------------- +// InternetError Error class +//------------------------- + +/** + Constructor + @param message :: The error message + @param errorCode :: The HTTP error code if available +*/ +InternetError::InternetError(const std::string& message, const int& errorCode) : + std::runtime_error(message) +{ + std::stringstream cx; + cx << "InternetError: "; + if (errorCode!=0) + { + cx << "[" << errorCode << "] "; + } + cx << message; + outMessage = cx.str(); + m_errorCode = errorCode; +} + +/** + Copy Constructor + @param A :: IndexError to copy +*/ +InternetError::InternetError(const InternetError& A) : + std::runtime_error(A) +{} + +/** + Writes out the range and limits + @return the error string +*/ +const char* InternetError::what() const throw() +{ + return outMessage.c_str(); +} + +/** + Writes out the range and limits + @return the error string +*/ +const int& InternetError::errorCode() const +{ + return m_errorCode; +} + + + } // namespace Exception } // namespace Kernel } // namespace Mantid diff --git a/Code/Mantid/Framework/Kernel/src/InternetHelper.cpp b/Code/Mantid/Framework/Kernel/src/InternetHelper.cpp new file mode 100644 index 00000000000..96c699176c5 --- /dev/null +++ b/Code/Mantid/Framework/Kernel/src/InternetHelper.cpp @@ -0,0 +1,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 \ No newline at end of file diff --git a/Code/Mantid/Framework/Kernel/test/InternetHelperTest.h b/Code/Mantid/Framework/Kernel/test/InternetHelperTest.h new file mode 100644 index 00000000000..209129f9b0f --- /dev/null +++ b/Code/Mantid/Framework/Kernel/test/InternetHelperTest.h @@ -0,0 +1,125 @@ +#ifndef MANTID_KERNEL_INTERNETSERVICETEST_H_ +#define MANTID_KERNEL_INTERNETSERVICETEST_H_ + +#include <cxxtest/TestSuite.h> + +#include "MantidKernel/InternetHelper.h" +#include "MantidKernel/ConfigService.h" +#include "MantidKernel/NetworkProxy.h" +#include "MantidKernel/ProxyInfo.h" + +#include <Poco/TemporaryFile.h> + + +#include <fstream> +#include <sstream> + +using Mantid::Kernel::InternetHelper; +using namespace Mantid::Kernel; + + + +namespace +{ + /** + * Mock out the internet calls of this algorithm + */ + class MockedInternetHelper : public InternetHelper + { + protected: + virtual int sendHTTPSRequest(const std::string& url, + std::ostream& responseStream, + const StringToStringMap& headers = StringToStringMap()) + { + responseStream << "HTTPS request succeeded"; + return 200; + } + virtual int sendHTTPRequest(const std::string& url, + std::ostream& responseStream, + const StringToStringMap& headers = StringToStringMap()) + { + responseStream << "HTTP request succeeded"; + return 200; + } + }; +} + + +class InternetHelperTest : public CxxTest::TestSuite +{ +public: + // This pair of boilerplate methods prevent the suite being created statically + // This means the constructor isn't called when running other tests + static InternetHelperTest *createSuite() { return new InternetHelperTest(); } + static void destroySuite( InternetHelperTest *suite ) { delete suite; } + + + void test_sendRequest_HTTP() + { + MockedInternetHelper internetHelper; + std::string url = "http://www.google.com"; + + std::stringstream ss; + int response; + TS_ASSERT_THROWS_NOTHING (response = internetHelper.sendRequest(url, ss);) + TS_ASSERT_EQUALS (200, response); + TS_ASSERT_EQUALS ("HTTP request succeeded", ss.str()); + } + + void test_sendRequest_HTTPS() + { + MockedInternetHelper internetHelper; + std::string httpsUrl = "https://api.github.com/repos/mantidproject/mantid/contents"; + + std::stringstream ss; + int response; + TS_ASSERT_THROWS_NOTHING (response = internetHelper.sendRequest(httpsUrl, ss);) + TS_ASSERT_EQUALS (200, response); + TS_ASSERT_EQUALS ("HTTPS request succeeded", ss.str()); + } + + void test_DownloadFile_HTTP() + { + MockedInternetHelper internetHelper; + std::string url = "http://www.google.com"; + Poco::TemporaryFile tmpFile; + int response = internetHelper.downloadFile(url,tmpFile.path()); + TSM_ASSERT("File has not been created.",tmpFile.exists()); + TSM_ASSERT("File is not a file.",tmpFile.isFile()); + std::fstream fs; + TS_ASSERT_THROWS_NOTHING (fs.open (tmpFile.path(), std::fstream::in )); + + TSM_ASSERT("Cannot open file.",fs.is_open()); + + std::stringstream ss; + ss << fs.rdbuf();//read the file + fs.close(); + + TS_ASSERT_EQUALS ("HTTP request succeeded", ss.str()); + } + + void test_DownloadFile_HTTPS() + { + MockedInternetHelper internetHelper; + std::string httpsUrl = "https://api.github.com/repos/mantidproject/mantid/contents"; + Poco::TemporaryFile tmpFile; + int response = internetHelper.downloadFile(httpsUrl,tmpFile.path()); + TSM_ASSERT("File has not been created.",tmpFile.exists()); + TSM_ASSERT("File is not a file.",tmpFile.isFile()); + std::fstream fs; + TS_ASSERT_THROWS_NOTHING (fs.open (tmpFile.path(), std::fstream::in )); + + TSM_ASSERT("Cannot open file.",fs.is_open()); + + std::stringstream ss; + ss << fs.rdbuf();//read the file + fs.close(); + + TS_ASSERT_EQUALS ("HTTPS request succeeded", ss.str()); + } + + +}; + + +#endif /* MANTID_KERNEL_INTERNETSERVICETEST_H_ */ \ No newline at end of file diff --git a/Code/Mantid/Framework/ScriptRepository/src/ScriptRepositoryImpl.cpp b/Code/Mantid/Framework/ScriptRepository/src/ScriptRepositoryImpl.cpp index 94e44bbca25..3d4501caddb 100644 --- a/Code/Mantid/Framework/ScriptRepository/src/ScriptRepositoryImpl.cpp +++ b/Code/Mantid/Framework/ScriptRepository/src/ScriptRepositoryImpl.cpp @@ -833,7 +833,6 @@ namespace Mantid // inserting the file FilePartSource * m_file = new FilePartSource(absolute_path); form.addPart("file", m_file); - form.prepareSubmit(req); // get the size of everything std::stringstream sst; @@ -844,6 +843,7 @@ namespace Mantid // set the size req.setContentLength((int) sst.str().size()); + form.prepareSubmit(req); std::ostream& ostr = session.sendRequest(req); // send the request. ostr << sst.str(); diff --git a/Code/Mantid/docs/source/algorithms/DownloadFile-v1.rst b/Code/Mantid/docs/source/algorithms/DownloadFile-v1.rst new file mode 100644 index 00000000000..5f0407d72a2 --- /dev/null +++ b/Code/Mantid/docs/source/algorithms/DownloadFile-v1.rst @@ -0,0 +1,73 @@ + +.. algorithm:: + +.. summary:: + +.. alias:: + +.. properties:: + +Description +----------- + +This is a simple algorithm that will download the contents of a url address to a file. +It can support http:// and https:// based urls, and if the method is not supplied then http:// will be assumed. +For example: If the address is www.mantidproject.org, then this will be adjusted to http://www.mantidproject.org. + + +Usage +----- + +**Example - http** + +.. testcode:: DownloadFileHttp + + #import the os path libraries for directory functions + import os + + #Create an absolute path by joining the proposed filename to a directory + #os.path.expanduser("~") used in this case returns the home directory of the current user + savefile = os.path.join(os.path.expanduser("~"), "DownloadedFile.txt") + + DownloadFile("http://www.mantidproject.org", savefile) + + print "File Exists:", os.path.exists(savefile) + +.. testcleanup:: DownloadFileHttp + + os.remove(savefile) + +Output: + +.. testoutput:: DownloadFileHttp + + File Exists: True + + +**Example - https** + +.. testcode:: DownloadFileHttps + + #import the os path libraries for directory functions + import os + + #Create an absolute path by joining the proposed filename to a directory + #os.path.expanduser("~") used in this case returns the home directory of the current user + savefile = os.path.join(os.path.expanduser("~"), "DownloadedFile.txt") + + DownloadFile("https://raw.githubusercontent.com/mantidproject/mantid/master/README.md", savefile) + + print "File Exists:", os.path.exists(savefile) + +.. testcleanup:: DownloadFileHttps + + os.remove(savefile) + +Output: + +.. testoutput:: DownloadFileHttps + + File Exists: True + +.. categories:: + -- GitLab