/** * A HTTP request is finished: invoke its callback and free it. */ static void http_request_done(struct http_request *request, CURLcode result, long status) { /* invoke the handler method */ if (result == CURLE_WRITE_ERROR && /* handle the postponed error that was caught in http_request_writefunction() */ request->body->len > MAX_RESPONSE_BODY) { GError *error = g_error_new_literal(curl_quark(), 0, "response body is too large"); request->handler->error(error, request->handler_ctx); } else if (result != CURLE_OK) { GError *error = g_error_new(curl_quark(), result, "curl failed: %s", request->error); request->handler->error(error, request->handler_ctx); } else if (status < 200 || status >= 300) { GError *error = g_error_new(curl_quark(), 0, "got HTTP status %ld", status); request->handler->error(error, request->handler_ctx); } else request->handler->response(request->body->len, request->body->str, request->handler_ctx); /* remove it from the list and free resources */ http_client.requests = g_slist_remove(http_client.requests, request); http_request_free(request); }
static struct input_stream * input_curl_open(const char *url, GError **error_r) { struct input_curl *c; bool ret; if (strncmp(url, "http://", 7) != 0) return NULL; c = g_new0(struct input_curl, 1); input_stream_init(&c->base, &input_plugin_curl, url); c->url = g_strdup(url); c->buffers = g_queue_new(); c->multi = curl_multi_init(); if (c->multi == NULL) { g_set_error(error_r, curl_quark(), 0, "curl_multi_init() failed"); input_curl_free(c); return NULL; } icy_clear(&c->icy_metadata); c->tag = NULL; ret = input_curl_easy_init(c, error_r); if (!ret) { input_curl_free(c); return NULL; } ret = input_curl_send_request(c, error_r); if (!ret) { input_curl_free(c); return NULL; } ret = input_curl_multi_info_read(c, error_r); if (!ret) { input_curl_free(c); return NULL; } return &c->base; }
static bool input_curl_send_request(struct input_curl *c, GError **error_r) { CURLMcode mcode; int running_handles; do { mcode = curl_multi_perform(c->multi, &running_handles); } while (mcode == CURLM_CALL_MULTI_PERFORM); if (mcode != CURLM_OK) { g_set_error(error_r, curl_quark(), mcode, "curl_multi_perform() failed: %s", curl_multi_strerror(mcode)); return false; } return true; }
static int input_curl_buffer(struct input_stream *is, GError **error_r) { struct input_curl *c = (struct input_curl *)is; if (curl_total_buffer_size(c) >= CURL_MAX_BUFFERED) return 0; CURLMcode mcode; int running_handles; bool ret; c->buffered = false; if (!is->ready && !c->eof) /* not ready yet means the caller is waiting in a busy loop; relax that by calling select() on the socket */ if (input_curl_select(c, error_r) < 0) return -1; do { mcode = curl_multi_perform(c->multi, &running_handles); } while (mcode == CURLM_CALL_MULTI_PERFORM && g_queue_is_empty(c->buffers)); if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { g_set_error(error_r, curl_quark(), mcode, "curl_multi_perform() failed: %s", curl_multi_strerror(mcode)); c->eof = true; is->ready = true; return -1; } ret = input_curl_multi_info_read(c, error_r); if (!ret) return -1; return c->buffered; }
/** * Give control to CURL. */ static bool http_multi_perform(void) { CURLMcode mcode; int running_handles; do { mcode = curl_multi_perform(http_client.multi, &running_handles); } while (mcode == CURLM_CALL_MULTI_PERFORM); if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { GError *error = g_error_new(curl_quark(), mcode, "curl_multi_perform() failed: %s", curl_multi_strerror(mcode)); http_client_abort_all_requests(error); return false; } return true; }
static bool input_curl_multi_info_read(struct input_curl *c, GError **error_r) { CURLMsg *msg; int msgs_in_queue; while ((msg = curl_multi_info_read(c->multi, &msgs_in_queue)) != NULL) { if (msg->msg == CURLMSG_DONE) { c->eof = true; c->base.ready = true; if (msg->data.result != CURLE_OK) { g_set_error(error_r, curl_quark(), msg->data.result, "curl failed: %s", c->error); return false; } } } return true; }
static bool input_curl_easy_init(struct input_curl *c, GError **error_r) { CURLcode code; CURLMcode mcode; c->eof = false; c->easy = curl_easy_init(); if (c->easy == NULL) { g_set_error(error_r, curl_quark(), 0, "curl_easy_init() failed"); return false; } mcode = curl_multi_add_handle(c->multi, c->easy); if (mcode != CURLM_OK) { g_set_error(error_r, curl_quark(), mcode, "curl_multi_add_handle() failed: %s", curl_multi_strerror(mcode)); return false; } curl_easy_setopt(c->easy, CURLOPT_USERAGENT, "Music Player Daemon " VERSION); curl_easy_setopt(c->easy, CURLOPT_HEADERFUNCTION, input_curl_headerfunction); curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, c); curl_easy_setopt(c->easy, CURLOPT_WRITEFUNCTION, input_curl_writefunction); curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c); curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases); curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5); curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true); curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error); curl_easy_setopt(c->easy, CURLOPT_NOPROGRESS, 1l); curl_easy_setopt(c->easy, CURLOPT_NOSIGNAL, 1l); curl_easy_setopt(c->easy, CURLOPT_CONNECTTIMEOUT, 10l); if (proxy != NULL) curl_easy_setopt(c->easy, CURLOPT_PROXY, proxy); if (proxy_port > 0) curl_easy_setopt(c->easy, CURLOPT_PROXYPORT, (long)proxy_port); if (proxy_user != NULL && proxy_password != NULL) { char *proxy_auth_str = g_strconcat(proxy_user, ":", proxy_password, NULL); curl_easy_setopt(c->easy, CURLOPT_PROXYUSERPWD, proxy_auth_str); g_free(proxy_auth_str); } code = curl_easy_setopt(c->easy, CURLOPT_URL, c->url); if (code != CURLE_OK) { g_set_error(error_r, curl_quark(), code, "curl_easy_setopt() failed: %s", curl_easy_strerror(code)); return false; } c->request_headers = NULL; c->request_headers = curl_slist_append(c->request_headers, "Icy-Metadata: 1"); curl_easy_setopt(c->easy, CURLOPT_HTTPHEADER, c->request_headers); return true; }
/** * Wait for the libcurl socket. * * @return -1 on error, 0 if no data is available yet, 1 if data is * available */ static int input_curl_select(struct input_curl *c, GError **error_r) { fd_set rfds, wfds, efds; int max_fd, ret; CURLMcode mcode; struct timeval timeout = { .tv_sec = 1, .tv_usec = 0, }; assert(!c->eof); FD_ZERO(&rfds); FD_ZERO(&wfds); FD_ZERO(&efds); mcode = curl_multi_fdset(c->multi, &rfds, &wfds, &efds, &max_fd); if (mcode != CURLM_OK) { g_set_error(error_r, curl_quark(), mcode, "curl_multi_fdset() failed: %s", curl_multi_strerror(mcode)); return -1; } #if LIBCURL_VERSION_NUM >= 0x070f04 long timeout2; mcode = curl_multi_timeout(c->multi, &timeout2); if (mcode != CURLM_OK) { g_warning("curl_multi_timeout() failed: %s\n", curl_multi_strerror(mcode)); return -1; } if (timeout2 >= 0) { if (timeout2 > 10000) timeout2 = 10000; timeout.tv_sec = timeout2 / 1000; timeout.tv_usec = (timeout2 % 1000) * 1000; } #endif ret = select(max_fd + 1, &rfds, &wfds, &efds, &timeout); if (ret < 0) g_set_error(error_r, g_quark_from_static_string("errno"), errno, "select() failed: %s\n", g_strerror(errno)); return ret; } static bool fill_buffer(struct input_stream *is, GError **error_r) { struct input_curl *c = (struct input_curl *)is; CURLMcode mcode = CURLM_CALL_MULTI_PERFORM; while (!c->eof && g_queue_is_empty(c->buffers)) { int running_handles; bool bret; if (mcode != CURLM_CALL_MULTI_PERFORM) { /* if we're still here, there is no input yet - wait for input */ int ret = input_curl_select(c, error_r); if (ret <= 0) /* no data yet or error */ return false; } mcode = curl_multi_perform(c->multi, &running_handles); if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) { g_set_error(error_r, curl_quark(), mcode, "curl_multi_perform() failed: %s", curl_multi_strerror(mcode)); c->eof = true; is->ready = true; return false; } bret = input_curl_multi_info_read(c, error_r); if (!bret) return false; } return !g_queue_is_empty(c->buffers); } /** * Mark a part of the buffer object as consumed. */ static struct buffer * consume_buffer(struct buffer *buffer, size_t length) { assert(buffer != NULL); assert(buffer->consumed < buffer->size); buffer->consumed += length; if (buffer->consumed < buffer->size) return buffer; assert(buffer->consumed == buffer->size); g_free(buffer); return NULL; }
void http_client_request(const char *url, const char *post_data, const struct http_client_handler *handler, void *ctx) { struct http_request *request = g_new(struct http_request, 1); CURLcode code; CURLMcode mcode; bool success; request->handler = handler; request->handler_ctx = ctx; /* create a CURL request */ request->curl = curl_easy_init(); if (request->curl == NULL) { g_free(request); GError *error = g_error_new_literal(curl_quark(), 0, "curl_easy_init() failed"); handler->error(error, ctx); return; } mcode = curl_multi_add_handle(http_client.multi, request->curl); if (mcode != CURLM_OK) { curl_easy_cleanup(request->curl); g_free(request); GError *error = g_error_new_literal(curl_quark(), 0, "curl_multi_add_handle() failed"); handler->error(error, ctx); return; } /* .. and set it up */ curl_easy_setopt(request->curl, CURLOPT_USERAGENT, "mpdscribble/" VERSION); curl_easy_setopt(request->curl, CURLOPT_WRITEFUNCTION, http_request_writefunction); curl_easy_setopt(request->curl, CURLOPT_WRITEDATA, request); curl_easy_setopt(request->curl, CURLOPT_FAILONERROR, true); curl_easy_setopt(request->curl, CURLOPT_ERRORBUFFER, request->error); curl_easy_setopt(request->curl, CURLOPT_BUFFERSIZE, 2048); if (file_config.proxy != NULL) curl_easy_setopt(request->curl, CURLOPT_PROXY, file_config.proxy); request->post_data = g_strdup(post_data); if (request->post_data != NULL) { curl_easy_setopt(request->curl, CURLOPT_POST, true); curl_easy_setopt(request->curl, CURLOPT_POSTFIELDS, request->post_data); } code = curl_easy_setopt(request->curl, CURLOPT_URL, url); if (code != CURLE_OK) { curl_multi_remove_handle(http_client.multi, request->curl); curl_easy_cleanup(request->curl); g_free(request); GError *error = g_error_new_literal(curl_quark(), code, "curl_easy_setopt() failed"); handler->error(error, ctx); return; } request->body = g_string_sized_new(256); http_client.requests = g_slist_prepend(http_client.requests, request); /* initiate the transfer */ success = http_multi_perform(); if (!success) { http_client.requests = g_slist_remove(http_client.requests, request); http_request_free(request); GError *error = g_error_new_literal(curl_quark(), code, "http_multi_perform() failed"); handler->error(error, ctx); return; } if (!http_client.locked) http_multi_info_read(); }