diff --git a/src/ebusd/scan.cpp b/src/ebusd/scan.cpp index 2c42450f..9e337fe0 100644 --- a/src/ebusd/scan.cpp +++ b/src/ebusd/scan.cpp @@ -70,8 +70,9 @@ result_t ScanHelper::collectConfigFiles(const string& relPath, const string& pre + "t=" + extension.substr(1) + query; string names; bool repeat = false; - if (!m_configHttpClient->get(uri, "", &names, &repeat)) { - if (!names.empty()) { + bool json = true; + if (!m_configHttpClient->get(uri, "", &names, &repeat, nullptr, &json)) { + if (!names.empty() || json) { logError(lf_main, "HTTP failure%s: %s", repeat ? ", repeating" : "", names.c_str()); names = ""; } diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index 65639514..93fca1dd 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -431,8 +431,8 @@ void HttpClient::disconnect() { } } -bool HttpClient::get(const string& uri, const string& body, string* response, bool* repeatable, time_t* time) { - return request("GET", uri, body, response, repeatable, time); +bool HttpClient::get(const string& uri, const string& body, string* response, bool* repeatable, time_t* time, bool* jsonString) { + return request("GET", uri, body, response, repeatable, time, jsonString); } bool HttpClient::post(const string& uri, const string& body, string* response, bool* repeatable) { @@ -447,7 +447,7 @@ const int indexToMonth[] = { }; bool HttpClient::request(const string& method, const string& uri, const string& body, string* response, -bool* repeatable, time_t* time) { +bool* repeatable, time_t* time, bool* jsonString) { if (!ensureConnected()) { *response = "not connected"; if (repeatable) { @@ -559,9 +559,49 @@ bool* repeatable, time_t* time) { *response = "invalid content length "; return false; } + bool isJson = headers.find("\r\nContent-Type: application/json") != string::npos; + if (pos == string::npos) { + disconnect(); + return true; + } pos = readUntil("", length, response); disconnect(); - return pos == length; + if (pos != length) { + return false; + } + if (jsonString && isJson && *jsonString && length >= 2 && response->at(0) == '"') { + // check for inline conversion of JSON to string expecting a single string to de-escape + pos = length; + while (pos > 1 && (response->at(pos-1) == '\r' || response->at(pos-1) == '\n')) { + pos--; + } + if (pos > 2 && response->at(pos-1) == '"') { + response->erase(pos-1); + response->erase(0, 1); + size_t from = 0; + while ((pos = response->find_first_of("\\", from)) != string::npos) { + response->erase(pos, 1); // pos is now pointing at the char behind the backslash + switch (response->at(pos)) { + case 'r': + response->erase(pos, 1); // removed + from = pos; + continue; + case 'n': + (*response)[pos] = '\n'; // replaced + from = pos+1; + break; + default: + from = pos+1; + break; // kept + } + } + isJson = false; + } + } + if (jsonString) { + *jsonString = isJson; + } + return true; } size_t HttpClient::readUntil(const string& delim, size_t length, string* result) { diff --git a/src/lib/utils/httpclient.h b/src/lib/utils/httpclient.h index 7bd3cf1f..28a4afb0 100755 --- a/src/lib/utils/httpclient.h +++ b/src/lib/utils/httpclient.h @@ -191,10 +191,13 @@ class HttpClient { * @param repeatable optional pointer to a bool in which to store whether the request should be repeated later on * (e.g. due to temporary connectivity issues). * @param time optional pointer to a @a time_t value for storing the modification time of the file, or nullptr. + * @param jsonString optional pointer to a bool value. When returning, it is set to whether the retrieved + * content-type indicates JSON. When true upon entry, content is JSON, and response is a single JSON string, it + * will be de-escaped to a pure string and the value set to false. * @return true on success, false on error. */ bool get(const string& uri, const string& body, string* response, bool* repeatable = nullptr, - time_t* time = nullptr); + time_t* time = nullptr, bool* jsonString = nullptr); /** * Execute a POST request. @@ -216,10 +219,13 @@ class HttpClient { * @param repeatable optional pointer to a bool in which to store whether the request should be repeated later on * (e.g. due to temporary connectivity issues). * @param time optional pointer to a @a time_t value for storing the modification time of the file, or nullptr. + * @param jsonString optional pointer to a bool value. When returning, it is set to whether the retrieved + * content-type indicates JSON. When true upon entry, content is JSON, and response is a single JSON string, it + * will be de-escaped to a pure string and the value set to false. * @return true on success, false on error. */ bool request(const string& method, const string& uri, const string& body, string* response, - bool* repeatable = nullptr, time_t* time = nullptr); + bool* repeatable = nullptr, time_t* time = nullptr, bool* jsonString = nullptr); private: /**