diff --git a/NEWS.md b/NEWS.md index c8076de..56b83a0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -46,6 +46,9 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Changed +- URL encode location when requesting Geocoding API + [#55](https://github.com/orontee/taranis/issues/55) + - Fix possible crash on missing attributes in OpenWeather API payload [#52](https://github.com/orontee/taranis/issues/52) diff --git a/src/http.cc b/src/http.cc index 34cea30..0c12a04 100644 --- a/src/http.cc +++ b/src/http.cc @@ -5,17 +5,32 @@ #include "errors.h" -Json::Value taranis::HttpClient::get(const std::string &url) { +namespace taranis { + +std::string +HttpClient::encode_query_parameter(const std::string ¶meter) const { + std::shared_ptr escaped{ + curl_easy_escape(nullptr, parameter.c_str(), parameter.size()), + &curl_free}; + if (escaped == nullptr) { + return ""; + } + + std::string value{escaped.get()}; + return value; +} + +Json::Value HttpClient::get(const std::string &url) { BOOST_LOG_TRIVIAL(debug) << "Sending GET request " << url; this->ensure_network(); - auto curl = this->preprare_curl(); - curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str()); + this->response_data.clear(); + this->response_data.reserve(10 * CURLOPT_BUFFERSIZE); - std::string response_data; - response_data.reserve(10 * CURLOPT_BUFFERSIZE); - curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response_data); + auto curl = this->prepare_curl(); + curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &this->response_data); const CURLcode code = curl_easy_perform(curl.get()); if (code != CURLE_OK) { @@ -31,13 +46,14 @@ Json::Value taranis::HttpClient::get(const std::string &url) { throw HttpError{response_code}; } - BOOST_LOG_TRIVIAL(debug) << "Received " << response_data.size() << " bytes"; + BOOST_LOG_TRIVIAL(debug) << "Received " << this->response_data.size() + << " bytes"; Json::Value root; Json::CharReaderBuilder reader; reader["collectComments"] = false; std::string json_errors; - std::stringstream input_stream{response_data}; + std::stringstream input_stream{this->response_data}; try { if (not Json::parseFromStream(reader, input_stream, &root, &json_errors)) { BOOST_LOG_TRIVIAL(error) @@ -49,7 +65,7 @@ Json::Value taranis::HttpClient::get(const std::string &url) { return root; } -std::unique_ptr taranis::HttpClient::preprare_curl() { +std::unique_ptr HttpClient::prepare_curl() { std::unique_ptr curl{curl_easy_init(), &curl_easy_cleanup}; @@ -60,6 +76,7 @@ std::unique_ptr taranis::HttpClient::preprare_curl() { curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl.get(), CURLOPT_BUFFERSIZE, 102400L); curl_easy_setopt(curl.get(), CURLOPT_NOPROGRESS, 1L); curl_easy_setopt(curl.get(), CURLOPT_USERAGENT, "taranis/0.0.1"); curl_easy_setopt(curl.get(), CURLOPT_MAXREDIRS, 50L); @@ -72,7 +89,7 @@ std::unique_ptr taranis::HttpClient::preprare_curl() { return curl; } -void taranis::HttpClient::ensure_network() { +void HttpClient::ensure_network() const { iv_netinfo *netinfo = NetInfo(); if (netinfo == nullptr or not netinfo->connected) { BOOST_LOG_TRIVIAL(debug) << "Will try to establish connection"; @@ -97,10 +114,11 @@ void taranis::HttpClient::ensure_network() { } } -size_t taranis::HttpClient::write_callback(void *contents, size_t size, - size_t nmemb, void *userp) { +size_t HttpClient::write_callback(void *contents, size_t size, size_t nmemb, + void *userp) { BOOST_LOG_TRIVIAL(debug) << "Writing " << size * nmemb << " bytes to buffer"; static_cast(userp)->append(static_cast(contents), size * nmemb); return size * nmemb; } +} // namespace taranis diff --git a/src/http.h b/src/http.h index 9947d44..b8ed6c0 100644 --- a/src/http.h +++ b/src/http.h @@ -12,12 +12,16 @@ namespace taranis { class HttpClient { public: + std::string encode_query_parameter(const std::string ¶meter) const; + Json::Value get(const std::string &url); private: - std::unique_ptr preprare_curl(); + std::string response_data; + + std::unique_ptr prepare_curl(); - void ensure_network(); + void ensure_network() const; static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp); diff --git a/src/service.cc b/src/service.cc index 05eae5e..caa5cf0 100644 --- a/src/service.cc +++ b/src/service.cc @@ -80,6 +80,7 @@ Service::identify_lonlat(const std::string &town, const std::string &country) { } if (not this->lonlat) { this->request_lonlat(); + BOOST_LOG_TRIVIAL(debug) << "Longitude and latitude taken from cache"; } return this->lonlat.value(); } @@ -89,11 +90,7 @@ void Service::request_lonlat() { std::stringstream url; url << openweather::url << openweather::geo_path << "?" - << "q=" << this->town; - if (not this->country.empty()) { - url << "," << this->country; - } - url << "&" + << "q=" << this->encode_location() << "&" << "appid=" << this->api_key; auto returned_value = this->send_get_request(url.str()); @@ -132,6 +129,16 @@ Json::Value Service::request_onecall_api(const std::string &town, return returned_value; } +std::string Service::encode_location() const { + std::string location = this->client.encode_query_parameter(this->town); + if (not this->country.empty()) { + location += "," + this->client.encode_query_parameter(this->country); + } + // ⚠️ this is not the usual way to encode query parameters, in + // particular the comma must not be encoded... + return location; +} + Json::Value Service::send_get_request(const std::string &url) { try { return this->client.get(url); diff --git a/src/service.h b/src/service.h index 2c260bd..34a26c5 100644 --- a/src/service.h +++ b/src/service.h @@ -68,6 +68,8 @@ class Service { const std::string &language, const std::string &units); + std::string encode_location() const; + Json::Value send_get_request(const std::string &url); static Condition extract_condition(const Json::Value &value);