/* * Licensed under BSD 3-Clause License */ #include #include #include #include #include #include /* For use with libcurl */ size_t mstdnt_curl_write_callback(char* ptr, size_t _size, size_t nmemb, void* _content) { size_t size = nmemb * _size; /* Mostly pointless, but portable */ struct mstdnt_fetch_data* res = _content; /* Cast */ char* data; if ((data = mstdnt_realloc(res->response, res->size + size + 1)) == NULL) { perror("realloc"); return 0; } res->response = data; memcpy(&(res->response[res->size]), ptr, size); res->size += size; res->response[res->size] = 0; return size; } #define TOKEN_STR_SIZE 512 int mstdnt_fetch_curl_async(mastodont_t* mstdnt, CURL* curl, struct mstdnt_args* m_args, mstdnt_request_cb_t cb_request, void* cb_args, int (*json_cb)(cJSON*, void*, mstdnt_request_cb_data*), void* json_args, char* _url, CURLoption request_t, char* request_t_custom) { #define is_custom request_t_custom && request_t == CURLOPT_CUSTOMREQUEST struct mstdnt_fetch_data* results = NULL; CURLMcode res = 0; char token[TOKEN_STR_SIZE] = { 0 }; struct curl_slist* list = NULL; // Setup URL char url[MSTDNT_URLSIZE] = { 0 }; strncpy(url, m_args->url, MSTDNT_URLSIZE-1); strncat(url, _url, MSTDNT_URLSIZE-1); // Setup token if (m_args->token) { snprintf(token, TOKEN_STR_SIZE, "Authorization: Bearer %s", m_args->token); list = curl_slist_append(list, token); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); } // Setup data to pass into results results = calloc(1, sizeof(struct mstdnt_fetch_data)); if (!results) { perror("calloc"); return -1; } results->callback = cb_request; results->callback_args = cb_args; results->json_cb = json_cb; results->json_args = json_args; // Set options curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, mstdnt_curl_write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, results); /* Copy into private pointer value * A little stupid, but we can let CURL hold our values for us. * Curl won't let us get the WRITEDATA opt pointer back sadly, so this has to be done */ curl_easy_setopt(curl, CURLOPT_PRIVATE, results); /* Should we verify the peer's SSL cert? */ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, !MSTDNT_T_FLAG_ISSET(m_args, MSTDNT_FLAG_SSL_UNVERIFIED)); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, !MSTDNT_T_FLAG_ISSET(m_args, MSTDNT_FLAG_SSL_UNVERIFIED)); // PUT, POST, GET, Custom // Mimes are expected to be set beforehand manually if (is_custom) curl_easy_setopt(curl, request_t, request_t_custom); else if (request_t != CURLOPT_MIMEPOST) curl_easy_setopt(curl, request_t, 1); // Add curl handle to multi, then run res = curl_multi_add_handle(mstdnt->curl, curl); if (res != CURLM_OK) { printf("error %s\n", curl_multi_strerror(res)); return -1; } /* int running; */ /* res = curl_multi_perform(mstdnt->curl, &running); */ /* if (res != CURLM_OK) */ /* { */ /* printf("error %s\n", curl_multi_strerror(res)); */ /* return -1; */ /* } */ return 0; } int mstdnt_await(mastodont_t* mstdnt, enum mstdnt_fetch_await opt, struct mstdnt_fd extra_fds[], size_t nfds) { CURLMsg* msg; int msgs_left = 1; int res; struct curl_waitfd* fds = NULL; // Any other fds can go here if (extra_fds) { fds = calloc(nfds, sizeof(struct curl_waitfd)); for (int i = 0; i < nfds; ++i) { fds[i].fd = extra_fds[i].fd; fds[i].events = extra_fds[i].events; fds[i].revents = extra_fds[i].revents; } } int numfds; int running = 1; // Data used with response, must keep it with request struct mstdnt_fetch_data* data; // Data that the user will work with mstdnt_request_cb_data* results = calloc(1, sizeof(mstdnt_request_cb_data)); // Check if our socket is done do { // TODO error check curl_multi_perform(mstdnt->curl, &running); res = curl_multi_poll(mstdnt->curl, fds, nfds, 1000, &numfds); while ((msg = curl_multi_info_read(mstdnt->curl, &msgs_left)) != NULL) { if (msg->msg == CURLMSG_DONE) { // Get easy info curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &data); // Setup results->fetch_data = data; // So we can clean it up results->storage.needs_cleanup = 0; // Get json if (_mstdnt_json_init(&(results->root), data, &(results->storage))) { res = 1; goto cleanup_res; } // Pass data to json callback, so it can store it's data if (data->json_cb) res = data->json_cb(results->storage.root, data->json_args, results); // Call the actual callback res = data->callback(results, data->callback_args); cleanup_res: /* The response of the callback is important! * If the user returns the below response, then the request * must be cleaned up manually by them */ if (res != MSTDNT_REQUEST_DATA_NOCLEANUP) { // Will cleanup fetch too mstdnt_request_cb_cleanup(results); } // We can clean the handle up though curl_multi_remove_handle(mstdnt->curl, msg->easy_handle); curl_easy_cleanup(msg->easy_handle); } } if (res) break; } while (/* opt == MSTDNT_AWAIT_ALL && msgs_left */ running); // Put revents back for callee if (extra_fds) for (int i = 0; i < nfds; ++i) extra_fds[i].revents = fds[i].revents; free(fds); return res; } void mstdnt_storage_cleanup(struct mstdnt_storage* storage); void mstdnt_fetch_data_cleanup(struct mstdnt_fetch_data* res) { mstdnt_free(res->response); // Free ourself mstdnt_free(res); } void mstdnt_request_cb_cleanup(mstdnt_request_cb_data* data) { mstdnt_storage_cleanup(&(data->storage)); data->data_free_cb(data->data); // Cleanup mstdnt_fetch_data_cleanup(data->fetch_data); // Free ourself mstdnt_free(data); }