host/AzureRecoveryLib/resthelper/HttpUtil.cpp (325 lines of code) (raw):

/* +------------------------------------------------------------------------------------+ Copyright(c) Microsoft Corp. 2015 +------------------------------------------------------------------------------------+ File : HttpUtil.cpp Description : Utility classes & functions implementations History : 29-4-2015 (Venu Sivanadham) - Created +------------------------------------------------------------------------------------+ */ #include "HttpUtil.h" #include "../common/Trace.h" #include "../common/AzureRecoveryException.h" #include "../inmsafeint/inmsafeint.h" #include "../inmsafecapis/inmsafecapis.h" #include "../errorexception/errorexception.h" #include <exception> #include <sstream> #include <curl/curl.h> #include <boost/assert.hpp> #include <boost/algorithm/string.hpp> #include <boost/algorithm/string/split.hpp> #include <boost/property_tree/json_parser.hpp> const char IMDS_URL[] = "http://169.254.169.254/metadata/instance?api-version=2021-02-01"; const char IMDS_HEADERS[] = "Metadata: true"; const char IMDS_COMPUTE_ENV[] = "compute.azEnvironment"; const char IMDS_AZURESTACK_NAME[] = "AzureStack"; const long HTTP_OK = 200L; #define INMAGE_EX ContextualException( __FILE__, __LINE__, __FUNCTION__ ) namespace AzureStorageRest { typedef struct tagMemoryStruct { char *memory; size_t insize; size_t size; } MemoryStruct; size_t WriteMemoryCallbackFileReplication(void *ptr, size_t size, size_t nmemb, void *data) { TRACE_FUNC_BEGIN; size_t realsize; INM_SAFE_ARITHMETIC(realsize = InmSafeInt<size_t>::Type(size) * nmemb, INMAGE_EX(size)(nmemb)) MemoryStruct *mem = (MemoryStruct *)data; size_t memorylen; INM_SAFE_ARITHMETIC(memorylen = InmSafeInt<size_t>::Type(mem->size) + realsize + 1, INMAGE_EX(mem->size)(realsize)) mem->memory = (char *)realloc(mem->memory, memorylen); if (mem->memory) { inm_memcpy_s(&(mem->memory[mem->size]), realsize + 1, ptr, realsize); mem->size += realsize; mem->memory[mem->size] = 0; } TRACE_FUNC_END; return realsize; } std::string GetImdsMetadata() { TRACE_FUNC_BEGIN; MemoryStruct chunk = {0}; CURL *curl = curl_easy_init(); try { chunk.size = 0; chunk.memory = NULL; if(CURLE_OK != curl_easy_setopt(curl, CURLOPT_URL, IMDS_URL)) { throw ERROR_EXCEPTION << FUNCTION_NAME << ": Failed to set curl options IMDS_URL.\n"; } if(CURLE_OK != curl_easy_setopt(curl, CURLOPT_NOPROXY, "*")) { throw ERROR_EXCEPTION << FUNCTION_NAME << ": Failed to set curl options CURLOPT_NOPROXY.\n"; } struct curl_slist * pheaders = curl_slist_append(NULL, IMDS_HEADERS); if (CURLE_OK != curl_easy_setopt(curl, CURLOPT_HTTPHEADER, pheaders)) { throw ERROR_EXCEPTION << FUNCTION_NAME << ": Failed to set curl options CURLOPT_HEADERDATA.\n"; } if(CURLE_OK != curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallbackFileReplication)) { throw ERROR_EXCEPTION << FUNCTION_NAME << ": Failed to set curl options CURLOPT_WRITEFUNCTION.\n"; } if(CURLE_OK != curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast<void *>( &chunk ))) { throw ERROR_EXCEPTION << FUNCTION_NAME << ": Failed to set curl options CURLOPT_WRITEDATA.\n"; } CURLcode curl_code = curl_easy_perform(curl); if (curl_code == CURLE_ABORTED_BY_CALLBACK) { throw ERROR_EXCEPTION << FUNCTION_NAME << ": Failed to perfvorm curl request, request aborted.\n"; } long response_code = 0L; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); if (response_code != HTTP_OK) { throw ERROR_EXCEPTION << FUNCTION_NAME << ": Failed to perform curl request, curl error " << curl_code << ": " << curl_easy_strerror(curl_code) << ", status code " << response_code << ((chunk.memory != NULL) ? (std::string(", error ") + chunk.memory) : "") << ".\n"; } } catch (std::exception& e) { TRACE_ERROR("%s: Failed with exception: %s.\n", FUNCTION_NAME, e.what()); } catch (...) { TRACE_ERROR("%s: Failed with exception.\n", FUNCTION_NAME); } std::string ret; if (chunk.memory != NULL) { ret = std::string(chunk.memory, chunk.size); free(chunk.memory); } curl_easy_cleanup(curl); TRACE_FUNC_END; return ret; } /* Method : HttpRestUtil::Get_X_MS_Version Description : Returns the Azure Rest API version Parameters : None */ std::string HttpRestUtil::Get_X_MS_Version() { return X_MS_Version; } /* Method : HttpRestUtil::Set_X_MS_Version Description : Set the Azure Rest API version Parameters : [in] x_ms_version : Azure Rest API version */ void HttpRestUtil::Set_X_MS_Version(const std::string& x_ms_version) { X_MS_Version = x_ms_version; } /* Method : HttpRestUtil::GetUrlComponent Description : Parses the url and returns the component as per the requested component index. Parameters : [in] ulr : valid url [in] compIndex : Any of HttpRestUtilUrl_Component_Index. Return : Returns the requested component from the give Url on success, othewise returns an empty string. */ std::string HttpRestUtil::GetUrlComponent(const std::string& url, int compIndex) { std::string req_comp; // URL Format => [scheme/protocol]://[authority/server-name]:port/[relative-path]?[query]#[fragment] // // Index Value => 1 2 3 4 5 6 // boost::regex url_ex("(http|https)://([^/ :]+):?([^/ ]*)(/?[^ #?]*)\\x3f?([^ #]*)#?([^ ]*)"); boost::cmatch urlComponents; if (compIndex >= HttpRestUtil::Url_Component_Index::Protocol && compIndex <= HttpRestUtil::Url_Component_Index::fragment && regex_match(url.c_str(), urlComponents, url_ex) && urlComponents.length() >= compIndex ) { req_comp = std::string(urlComponents[compIndex].first, urlComponents[compIndex].second); } return req_comp; } /* Method : HttpRestUtil::To_key_value_pair Description : Parses the raw format of the key-value pair string, seperated by a delimiter, and returns the key-value pair object. If key is empty or the delemeter is missing in raw string then it throws exception. Parameters : [in] raw_param : raw key value pair string. Ex: key=value, where '=' is delimiter [in] deli_key_value : delimiters string of the key-value pair. Return : key-value pair */ key_pair_t HttpRestUtil::To_key_value_pair( const std::string& raw_param, const std::string& deli_key_value ) { BOOST_ASSERT(!deli_key_value.empty()); size_t pos = raw_param.find_first_of(deli_key_value.c_str()); if (pos == std::string::npos) THROW_REST_EXCEPTION("could not find the delimiter"); if (pos == 0) THROW_REST_EXCEPTION("key should not be empty"); return std::make_pair( raw_param.substr(0, pos), raw_param.length() > (pos + 1) ? raw_param.substr(pos + 1) : "" ); } /* Method : HttpRestUtil::Get_X_MS_Range Description : Form the x-ms-range header value with a give start offset and length. Parameters : [in] start_offset [in] length Return : string value in the form of x-ms-range header value format. */ std::string HttpRestUtil::Get_X_MS_Range(offset_t start_offset, offset_t length) { std::stringstream x_ms_range; x_ms_range << "bytes=" << start_offset << "-" << (start_offset + length - 1); return x_ms_range.str(); } std::string HttpRestUtil::UrlEncode(const std::string& url) { std::string encodedStr; CURL *curl = curl_easy_init(); if (curl) { char *output = curl_easy_escape(curl, url.c_str(), url.length()); if (output) { encodedStr = output; curl_free(output); } curl_easy_cleanup(curl); } return encodedStr; } std::string HttpRestUtil::UrlDecode(const std::string& url) { std::string decodedStr; CURL *curl = curl_easy_init(); if (curl) { int outlength; char *output = curl_easy_unescape(curl, url.c_str(), url.length(), &outlength); if (output) { decodedStr = output; curl_free(output); } curl_easy_cleanup(curl); } return decodedStr; } // TODO-SanKumar-1612: Should we rather use stringbuf for better performance? bool HttpRestUtil::BuildJsonArray(std::string &result, const std::string &currElement, std::string::size_type maxSize, bool moreData) { bool isCurrElementIncluded = false; // We ignore this check for the first element in the array. // From the second element onwards, check if we have enough bytes to accomodate // prefix comma, the current element and the ending ] bracket. // So, in the overflow scenario, until the last appended element, we make sure // there's space for the ending ] bracket. Whenever the overflow happens, the // square bracket is filled, as a byte is guaranteed to be available (unless // the overflow occurs with the first element of the array). if (!result.empty() && (result.size() + currElement.size() + 2 > maxSize)) { moreData = false; } else { result += result.empty() ? '[' : ','; result += currElement; isCurrElementIncluded = true; } if (!moreData) { // End of array, either in genuine case or size overflow. result += ']'; } return isCurrElementIncluded; } void HttpRestUtil::ConstructBrokerProperties(std::string& properties, const std::map<std::string, std::string> propmap) { using namespace ServiceBusConstants; properties += OPEN_BRACES; properties += WHITE_SPACE; std::map<std::string, std::string>::const_iterator propIter = propmap.begin(); while (propIter != propmap.end()) { properties += DOUBLE_QUOTES; properties += propIter->first; properties += DOUBLE_QUOTES; properties += KEY_VALUE_SEPERATOR; properties += DOUBLE_QUOTES; properties += propIter->second; properties += DOUBLE_QUOTES; propIter++; if (propIter != propmap.end()) properties += PROP_SEPERATOR; } properties += CLOSE_BRACES; return; } /* Method : Uri::Uri Description : Uri constructor. The uri string passed to the constructor should not be empty. The Uri will be parsed and then fills the data members as per parsed data. Parameters : strUri: uri string Return Code : None */ Uri::Uri(const std::string& strUri) { BOOST_ASSERT(!strUri.empty()); FillUriDataMembers(strUri); FillQueryParams(); } /* Method : Uri::Uri Description : Uri copy constructor Parameters : Return Code : */ Uri::Uri(const Uri & rhs) { m_resource_uri = rhs.m_resource_uri; m_query_uri = rhs.m_query_uri; m_query_parameters.assign( rhs.m_query_parameters.begin(), rhs.m_query_parameters.end() ); } /* Method : Description : overloaded operators for Uri Parameters : Return Code : */ bool Uri::operator == (const Uri rhs) const { return boost::iequals(m_query_uri, rhs.m_query_uri) && boost::iequals(m_resource_uri, rhs.m_resource_uri); } Uri& Uri::operator = (const Uri& rhs) { if (this != &rhs) { m_resource_uri = rhs.m_resource_uri; m_query_uri = rhs.m_query_uri; m_query_parameters.assign(rhs.m_query_parameters.begin(), rhs.m_query_parameters.end()); } return *this; } /* Method : Uri::FillUriDataMembers Description : Splits the uri string into resource uri & query string. Parameters : [in] strUti: string uri Return Code : None */ void Uri::FillUriDataMembers(const std::string& strUri) { BOOST_ASSERT(!strUri.empty()); size_t pos = strUri.find_first_of(URI_DELIMITER::QUERY); if (std::string::npos == pos) //No query parameters in uri { m_resource_uri = strUri; } else if ((pos + 1) == strUri.length()) //the delemeter is the last character in uri { m_resource_uri = strUri.substr(0, strUri.length() - 1); } else { m_resource_uri = strUri.substr(0, pos); m_query_uri = strUri.substr(pos + 1); } } /* Method : Uri::FillQueryParams Description : Splits the query string into uri query parameters and then key-value pairs Parameters : None Return Code : None */ void Uri::FillQueryParams() { if (!m_query_uri.empty()) { std::vector< std::string > raw_params; boost::split(raw_params, m_query_uri, boost::is_any_of(URI_DELIMITER::QUERY_PARAM_SEP)); for (size_t i = 0; i < raw_params.size(); i++) { try { m_query_parameters.push_back(HttpRestUtil::To_key_value_pair(raw_params[i])); } catch (...) { TRACE_ERROR("Exception in query-param string parsing for: %s\n", raw_params[i].c_str()); } } } } /* Method : Uri::GetQueryString Description : Constructs the uri query string with query available query key-value pairs Parameters : None Return : query string */ std::string Uri::GetQueryString() const { std::stringstream ssQuery; if (!m_query_parameters.empty()) { ssQuery << URI_DELIMITER::QUERY; for (size_t i = 0; i < m_query_parameters.size();) { ssQuery << m_query_parameters[i].first; ssQuery << URI_DELIMITER::QUERY_PARAM_VAL; ssQuery << m_query_parameters[i].second; if (++i < m_query_parameters.size()) ssQuery << URI_DELIMITER::QUERY_PARAM_SEP; } } return ssQuery.str(); } /* Method : Uri::GetResourceUri, Uri::GetQueryParameters Description : Get methods for resource uri & query parameters data-members Parameters : None Return : query string */ std::string Uri::GetResourceUri() const { return m_resource_uri; } const parameters_t& Uri::GetQueryParameters() const { return m_query_parameters; } /* Method : Uri::ToString Description : Constructs the complete string uri. Parameters : None Return : complete uri in string representation. */ std::string Uri::ToString() const { return m_resource_uri + GetQueryString(); } /* Method : Uri::AddQueryParam, Uri::AddQueryParamAtFront Description : Add methods for adding new query parameters to the uri Parameters : Return Code : None */ void Uri::AddQueryParam(const std::string& key, const std::string& value) { BOOST_ASSERT(!key.empty()); m_query_parameters.push_back(std::make_pair(key, value)); } void Uri::AddQueryParamAtFront(const std::string& key, const std::string& value) { BOOST_ASSERT(!key.empty()); m_query_parameters.insert(m_query_parameters.begin(), std::make_pair(key, value)); } } // ~AzureStorageRest namespace