#include "restclient.h" #include #include static size_t arrayWriteCallback(char* contents, size_t size, size_t nmemb, void* userdata) { if (userdata) { QByteArray* arr = (QByteArray*) userdata; arr->append(contents, size * nmemb); return size * nmemb; } return 0; } static void dump(const char* text, FILE* stream, unsigned char* ptr, size_t size) { const unsigned int width = 0x40; fprintf(stream, "%s, %10.10lu bytes (0x%8.8lx)\n", text, (unsigned long) size, (unsigned long) size); for (size_t i = 0; i < size; i += width) { fprintf(stream, "%4.4lx: ", (unsigned long) i); for (size_t c = 0; (c < width) && (i + c < size); c++) { /* check for 0D0A; if found, skip past and start a new line of output */ fprintf(stream, "%c", (ptr[i + c] >= 0x20) && (ptr[i + c] < 0x80) ? ptr[i + c] : '.'); } fputc('\n', stream); /* newline */ } fflush(stream); } static int my_trace(CURL* handle, curl_infotype type, char* data, size_t size, void* userp) { const char* text; (void) handle; /* prevent compiler warning */ (void) userp; switch (type) { case CURLINFO_TEXT: fprintf(stderr, "== Info: %s", data); /* FALLTHROUGH */ default: /* in case a new one is introduced to shock us */ return 0; case CURLINFO_HEADER_OUT: text = "=> Send header"; break; case CURLINFO_DATA_OUT: text = "=> Send data"; break; case CURLINFO_HEADER_IN: text = "<= Recv header"; break; case CURLINFO_DATA_IN: text = "<= Recv data"; break; case CURLINFO_SSL_DATA_OUT: case CURLINFO_SSL_DATA_IN: return 0; } dump(text, stderr, (unsigned char*) data, size); return 0; } RestClient::RestClient(const QString& base) : baseurl(base) { curl = curl_easy_init(); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, arrayWriteCallback); } RestClient::~RestClient() { curl_easy_cleanup(curl); curl = nullptr; } RestClient::WebException::WebException(CURLcode c) : curlresponse(c) { } QByteArray RestClient::requestRaw(const QString& url, Method m, const QString& data) { if (!curl) return {}; QString requrl = baseurl + url; const QByteArray reqArr = requrl.toLocal8Bit(); curl_easy_setopt(curl, CURLOPT_URL, reqArr.data()); const QByteArray basicArr = basicauth.toLocal8Bit(); curl_easy_setopt(curl, CURLOPT_USERPWD, basicArr.data()); curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); if (disableCertCheck) { curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); } // restore default HTTP Options curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL); // curl header struct curl_slist* headerlist = NULL; headerlist = curl_slist_append(headerlist, "Accept: application/json"); const QByteArray dataArr = data.toLocal8Bit(); switch (m) { default: case Method::GET: curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); break; case Method::POST: curl_easy_setopt(curl, CURLOPT_POST, 1L); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, dataArr.data()); headerlist = curl_slist_append(headerlist, "Content-Type: application/json"); break; case Method::PUT: case Method::PATCH: case Method::DELETE: curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, toString(m)); // to use the POSTFIELDS (do not use CURLOPT_PUT, it does not support POSTFIELDS) curl_easy_setopt(curl, CURLOPT_POSTFIELDS, dataArr.data()); headerlist = curl_slist_append(headerlist, "Content-Type: application/json"); break; } curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist); QByteArray ba; //buffer // set callback data curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ba); CURLcode res = curl_easy_perform(curl); if (headerlist) { curl_slist_free_all(headerlist); } // Check for errors if (res != CURLE_OK) { if (res == CURLE_HTTP_RETURNED_ERROR) { long responsecode = -1; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responsecode); qWarning() << "API request failed: " << baseurl << " " << url << " -> " << responsecode; } else { qWarning() << "API request failed: " << baseurl << " " << url << " " << curl_easy_strerror(res); if(res == CURLE_COULDNT_CONNECT) { throw WebException(res); } } return {}; } return ba; } QJsonDocument RestClient::request(const QString& url, Method m, const QString& data) { QByteArray arr = requestRaw(url, m, data); if (arr.isEmpty()) return {}; QJsonParseError err; QJsonDocument parsed = QJsonDocument::fromJson(arr, &err); if (parsed.isNull() || err.error != QJsonParseError::NoError) { qCritical() << "API Json parse error " << err.errorString() << " offset: " << err.offset; return {}; } return parsed; } void RestClient::enableDebugging(bool enabled) { // enable debugging if(enabled) { curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, my_trace); curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); } else { curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, NULL); curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L); } } QString RestClient::escape(const QString& in) const { char* e = curl_easy_escape(curl, in.toLocal8Bit().data(), in.length()); QString esc(e); curl_free(e); return esc; } const char* toString(RestClient::Method m) { static const char* MethodNames[] = {"GET", "POST", "PUT", "PATCH", "DELETE"}; return MethodNames[(int) m]; }