#define AL_LOG_SECTION "curl_http" #include #include "http.h" static void check_status_codes(struct nn_curl *curl) { struct nn_http *http = (struct nn_http *)curl; if (http->status_code <= 0) { curl_easy_getinfo(curl->handle, CURLINFO_RESPONSE_CODE, &http->status_code); if (http->status_code > 0) { http->callback(http->userdata, NNWT_HTTP_RESPONSE_CODE, NULL, &http->status_code); } } if (http->content_length < 0 && http->status_code / 100 == 2) { curl_easy_getinfo(curl->handle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &http->content_length); if (http->content_length >= 0) { http->callback(http->userdata, NNWT_HTTP_CONTENT_LENGTH, NULL, &http->content_length); } } } static void http_handle_events(void *userdata, struct nn_curl *curl) { (void)userdata; s32 pending; CURLMsg *msg; struct nn_http *http = (struct nn_http *)curl; while ((msg = curl_multi_info_read(curl->multi_handle, &pending))) { switch (msg->msg) { case CURLMSG_DONE: { al_assert(msg->easy_handle == curl->handle); CURLcode code = msg->data.result; if (code != CURLE_OK) { log_error("Curl error: %s.", curl_easy_strerror(code)); http->status_code = -1; http->callback(http->userdata, NNWT_HTTP_ERROR, NULL, &http->status_code); return; } if (!nn_curl_remove_handle(curl)) return; check_status_codes(curl); if (http->status_code == 200) { http->callback(http->userdata, NNWT_HTTP_FINISHED, NULL, &http->status_code); } else if (http->status_code / 100 == 3) { http->callback(http->userdata, NNWT_HTTP_REDIRECT, NULL, &http->status_code); char *redirect_url = NULL; curl_easy_getinfo(curl->handle, CURLINFO_REDIRECT_URL, &redirect_url); if (!redirect_url) { http->callback(http->userdata, NNWT_HTTP_ERROR, NULL, &http->status_code); return; } nn_curl_set_url(curl, &al_str_cr(redirect_url)); http->status_code = -1; http->content_length = -1; if (!nn_curl_add_handle(curl)) return; } else { http->callback(http->userdata, NNWT_HTTP_ERROR, NULL, &http->status_code); } return; } case CURLMSG_LAST: log_debug("Unhandled CURLMSG_LAST."); break; case CURLMSG_NONE: log_debug("Unhandled CURLMSG_NONE."); break; } } } bool nn_http_init(struct nn_http *http) { http->status_code = -1; http->content_length = -1; http->headers = NULL; return nn_curl_init(&http->curl); } void nn_http_set_url(struct nn_http *http, str *url) { nn_curl_set_url(&http->curl, url); } void nn_http_set_user_agent(struct nn_http *http, str *user_agent) { char *c_str = al_str_to_c_str(user_agent); curl_easy_setopt(http->curl.handle, CURLOPT_USERAGENT, c_str); al_free(c_str); } void nn_http_add_header(struct nn_http *http, str *header) { char *c_str = al_str_to_c_str(header); http->headers = curl_slist_append(http->headers, c_str); al_free(c_str); } void nn_http_set_range(struct nn_http *http, size_t start, size_t end) { char range[44]; if (start == 0u) al_snprintf(range, sizeof(range), "-%zu", end); else if (end == 0u) al_snprintf(range, sizeof(range), "%zu-", start); else al_snprintf(range, sizeof(range), "%zu-%zu", start, end); curl_easy_setopt(http->curl.handle, CURLOPT_RANGE, range); } void nn_http_close(struct nn_http *http) { nn_curl_close(&http->curl); } static size_t stream_write_callback(char *buffer, size_t size, size_t nmemb, void *userdata) { struct nn_http *http = (struct nn_http *)userdata; check_status_codes(&http->curl); size_t n = size * nmemb; http->callback(http->userdata, NNWT_HTTP_WRITE, (u8 *)buffer, &n); return n; } static size_t stream_read_callback(char *buffer, size_t size, size_t nmemb, void *userdata) { struct nn_http *http = (struct nn_http *)userdata; size_t n = size * nmemb; http->callback(http->userdata, NNWT_HTTP_READ, (u8 *)buffer, &n); return n; } static void request_callback(void *userdata, u8 op, u8 *buf, void *opaque) { struct nn_http_request *request = (struct nn_http_request *)userdata; switch (op) { case NNWT_HTTP_READ: { size_t n = *(size_t *)opaque; size_t size = request->payload.size; if (request->pointer + n > size) { n = size - request->pointer; } nn_buffer_read(&request->payload, buf, request->pointer, n); request->pointer += n; *(size_t *)opaque = n; break; } case NNWT_HTTP_WRITE: { size_t n = *(size_t *)opaque; nn_buffer_append(&request->response, buf, n); break; } case NNWT_HTTP_RESPONSE_CODE: break; case NNWT_HTTP_CONTENT_LENGTH: { curl_off_t length = *(curl_off_t *)opaque; nn_buffer_ensure_space(&request->response, length); break; } case NNWT_HTTP_REDIRECT: break; case NNWT_HTTP_FINISHED: request->callback(request->userdata, request, true); break; case NNWT_HTTP_ERROR: request->callback(request->userdata, request, false); break; } } static bool init_request_internal(struct nn_http *http, u8 method) { struct nn_curl *curl = &http->curl; switch (method) { case NNWT_HTTP_HEAD: curl_easy_setopt(curl->handle, CURLOPT_NOBODY, 1L); break; case NNWT_HTTP_GET: curl_easy_setopt(curl->handle, CURLOPT_HTTPGET, 1L); break; case NNWT_HTTP_POST: curl_easy_setopt(curl->handle, CURLOPT_POST, 1L); break; } curl_easy_setopt(curl->handle, CURLOPT_WRITEFUNCTION, stream_write_callback); curl_easy_setopt(curl->handle, CURLOPT_WRITEDATA, http); curl_easy_setopt(curl->handle, CURLOPT_READFUNCTION, stream_read_callback); curl_easy_setopt(curl->handle, CURLOPT_READDATA, http); if (http->headers) { curl_easy_setopt(curl->handle, CURLOPT_HTTPHEADER, http->headers); } curl->handle_events = http_handle_events; return nn_curl_add_handle(curl); } bool nn_http_request_stream(struct nn_http *http, u8 method, struct nn_event_loop *loop, void (*callback)(void *, u8, u8 *, void *), void *userdata) { struct nn_curl *curl = &http->curl; al_assert(curl->loop == NULL); al_assert(method != NNWT_HTTP_POST); // Not implemented. curl->loop = loop; http->callback = callback; http->userdata = userdata; return init_request_internal(http, method); } bool nn_http_request_init(struct nn_http_request *request) { request->pointer = 0; nn_buffer_init(&request->payload); nn_buffer_init(&request->response); return nn_http_init(&request->http); } bool nn_http_request(struct nn_http_request *request, u8 method, struct nn_event_loop *loop, void (*callback)(void *, struct nn_http_request *, bool), void *userdata) { struct nn_curl *curl = &request->http.curl; al_assert(curl->loop == NULL); curl->loop = loop; request->http.callback = request_callback; request->http.userdata = request; long payload = (long)request->payload.size; if (payload > 0) { curl_easy_setopt(curl->handle, CURLOPT_POSTFIELDSIZE, payload); } request->callback = callback; request->userdata = userdata; return init_request_internal(&request->http, method); } void nn_http_request_close(struct nn_http_request *request) { nn_buffer_free(&request->payload); nn_buffer_free(&request->response); nn_http_close(&request->http); }