CurlInputPlugin.cxx 23.2 KB
Newer Older
1
/*
2
 * Copyright (C) 2003-2013 The Music Player Daemon Project
3
 * http://www.musicpd.org
Max Kellermann's avatar
Max Kellermann committed
4 5 6 7 8 9 10 11 12 13
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
14 15 16 17
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Max Kellermann's avatar
Max Kellermann committed
18 19
 */

20
#include "config.h"
21
#include "CurlInputPlugin.hxx"
22 23
#include "InputInternal.hxx"
#include "InputStream.hxx"
24
#include "InputPlugin.hxx"
25 26
#include "ConfigGlobal.hxx"
#include "ConfigData.hxx"
27
#include "tag/Tag.hxx"
28
#include "IcyMetaDataParser.hxx"
29
#include "event/MultiSocketMonitor.hxx"
30
#include "event/Call.hxx"
31
#include "IOThread.hxx"
32 33
#include "util/Error.hxx"
#include "util/Domain.hxx"
Max Kellermann's avatar
Max Kellermann committed
34 35

#include <assert.h>
36 37 38 39 40 41 42

#if defined(WIN32)
	#include <winsock2.h>
#else
	#include <sys/select.h>
#endif

Max Kellermann's avatar
Max Kellermann committed
43 44 45
#include <string.h>
#include <errno.h>

46
#include <list>
47 48
#include <forward_list>

Max Kellermann's avatar
Max Kellermann committed
49 50 51
#include <curl/curl.h>
#include <glib.h>

52 53 54 55
#if LIBCURL_VERSION_NUM < 0x071200
#error libcurl is too old
#endif

56 57 58
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "input_curl"

59 60 61 62 63 64 65
/**
 * Do not buffer more than this number of bytes.  It should be a
 * reasonable limit that doesn't make low-end machines suffer too
 * much, but doesn't cause stuttering on high-latency lines.
 */
static const size_t CURL_MAX_BUFFERED = 512 * 1024;

66 67 68 69 70
/**
 * Resume the stream at this number of bytes after it has been paused.
 */
static const size_t CURL_RESUME_AT = 384 * 1024;

Max Kellermann's avatar
Max Kellermann committed
71 72 73
/**
 * Buffers created by input_curl_writefunction().
 */
74
class CurlInputBuffer {
Max Kellermann's avatar
Max Kellermann committed
75 76 77 78 79 80 81
	/** size of the payload */
	size_t size;

	/** how much has been consumed yet? */
	size_t consumed;

	/** the payload */
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
	uint8_t *data;

public:
	CurlInputBuffer(const void *_data, size_t _size)
		:size(_size), consumed(0), data(new uint8_t[size]) {
		memcpy(data, _data, size);
	}

	~CurlInputBuffer() {
		delete[] data;
	}

	CurlInputBuffer(const CurlInputBuffer &) = delete;
	CurlInputBuffer &operator=(const CurlInputBuffer &) = delete;

	const void *Begin() const {
		return data + consumed;
	}

	size_t TotalSize() const {
		return size;
	}

	size_t Available() const {
		return size - consumed;
	}

	/**
	 * Mark a part of the buffer as consumed.
	 *
	 * @return false if the buffer is now empty
	 */
	bool Consume(size_t length) {
		assert(consumed < size);

		consumed += length;
		if (consumed < size)
			return true;

		assert(consumed == size);
		return false;
	}

	bool Read(void *dest, size_t length) {
		assert(consumed + length <= size);

		memcpy(dest, data + consumed, length);
		return Consume(length);
	}
Max Kellermann's avatar
Max Kellermann committed
131 132 133
};

struct input_curl {
134 135
	struct input_stream base;

Max Kellermann's avatar
Max Kellermann committed
136 137
	/* some buffers which were passed to libcurl, which we have
	   too free */
138
	char *range;
Max Kellermann's avatar
Max Kellermann committed
139 140 141 142
	struct curl_slist *request_headers;

	/** the curl handles */
	CURL *easy;
143

Max Kellermann's avatar
Max Kellermann committed
144 145
	/** list of buffers, where input_curl_writefunction() appends
	    to, and input_curl_read() reads from them */
146
	std::list<CurlInputBuffer> buffers;
Max Kellermann's avatar
Max Kellermann committed
147

148 149 150 151 152 153
	/**
	 * Is the connection currently paused?  That happens when the
	 * buffer was getting too large.  It will be unpaused when the
	 * buffer is below the threshold again.
	 */
	bool paused;
Max Kellermann's avatar
Max Kellermann committed
154

155 156
	/** error message provided by libcurl */
	char error[CURL_ERROR_SIZE];
157

158
	/** parser for icy-metadata */
159
	IcyMetaDataParser icy;
160

161 162 163 164
	/** the stream name from the icy-name response header */
	char *meta_name;

	/** the tag object ready to be requested via
165
	    input_stream::ReadTag() */
Max Kellermann's avatar
Max Kellermann committed
166
	Tag *tag;
167

168
	Error postponed_error;
169

170
	input_curl(const char *url, Mutex &mutex, Cond &cond)
171 172
		:base(input_plugin_curl, url, mutex, cond),
		 range(nullptr), request_headers(nullptr),
173 174
		 paused(false),
		 meta_name(nullptr),
175
		 tag(nullptr) {}
176 177 178 179 180

	~input_curl();

	input_curl(const input_curl &) = delete;
	input_curl &operator=(const input_curl &) = delete;
Max Kellermann's avatar
Max Kellermann committed
181 182
};

183 184 185 186 187 188 189 190 191 192 193 194 195
/**
 * This class monitors all CURL file descriptors.
 */
class CurlSockets final : private MultiSocketMonitor {
public:
	CurlSockets(EventLoop &_loop)
		:MultiSocketMonitor(_loop) {}

	using MultiSocketMonitor::InvalidateSockets;

private:
	void UpdateSockets();

196
	virtual int PrepareSockets() override;
197 198 199
	virtual void DispatchSockets() override;
};

Max Kellermann's avatar
Max Kellermann committed
200 201 202
/** libcurl should accept "ICY 200 OK" */
static struct curl_slist *http_200_aliases;

203 204 205 206
/** HTTP proxy settings */
static const char *proxy, *proxy_user, *proxy_password;
static unsigned proxy_port;

207 208 209 210 211 212 213
static struct {
	CURLM *multi;

	/**
	 * A linked list of all active HTTP requests.  An active
	 * request is one that doesn't have the "eof" flag set.
	 */
214
	std::forward_list<input_curl *> requests;
215

216
	CurlSockets *sockets;
217
} curl;
218

219 220 221
static constexpr Domain http_domain("http");
static constexpr Domain curl_domain("curl");
static constexpr Domain curlm_domain("curlm");
222

223 224 225
/**
 * Find a request by its CURL "easy" handle.
 *
226
 * Runs in the I/O thread.  No lock needed.
227 228 229 230
 */
static struct input_curl *
input_curl_find_request(CURL *easy)
{
231 232
	assert(io_thread_inside());

233
	for (auto c : curl.requests)
234 235 236 237 238 239
		if (c->easy == easy)
			return c;

	return NULL;
}

240 241
static void
input_curl_resume(struct input_curl *c)
242
{
243 244
	assert(io_thread_inside());

245 246
	if (c->paused) {
		c->paused = false;
247
		curl_easy_pause(c->easy, CURLPAUSE_CONT);
248 249 250 251 252 253 254
	}
}

/**
 * Calculates the GLib event bit mask for one file descriptor,
 * obtained from three #fd_set objects filled by curl_multi_fdset().
 */
255
static unsigned
256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
input_curl_fd_events(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds)
{
	gushort events = 0;

	if (FD_ISSET(fd, rfds)) {
		events |= G_IO_IN | G_IO_HUP | G_IO_ERR;
		FD_CLR(fd, rfds);
	}

	if (FD_ISSET(fd, wfds)) {
		events |= G_IO_OUT | G_IO_ERR;
		FD_CLR(fd, wfds);
	}

	if (FD_ISSET(fd, efds)) {
		events |= G_IO_HUP | G_IO_ERR;
		FD_CLR(fd, efds);
	}

	return events;
}

/**
 * Updates all registered GPollFD objects, unregisters old ones,
 * registers new ones.
 *
282
 * Runs in the I/O thread.  No lock needed.
283
 */
284 285
void
CurlSockets::UpdateSockets()
286
{
287 288
	assert(io_thread_inside());

289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
	fd_set rfds, wfds, efds;

	FD_ZERO(&rfds);
	FD_ZERO(&wfds);
	FD_ZERO(&efds);

	int max_fd;
	CURLMcode mcode = curl_multi_fdset(curl.multi, &rfds, &wfds,
					   &efds, &max_fd);
	if (mcode != CURLM_OK) {
		g_warning("curl_multi_fdset() failed: %s\n",
			  curl_multi_strerror(mcode));
		return;
	}

304 305 306 307
	UpdateSocketList([&rfds, &wfds, &efds](int fd){
			return input_curl_fd_events(fd, &rfds,
						    &wfds, &efds);
		});
308 309

	for (int fd = 0; fd <= max_fd; ++fd) {
310 311 312
		unsigned events = input_curl_fd_events(fd, &rfds, &wfds, &efds);
		if (events != 0)
			AddSocket(fd, events);
313 314 315
	}
}

316 317 318
/**
 * Runs in the I/O thread.  No lock needed.
 */
319
static bool
320
input_curl_easy_add(struct input_curl *c, Error &error)
321
{
322
	assert(io_thread_inside());
323 324 325 326
	assert(c != NULL);
	assert(c->easy != NULL);
	assert(input_curl_find_request(c->easy) == NULL);

327
	curl.requests.push_front(c);
328 329 330

	CURLMcode mcode = curl_multi_add_handle(curl.multi, c->easy);
	if (mcode != CURLM_OK) {
331 332 333
		error.Format(curlm_domain, mcode,
			     "curl_multi_add_handle() failed: %s",
			     curl_multi_strerror(mcode));
334 335 336
		return false;
	}

337
	curl.sockets->InvalidateSockets();
338 339 340 341

	return true;
}

342 343 344 345 346
/**
 * Call input_curl_easy_add() in the I/O thread.  May be called from
 * any thread.  Caller must not hold a mutex.
 */
static bool
347
input_curl_easy_add_indirect(struct input_curl *c, Error &error)
348 349 350 351
{
	assert(c != NULL);
	assert(c->easy != NULL);

352
	bool result;
353 354
	BlockingCall(io_thread_get(), [c, &error, &result](){
			result = input_curl_easy_add(c, error);
355 356
		});
	return result;
357 358
}

359 360 361 362
/**
 * Frees the current "libcurl easy" handle, and everything associated
 * with it.
 *
363
 * Runs in the I/O thread.
364 365 366 367
 */
static void
input_curl_easy_free(struct input_curl *c)
{
368
	assert(io_thread_inside());
369 370 371 372 373
	assert(c != NULL);

	if (c->easy == NULL)
		return;

374
	curl.requests.remove(c);
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395

	curl_multi_remove_handle(curl.multi, c->easy);
	curl_easy_cleanup(c->easy);
	c->easy = NULL;

	curl_slist_free_all(c->request_headers);
	c->request_headers = NULL;

	g_free(c->range);
	c->range = NULL;
}

/**
 * Frees the current "libcurl easy" handle, and everything associated
 * with it.
 *
 * The mutex must not be locked.
 */
static void
input_curl_easy_free_indirect(struct input_curl *c)
{
396 397 398 399 400
	BlockingCall(io_thread_get(), [c](){
			input_curl_easy_free(c);
			curl.sockets->InvalidateSockets();
		});

401 402 403 404 405 406
	assert(c->easy == NULL);
}

/**
 * Abort and free all HTTP requests.
 *
407
 * Runs in the I/O thread.  The caller must not hold locks.
408 409
 */
static void
410
input_curl_abort_all_requests(const Error &error)
411
{
412
	assert(io_thread_inside());
413
	assert(error.IsDefined());
414

415 416
	while (!curl.requests.empty()) {
		struct input_curl *c = curl.requests.front();
417
		assert(!c->postponed_error.IsDefined());
418 419

		input_curl_easy_free(c);
420

421
		const ScopeLock protect(c->base.mutex);
422

423
		c->postponed_error.Set(error);
424
		c->base.ready = true;
425

426
		c->base.cond.broadcast();
427 428 429 430 431 432 433
	}

}

/**
 * A HTTP request is finished.
 *
434
 * Runs in the I/O thread.  The caller must not hold locks.
435 436 437 438
 */
static void
input_curl_request_done(struct input_curl *c, CURLcode result, long status)
{
439
	assert(io_thread_inside());
440
	assert(c != NULL);
441
	assert(c->easy == NULL);
442
	assert(!c->postponed_error.IsDefined());
443

444
	const ScopeLock protect(c->base.mutex);
445

446
	if (result != CURLE_OK) {
447 448
		c->postponed_error.Format(curl_domain, result,
					  "curl failed: %s", c->error);
449
	} else if (status < 200 || status >= 300) {
450 451 452
		c->postponed_error.Format(http_domain, status,
					  "got HTTP status %ld",
					  status);
453
	}
454

455
	c->base.ready = true;
456

457
	c->base.cond.broadcast();
458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
}

static void
input_curl_handle_done(CURL *easy_handle, CURLcode result)
{
	struct input_curl *c = input_curl_find_request(easy_handle);
	assert(c != NULL);

	long status = 0;
	curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &status);

	input_curl_easy_free(c);
	input_curl_request_done(c, result, status);
}

/**
 * Check for finished HTTP responses.
 *
476
 * Runs in the I/O thread.  The caller must not hold locks.
477 478 479 480
 */
static void
input_curl_info_read(void)
{
481 482
	assert(io_thread_inside());

483 484 485 486 487 488 489 490 491 492 493 494 495
	CURLMsg *msg;
	int msgs_in_queue;

	while ((msg = curl_multi_info_read(curl.multi,
					   &msgs_in_queue)) != NULL) {
		if (msg->msg == CURLMSG_DONE)
			input_curl_handle_done(msg->easy_handle, msg->data.result);
	}
}

/**
 * Give control to CURL.
 *
496
 * Runs in the I/O thread.  The caller must not hold locks.
497 498 499 500
 */
static bool
input_curl_perform(void)
{
501 502
	assert(io_thread_inside());

503 504 505 506 507 508 509 510
	CURLMcode mcode;

	do {
		int running_handles;
		mcode = curl_multi_perform(curl.multi, &running_handles);
	} while (mcode == CURLM_CALL_MULTI_PERFORM);

	if (mcode != CURLM_OK && mcode != CURLM_CALL_MULTI_PERFORM) {
511 512 513 514
		Error error;
		error.Format(curlm_domain, mcode,
			     "curl_multi_perform() failed: %s",
			     curl_multi_strerror(mcode));
515 516 517 518 519 520 521
		input_curl_abort_all_requests(error);
		return false;
	}

	return true;
}

522 523
int
CurlSockets::PrepareSockets()
524
{
525
	UpdateSockets();
526 527 528 529 530 531 532 533 534 535 536

	long timeout2;
	CURLMcode mcode = curl_multi_timeout(curl.multi, &timeout2);
	if (mcode == CURLM_OK) {
		if (timeout2 >= 0 && timeout2 < 10)
			/* CURL 7.21.1 likes to report "timeout=0",
			   which means we're running in a busy loop.
			   Quite a bad idea to waste so much CPU.
			   Let's use a lower limit of 10ms. */
			timeout2 = 10;

537 538
		return timeout2;
	} else {
539 540
		g_warning("curl_multi_timeout() failed: %s\n",
			  curl_multi_strerror(mcode));
541 542
		return -1;
	}
543 544
}

545 546
void
CurlSockets::DispatchSockets()
547 548 549 550 551 552 553 554 555 556
{
	if (input_curl_perform())
		input_curl_info_read();
}

/*
 * input_plugin methods
 *
 */

557
static bool
558
input_curl_init(const config_param &param, Error &error)
Max Kellermann's avatar
Max Kellermann committed
559 560
{
	CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
561
	if (code != CURLE_OK) {
562 563 564
		error.Format(curl_domain, code,
			     "curl_global_init() failed: %s",
			     curl_easy_strerror(code));
565 566
		return false;
	}
Max Kellermann's avatar
Max Kellermann committed
567 568

	http_200_aliases = curl_slist_append(http_200_aliases, "ICY 200 OK");
569

570 571 572 573
	proxy = param.GetBlockValue("proxy");
	proxy_port = param.GetBlockValue("proxy_port", 0u);
	proxy_user = param.GetBlockValue("proxy_user");
	proxy_password = param.GetBlockValue("proxy_password");
574 575 576 577 578 579 580 581 582 583

	if (proxy == NULL) {
		/* deprecated proxy configuration */
		proxy = config_get_string(CONF_HTTP_PROXY_HOST, NULL);
		proxy_port = config_get_positive(CONF_HTTP_PROXY_PORT, 0);
		proxy_user = config_get_string(CONF_HTTP_PROXY_USER, NULL);
		proxy_password = config_get_string(CONF_HTTP_PROXY_PASSWORD,
						   "");
	}

584 585
	curl.multi = curl_multi_init();
	if (curl.multi == NULL) {
586
		error.Set(curl_domain, 0, "curl_multi_init() failed");
587 588 589
		return false;
	}

590
	curl.sockets = new CurlSockets(io_thread_get());
591

592
	return true;
Max Kellermann's avatar
Max Kellermann committed
593 594
}

595 596
static void
input_curl_finish(void)
Max Kellermann's avatar
Max Kellermann committed
597
{
598
	assert(curl.requests.empty());
599

600 601 602
	BlockingCall(io_thread_get(), [](){
			delete curl.sockets;
		});
603 604 605

	curl_multi_cleanup(curl.multi);

Max Kellermann's avatar
Max Kellermann committed
606 607 608 609 610
	curl_slist_free_all(http_200_aliases);

	curl_global_cleanup();
}

611 612 613
/**
 * Determine the total sizes of all buffers, including portions that
 * have already been consumed.
614 615
 *
 * The caller must lock the mutex.
616
 */
617
gcc_pure
618 619 620 621 622
static size_t
curl_total_buffer_size(const struct input_curl *c)
{
	size_t total = 0;

623 624
	for (const auto &i : c->buffers)
		total += i.TotalSize();
625 626 627 628

	return total;
}

629
input_curl::~input_curl()
Max Kellermann's avatar
Max Kellermann committed
630
{
Max Kellermann's avatar
Max Kellermann committed
631 632
	delete tag;

633
	g_free(meta_name);
634

635
	input_curl_easy_free_indirect(this);
Max Kellermann's avatar
Max Kellermann committed
636 637
}

638
static bool
639
input_curl_check(struct input_stream *is, Error &error)
640 641 642
{
	struct input_curl *c = (struct input_curl *)is;

643
	bool success = !c->postponed_error.IsDefined();
644
	if (!success) {
645 646
		error = std::move(c->postponed_error);
		c->postponed_error.Clear();
647 648 649 650 651
	}

	return success;
}

Max Kellermann's avatar
Max Kellermann committed
652
static Tag *
653 654
input_curl_tag(struct input_stream *is)
{
655
	struct input_curl *c = (struct input_curl *)is;
Max Kellermann's avatar
Max Kellermann committed
656
	Tag *tag = c->tag;
657 658 659 660 661

	c->tag = NULL;
	return tag;
}

662
static bool
663
fill_buffer(struct input_curl *c, Error &error)
664
{
665
	while (c->easy != NULL && c->buffers.empty())
666
		c->base.cond.wait(c->base.mutex);
667

668 669 670
	if (c->postponed_error.IsDefined()) {
		error = std::move(c->postponed_error);
		c->postponed_error.Clear();
671
		return false;
672 673
	}

674
	return !c->buffers.empty();
675 676
}

Max Kellermann's avatar
Max Kellermann committed
677
static size_t
678
read_from_buffer(IcyMetaDataParser &icy, std::list<CurlInputBuffer> &buffers,
679
		 void *dest0, size_t length)
Max Kellermann's avatar
Max Kellermann committed
680
{
681
	auto &buffer = buffers.front();
682
	uint8_t *dest = (uint8_t *)dest0;
683 684
	size_t nbytes = 0;

685 686
	if (length > buffer.Available())
		length = buffer.Available();
Max Kellermann's avatar
Max Kellermann committed
687

688 689 690
	while (true) {
		size_t chunk;

691
		chunk = icy.Data(length);
692
		if (chunk > 0) {
693
			const bool empty = !buffer.Read(dest, chunk);
694 695 696 697 698

			nbytes += chunk;
			dest += chunk;
			length -= chunk;

699 700
			if (empty) {
				buffers.pop_front();
701
				break;
702
			}
703 704 705

			if (length == 0)
				break;
706 707
		}

708
		chunk = icy.Meta(buffer.Begin(), length);
709
		if (chunk > 0) {
710
			const bool empty = !buffer.Consume(chunk);
711 712 713

			length -= chunk;

714 715
			if (empty) {
				buffers.pop_front();
716
				break;
717
			}
718 719 720

			if (length == 0)
				break;
721 722 723 724 725 726 727 728 729
		}
	}

	return nbytes;
}

static void
copy_icy_tag(struct input_curl *c)
{
Max Kellermann's avatar
Max Kellermann committed
730
	Tag *tag = c->icy.ReadTag();
731 732 733 734

	if (tag == NULL)
		return;

Max Kellermann's avatar
Max Kellermann committed
735
	delete c->tag;
736

Max Kellermann's avatar
Max Kellermann committed
737 738
	if (c->meta_name != NULL && !tag->HasType(TAG_NAME))
		tag->AddItem(TAG_NAME, c->meta_name);
739 740

	c->tag = tag;
Max Kellermann's avatar
Max Kellermann committed
741 742
}

743 744 745 746 747
static bool
input_curl_available(struct input_stream *is)
{
	struct input_curl *c = (struct input_curl *)is;

748
	return c->postponed_error.IsDefined() || c->easy == NULL ||
749
		!c->buffers.empty();
750 751
}

Max Kellermann's avatar
Max Kellermann committed
752
static size_t
753
input_curl_read(struct input_stream *is, void *ptr, size_t size,
754
		Error &error)
Max Kellermann's avatar
Max Kellermann committed
755
{
756
	struct input_curl *c = (struct input_curl *)is;
757
	bool success;
Max Kellermann's avatar
Max Kellermann committed
758
	size_t nbytes = 0;
759
	char *dest = (char *)ptr;
Max Kellermann's avatar
Max Kellermann committed
760

761 762 763
	do {
		/* fill the buffer */

764
		success = fill_buffer(c, error);
765
		if (!success)
766 767 768 769
			return 0;

		/* send buffer contents */

770
		while (size > 0 && !c->buffers.empty()) {
771
			size_t copy = read_from_buffer(c->icy, c->buffers,
772
						       dest + nbytes, size);
773 774 775 776 777

			nbytes += copy;
			size -= copy;
		}
	} while (nbytes == 0);
Max Kellermann's avatar
Max Kellermann committed
778

779
	if (c->icy.IsDefined())
780 781
		copy_icy_tag(c);

782
	is->offset += (goffset)nbytes;
783

784
	if (c->paused && curl_total_buffer_size(c) < CURL_RESUME_AT) {
785
		c->base.mutex.unlock();
786 787 788 789 790

		BlockingCall(io_thread_get(), [c](){
				input_curl_resume(c);
			});

791
		c->base.mutex.lock();
792
	}
793

Max Kellermann's avatar
Max Kellermann committed
794 795 796
	return nbytes;
}

797
static void
Max Kellermann's avatar
Max Kellermann committed
798 799
input_curl_close(struct input_stream *is)
{
800 801
	struct input_curl *c = (struct input_curl *)is;

802
	delete c;
Max Kellermann's avatar
Max Kellermann committed
803 804
}

805
static bool
806
input_curl_eof(gcc_unused struct input_stream *is)
Max Kellermann's avatar
Max Kellermann committed
807
{
808
	struct input_curl *c = (struct input_curl *)is;
Max Kellermann's avatar
Max Kellermann committed
809

810
	return c->easy == NULL && c->buffers.empty();
Max Kellermann's avatar
Max Kellermann committed
811 812 813 814 815 816
}

/** called by curl when new data is available */
static size_t
input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
{
817
	struct input_curl *c = (struct input_curl *)stream;
Max Kellermann's avatar
Max Kellermann committed
818 819 820 821
	char name[64];

	size *= nmemb;

822 823 824 825
	const char *header = (const char *)ptr;
	const char *end = header + size;

	const char *value = (const char *)memchr(header, ':', size);
826
	if (value == NULL || (size_t)(value - header) >= sizeof(name))
Max Kellermann's avatar
Max Kellermann committed
827 828
		return size;

829 830 831 832 833 834 835 836 837 838 839 840 841 842
	memcpy(name, header, value - header);
	name[value - header] = 0;

	/* skip the colon */

	++value;

	/* strip the value */

	while (value < end && g_ascii_isspace(*value))
		++value;

	while (end > value && g_ascii_isspace(end[-1]))
		--end;
Max Kellermann's avatar
Max Kellermann committed
843

844
	if (g_ascii_strcasecmp(name, "accept-ranges") == 0) {
845
		/* a stream with icy-metadata is not seekable */
846
		if (!c->icy.IsDefined())
847
			c->base.seekable = true;
848
	} else if (g_ascii_strcasecmp(name, "content-length") == 0) {
849
		char buffer[64];
Max Kellermann's avatar
Max Kellermann committed
850

851
		if ((size_t)(end - header) >= sizeof(buffer))
Max Kellermann's avatar
Max Kellermann committed
852 853
			return size;

854 855
		memcpy(buffer, value, end - value);
		buffer[end - value] = 0;
Max Kellermann's avatar
Max Kellermann committed
856

857
		c->base.size = c->base.offset + g_ascii_strtoull(buffer, NULL, 10);
858
	} else if (g_ascii_strcasecmp(name, "content-type") == 0) {
859
		c->base.mime.assign(value, end);
860 861 862
	} else if (g_ascii_strcasecmp(name, "icy-name") == 0 ||
		   g_ascii_strcasecmp(name, "ice-name") == 0 ||
		   g_ascii_strcasecmp(name, "x-audiocast-name") == 0) {
863 864 865
		g_free(c->meta_name);
		c->meta_name = g_strndup(value, end - value);

Max Kellermann's avatar
Max Kellermann committed
866
		delete c->tag;
867

Max Kellermann's avatar
Max Kellermann committed
868 869
		c->tag = new Tag();
		c->tag->AddItem(TAG_NAME, c->meta_name);
870
	} else if (g_ascii_strcasecmp(name, "icy-metaint") == 0) {
871 872 873 874
		char buffer[64];
		size_t icy_metaint;

		if ((size_t)(end - header) >= sizeof(buffer) ||
875
		    c->icy.IsDefined())
876 877 878 879 880 881 882 883 884
			return size;

		memcpy(buffer, value, end - value);
		buffer[end - value] = 0;

		icy_metaint = g_ascii_strtoull(buffer, NULL, 10);
		g_debug("icy-metaint=%zu", icy_metaint);

		if (icy_metaint > 0) {
885
			c->icy.Start(icy_metaint);
886 887 888

			/* a stream with icy-metadata is not
			   seekable */
889
			c->base.seekable = false;
890
		}
Max Kellermann's avatar
Max Kellermann committed
891 892 893 894 895 896 897 898 899
	}

	return size;
}

/** called by curl when new data is available */
static size_t
input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream)
{
900
	struct input_curl *c = (struct input_curl *)stream;
Max Kellermann's avatar
Max Kellermann committed
901 902 903 904 905

	size *= nmemb;
	if (size == 0)
		return 0;

906
	const ScopeLock protect(c->base.mutex);
907

908 909 910 911 912
	if (curl_total_buffer_size(c) + size >= CURL_MAX_BUFFERED) {
		c->paused = true;
		return CURL_WRITEFUNC_PAUSE;
	}

913
	c->buffers.emplace_back(ptr, size);
914
	c->base.ready = true;
Max Kellermann's avatar
Max Kellermann committed
915

916
	c->base.cond.broadcast();
Max Kellermann's avatar
Max Kellermann committed
917 918 919 920
	return size;
}

static bool
921
input_curl_easy_init(struct input_curl *c, Error &error)
Max Kellermann's avatar
Max Kellermann committed
922 923 924 925 926
{
	CURLcode code;

	c->easy = curl_easy_init();
	if (c->easy == NULL) {
927
		error.Set(curl_domain, "curl_easy_init() failed");
Max Kellermann's avatar
Max Kellermann committed
928 929 930
		return false;
	}

931 932
	curl_easy_setopt(c->easy, CURLOPT_USERAGENT,
			 "Music Player Daemon " VERSION);
Max Kellermann's avatar
Max Kellermann committed
933 934
	curl_easy_setopt(c->easy, CURLOPT_HEADERFUNCTION,
			 input_curl_headerfunction);
935
	curl_easy_setopt(c->easy, CURLOPT_WRITEHEADER, c);
Max Kellermann's avatar
Max Kellermann committed
936 937
	curl_easy_setopt(c->easy, CURLOPT_WRITEFUNCTION,
			 input_curl_writefunction);
938
	curl_easy_setopt(c->easy, CURLOPT_WRITEDATA, c);
Max Kellermann's avatar
Max Kellermann committed
939
	curl_easy_setopt(c->easy, CURLOPT_HTTP200ALIASES, http_200_aliases);
940
	curl_easy_setopt(c->easy, CURLOPT_FOLLOWLOCATION, 1);
941
	curl_easy_setopt(c->easy, CURLOPT_NETRC, 1);
942
	curl_easy_setopt(c->easy, CURLOPT_MAXREDIRS, 5);
943
	curl_easy_setopt(c->easy, CURLOPT_FAILONERROR, true);
944
	curl_easy_setopt(c->easy, CURLOPT_ERRORBUFFER, c->error);
945 946 947
	curl_easy_setopt(c->easy, CURLOPT_NOPROGRESS, 1l);
	curl_easy_setopt(c->easy, CURLOPT_NOSIGNAL, 1l);
	curl_easy_setopt(c->easy, CURLOPT_CONNECTTIMEOUT, 10l);
Max Kellermann's avatar
Max Kellermann committed
948

949 950
	if (proxy != NULL)
		curl_easy_setopt(c->easy, CURLOPT_PROXY, proxy);
951

952 953
	if (proxy_port > 0)
		curl_easy_setopt(c->easy, CURLOPT_PROXYPORT, (long)proxy_port);
954

955
	if (proxy_user != NULL && proxy_password != NULL) {
956
		char *proxy_auth_str =
957
			g_strconcat(proxy_user, ":", proxy_password, NULL);
958 959 960 961
		curl_easy_setopt(c->easy, CURLOPT_PROXYUSERPWD, proxy_auth_str);
		g_free(proxy_auth_str);
	}

962
	code = curl_easy_setopt(c->easy, CURLOPT_URL, c->base.uri.c_str());
963
	if (code != CURLE_OK) {
964 965 966
		error.Format(curl_domain, code,
			     "curl_easy_setopt() failed: %s",
			     curl_easy_strerror(code));
Max Kellermann's avatar
Max Kellermann committed
967
		return false;
968
	}
Max Kellermann's avatar
Max Kellermann committed
969 970 971 972 973 974 975 976 977

	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;
}

978
static bool
979
input_curl_seek(struct input_stream *is, goffset offset, int whence,
980
		Error &error)
Max Kellermann's avatar
Max Kellermann committed
981
{
982
	struct input_curl *c = (struct input_curl *)is;
Max Kellermann's avatar
Max Kellermann committed
983 984
	bool ret;

985 986
	assert(is->ready);

987 988 989
	if (whence == SEEK_SET && offset == is->offset)
		/* no-op */
		return true;
990

Max Kellermann's avatar
Max Kellermann committed
991
	if (!is->seekable)
992
		return false;
Max Kellermann's avatar
Max Kellermann committed
993 994 995 996 997 998 999 1000

	/* calculate the absolute offset */

	switch (whence) {
	case SEEK_SET:
		break;

	case SEEK_CUR:
1001
		offset += is->offset;
Max Kellermann's avatar
Max Kellermann committed
1002 1003 1004
		break;

	case SEEK_END:
1005 1006 1007 1008
		if (is->size < 0)
			/* stream size is not known */
			return false;

1009
		offset += is->size;
Max Kellermann's avatar
Max Kellermann committed
1010 1011 1012
		break;

	default:
1013
		return false;
Max Kellermann's avatar
Max Kellermann committed
1014 1015
	}

1016
	if (offset < 0)
1017
		return false;
Max Kellermann's avatar
Max Kellermann committed
1018

1019 1020
	/* check if we can fast-forward the buffer */

1021 1022 1023
	while (offset > is->offset && !c->buffers.empty()) {
		auto &buffer = c->buffers.front();
		size_t length = buffer.Available();
1024
		if (offset - is->offset < (goffset)length)
1025 1026
			length = offset - is->offset;

1027 1028 1029
		const bool empty = !buffer.Consume(length);
		if (empty)
			c->buffers.pop_front();
1030

1031 1032 1033 1034 1035 1036
		is->offset += length;
	}

	if (offset == is->offset)
		return true;

Max Kellermann's avatar
Max Kellermann committed
1037 1038
	/* close the old connection and open a new one */

1039
	c->base.mutex.unlock();
1040

1041
	input_curl_easy_free_indirect(c);
1042
	c->buffers.clear();
Max Kellermann's avatar
Max Kellermann committed
1043

1044
	is->offset = offset;
1045 1046 1047 1048 1049 1050 1051
	if (is->offset == is->size) {
		/* seek to EOF: simulate empty result; avoid
		   triggering a "416 Requested Range Not Satisfiable"
		   response */
		return true;
	}

1052
	ret = input_curl_easy_init(c, error);
Max Kellermann's avatar
Max Kellermann committed
1053
	if (!ret)
1054
		return false;
Max Kellermann's avatar
Max Kellermann committed
1055 1056 1057 1058

	/* send the "Range" header */

	if (is->offset > 0) {
1059
		c->range = g_strdup_printf("%lld-", (long long)is->offset);
Max Kellermann's avatar
Max Kellermann committed
1060 1061 1062
		curl_easy_setopt(c->easy, CURLOPT_RANGE, c->range);
	}

1063 1064
	c->base.ready = false;

1065
	if (!input_curl_easy_add_indirect(c, error))
1066
		return false;
1067

1068
	c->base.mutex.lock();
1069 1070

	while (!c->base.ready)
1071
		c->base.cond.wait(c->base.mutex);
Max Kellermann's avatar
Max Kellermann committed
1072

1073 1074 1075
	if (c->postponed_error.IsDefined()) {
		error = std::move(c->postponed_error);
		c->postponed_error.Clear();
1076 1077 1078 1079
		return false;
	}

	return true;
Max Kellermann's avatar
Max Kellermann committed
1080 1081
}

1082
static struct input_stream *
1083
input_curl_open(const char *url, Mutex &mutex, Cond &cond,
1084
		Error &error)
Max Kellermann's avatar
Max Kellermann committed
1085
{
Ales Guzik's avatar
Ales Guzik committed
1086 1087
	if ((strncmp(url, "http://",  7) != 0) &&
	    (strncmp(url, "https://", 8) != 0))
1088
		return NULL;
1089

1090
	struct input_curl *c = new input_curl(url, mutex, cond);
Max Kellermann's avatar
Max Kellermann committed
1091

1092
	if (!input_curl_easy_init(c, error)) {
1093
		delete c;
1094
		return NULL;
Max Kellermann's avatar
Max Kellermann committed
1095 1096
	}

1097
	if (!input_curl_easy_add_indirect(c, error)) {
1098
		delete c;
1099
		return NULL;
1100 1101
	}

1102
	return &c->base;
Max Kellermann's avatar
Max Kellermann committed
1103
}
1104 1105

const struct input_plugin input_plugin_curl = {
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117
	"curl",
	input_curl_init,
	input_curl_finish,
	input_curl_open,
	input_curl_close,
	input_curl_check,
	nullptr,
	input_curl_tag,
	input_curl_available,
	input_curl_read,
	input_curl_eof,
	input_curl_seek,
1118
};