/**
 * 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();
}