inputStream_http.c 20.7 KB
Newer Older
Warren Dukes's avatar
Warren Dukes committed
1
/* the Music Player Daemon (MPD)
2
 * (c)2003-2006 by Warren Dukes (warren.dukes@gmail.com)
Warren Dukes's avatar
Warren Dukes committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 * This project's homepage is: http://www.musicpd.org
 *
 * 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.
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "inputStream_http.h"

#include "utils.h"
Warren Dukes's avatar
Warren Dukes committed
22
#include "log.h"
23
#include "conf.h"
Warren Dukes's avatar
Warren Dukes committed
24 25

#include <stdio.h>
Warren Dukes's avatar
Warren Dukes committed
26
#include <sys/time.h>
Warren Dukes's avatar
Warren Dukes committed
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

#define HTTP_CONN_STATE_CLOSED  0
#define HTTP_CONN_STATE_INIT    1
#define HTTP_CONN_STATE_HELLO   2
#define HTTP_CONN_STATE_OPEN    3
#define HTTP_CONN_STATE_REOPEN  4

45 46
#define HTTP_BUFFER_SIZE_DEFAULT        131072
#define HTTP_PREBUFFER_SIZE_DEFAULT	(HTTP_BUFFER_SIZE_DEFAULT >> 2)
Warren Dukes's avatar
Warren Dukes committed
47 48 49

#define HTTP_REDIRECT_MAX    10

Avuton Olrich's avatar
Avuton Olrich committed
50 51 52 53
static char *proxyHost = NULL;
static char *proxyPort = NULL;
static char *proxyUser = NULL;
static char *proxyPassword = NULL;
54 55
static int bufferSize = HTTP_BUFFER_SIZE_DEFAULT;
static int prebufferSize = HTTP_PREBUFFER_SIZE_DEFAULT;
56

Warren Dukes's avatar
Warren Dukes committed
57
typedef struct _InputStreemHTTPData {
Avuton Olrich's avatar
Avuton Olrich committed
58 59
	char *host;
	char *path;
60
	char *port;
Avuton Olrich's avatar
Avuton Olrich committed
61 62 63 64 65 66
	int sock;
	int connState;
	char *buffer;
	size_t buflen;
	int timesRedirected;
	int icyMetaint;
67
	int prebuffer;
Warren Dukes's avatar
Warren Dukes committed
68
	int icyOffset;
Avuton Olrich's avatar
Avuton Olrich committed
69 70
	char *proxyAuth;
	char *httpAuth;
Warren Dukes's avatar
Warren Dukes committed
71 72
} InputStreamHTTPData;

Avuton Olrich's avatar
Avuton Olrich committed
73 74 75 76
void inputStream_initHttp(void)
{
	ConfigParam *param = getConfigParam(CONF_HTTP_PROXY_HOST);
	char *test;
77

Avuton Olrich's avatar
Avuton Olrich committed
78
	if (param) {
79 80 81 82
		proxyHost = param->value;

		param = getConfigParam(CONF_HTTP_PROXY_PORT);

Avuton Olrich's avatar
Avuton Olrich committed
83
		if (!param) {
84
			ERROR("%s specified but not %s", CONF_HTTP_PROXY_HOST,
Avuton Olrich's avatar
Avuton Olrich committed
85
			      CONF_HTTP_PROXY_PORT);
86 87
			exit(EXIT_FAILURE);
		}
88
		proxyPort = param->value;
89

90 91
		param = getConfigParam(CONF_HTTP_PROXY_USER);

Avuton Olrich's avatar
Avuton Olrich committed
92
		if (param) {
93
			proxyUser = param->value;
Avuton Olrich's avatar
Avuton Olrich committed
94

95 96
			param = getConfigParam(CONF_HTTP_PROXY_PASSWORD);

Avuton Olrich's avatar
Avuton Olrich committed
97
			if (!param) {
98
				ERROR("%s specified but not %s\n",
Avuton Olrich's avatar
Avuton Olrich committed
99 100
				      CONF_HTTP_PROXY_USER,
				      CONF_HTTP_PROXY_PASSWORD);
101 102 103
				exit(EXIT_FAILURE);
			}

Avuton Olrich's avatar
Avuton Olrich committed
104
			proxyPassword = param->value;
105 106
		}

107 108
		param = getConfigParam(CONF_HTTP_PROXY_PASSWORD);

Avuton Olrich's avatar
Avuton Olrich committed
109
		if (param) {
110
			ERROR("%s specified but not %s\n",
Avuton Olrich's avatar
Avuton Olrich committed
111
			      CONF_HTTP_PROXY_PASSWORD, CONF_HTTP_PROXY_USER);
112 113
			exit(EXIT_FAILURE);
		}
Avuton Olrich's avatar
Avuton Olrich committed
114 115 116
	} else if ((param = getConfigParam(CONF_HTTP_PROXY_PORT))) {
		ERROR("%s specified but not %s, line %i\n",
		      CONF_HTTP_PROXY_PORT, CONF_HTTP_PROXY_HOST, param->line);
117
		exit(EXIT_FAILURE);
Avuton Olrich's avatar
Avuton Olrich committed
118 119 120
	} else if ((param = getConfigParam(CONF_HTTP_PROXY_USER))) {
		ERROR("%s specified but not %s, line %i\n",
		      CONF_HTTP_PROXY_USER, CONF_HTTP_PROXY_HOST, param->line);
121
		exit(EXIT_FAILURE);
Avuton Olrich's avatar
Avuton Olrich committed
122 123 124 125
	} else if ((param = getConfigParam(CONF_HTTP_PROXY_PASSWORD))) {
		ERROR("%s specified but not %s, line %i\n",
		      CONF_HTTP_PROXY_PASSWORD, CONF_HTTP_PROXY_HOST,
		      param->line);
126 127
		exit(EXIT_FAILURE);
	}
128 129 130

	param = getConfigParam(CONF_HTTP_BUFFER_SIZE);

Avuton Olrich's avatar
Avuton Olrich committed
131
	if (param) {
132
		bufferSize = strtol(param->value, &test, 10);
Avuton Olrich's avatar
Avuton Olrich committed
133 134

		if (bufferSize <= 0 || *test != '\0') {
135
			ERROR("\"%s\" specified for %s at line %i is not a "
Avuton Olrich's avatar
Avuton Olrich committed
136 137
			      "positive integer\n",
			      param->value, CONF_HTTP_BUFFER_SIZE, param->line);
138 139 140 141 142
			exit(EXIT_FAILURE);
		}

		bufferSize *= 1024;

Avuton Olrich's avatar
Avuton Olrich committed
143 144
		if (prebufferSize > bufferSize)
			prebufferSize = bufferSize;
145 146 147 148
	}

	param = getConfigParam(CONF_HTTP_PREBUFFER_SIZE);

Avuton Olrich's avatar
Avuton Olrich committed
149
	if (param) {
150
		prebufferSize = strtol(param->value, &test, 10);
Avuton Olrich's avatar
Avuton Olrich committed
151 152

		if (prebufferSize <= 0 || *test != '\0') {
153
			ERROR("\"%s\" specified for %s at line %i is not a "
Avuton Olrich's avatar
Avuton Olrich committed
154 155 156
			      "positive integer\n",
			      param->value, CONF_HTTP_PREBUFFER_SIZE,
			      param->line);
157 158 159 160 161
			exit(EXIT_FAILURE);
		}

		prebufferSize *= 1024;
	}
162

Avuton Olrich's avatar
Avuton Olrich committed
163 164
	if (prebufferSize > bufferSize)
		prebufferSize = bufferSize;
165 166
}

167 168 169 170
/* base64 code taken from xmms */

#define BASE64_LENGTH(len) (4 * (((len) + 2) / 3))

Avuton Olrich's avatar
Avuton Olrich committed
171 172
static char *base64Dup(char *s)
{
173 174
	int i;
	int len = strlen(s);
Avuton Olrich's avatar
Avuton Olrich committed
175 176
	char *ret = calloc(BASE64_LENGTH(len) + 1, 1);
	unsigned char *p = (unsigned char *)ret;
177 178

	char tbl[64] = {
Avuton Olrich's avatar
Avuton Olrich committed
179 180 181 182 183 184 185 186
		'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
		'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
		'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
		'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
		'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
		'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
		'w', 'x', 'y', 'z', '0', '1', '2', '3',
		'4', '5', '6', '7', '8', '9', '+', '/'
187 188 189
	};

	/* Transform the 3x8 bits to 4x6 bits, as required by base64.  */
Avuton Olrich's avatar
Avuton Olrich committed
190
	for (i = 0; i < len; i += 3) {
191
		*p++ = tbl[s[0] >> 2];
Avuton Olrich's avatar
Avuton Olrich committed
192 193 194 195
		*p++ = tbl[((s[0] & 3) << 4) + (s[1] >> 4)];
		*p++ = tbl[((s[1] & 0xf) << 2) + (s[2] >> 6)];
		*p++ = tbl[s[2] & 0x3f];
		s += 3;
196 197
	}
	/* Pad the result if necessary...  */
Avuton Olrich's avatar
Avuton Olrich committed
198 199 200 201 202 203
	if (i == len + 1)
		*(p - 1) = '=';
	else if (i == len + 2)
		*(p - 1) = *(p - 2) = '=';
	/* ...and zero-terminate it.  */
	*p = '\0';
204 205 206 207

	return ret;
}

Avuton Olrich's avatar
Avuton Olrich committed
208 209 210
static char *authString(char *header, char *user, char *password)
{
	char *ret = NULL;
211
	int templen;
Avuton Olrich's avatar
Avuton Olrich committed
212 213
	char *temp;
	char *temp64;
214

Avuton Olrich's avatar
Avuton Olrich committed
215 216
	if (!user || !password)
		return NULL;
217 218

	templen = strlen(user) + strlen(password) + 2;
Warren Dukes's avatar
Warren Dukes committed
219 220 221 222
	temp = malloc(templen);
	strcpy(temp, user);
	strcat(temp, ":");
	strcat(temp, password);
223 224 225
	temp64 = base64Dup(temp);
	free(temp);

Avuton Olrich's avatar
Avuton Olrich committed
226
	ret = malloc(strlen(temp64) + strlen(header) + 3);
227
	strcpy(ret, header);
228 229 230 231 232 233 234
	strcat(ret, temp64);
	strcat(ret, "\r\n");
	free(temp64);

	return ret;
}

235 236 237 238 239 240
#define PROXY_AUTH_HEADER	"Proxy-Authorization: Basic "
#define HTTP_AUTH_HEADER	"Authorization: Basic "

#define proxyAuthString(x, y)	authString(PROXY_AUTH_HEADER, x, y)
#define httpAuthString(x, y)	authString(HTTP_AUTH_HEADER, x, y)

Avuton Olrich's avatar
Avuton Olrich committed
241 242 243
static InputStreamHTTPData *newInputStreamHTTPData(void)
{
	InputStreamHTTPData *ret = malloc(sizeof(InputStreamHTTPData));
Warren Dukes's avatar
Warren Dukes committed
244

Avuton Olrich's avatar
Avuton Olrich committed
245
	if (proxyHost) {
246
		ret->proxyAuth = proxyAuthString(proxyUser, proxyPassword);
Avuton Olrich's avatar
Avuton Olrich committed
247 248
	} else
		ret->proxyAuth = NULL;
249

250
	ret->httpAuth = NULL;
251
	ret->host = NULL;
Avuton Olrich's avatar
Avuton Olrich committed
252
	ret->path = NULL;
253
	ret->port = NULL;
Avuton Olrich's avatar
Avuton Olrich committed
254 255 256
	ret->connState = HTTP_CONN_STATE_CLOSED;
	ret->timesRedirected = 0;
	ret->icyMetaint = 0;
257
	ret->prebuffer = 0;
Warren Dukes's avatar
Warren Dukes committed
258
	ret->icyOffset = 0;
259
	ret->buffer = malloc(bufferSize);
Warren Dukes's avatar
Warren Dukes committed
260

Avuton Olrich's avatar
Avuton Olrich committed
261
	return ret;
Warren Dukes's avatar
Warren Dukes committed
262 263
}

Avuton Olrich's avatar
Avuton Olrich committed
264 265 266 267 268 269 270 271 272 273 274 275
static void freeInputStreamHTTPData(InputStreamHTTPData * data)
{
	if (data->host)
		free(data->host);
	if (data->path)
		free(data->path);
	if (data->port)
		free(data->port);
	if (data->proxyAuth)
		free(data->proxyAuth);
	if (data->httpAuth)
		free(data->httpAuth);
Warren Dukes's avatar
Warren Dukes committed
276

277 278
	free(data->buffer);

Avuton Olrich's avatar
Avuton Olrich committed
279
	free(data);
Warren Dukes's avatar
Warren Dukes committed
280 281
}

Avuton Olrich's avatar
Avuton Olrich committed
282 283 284 285 286 287 288
static int parseUrl(InputStreamHTTPData * data, char *url)
{
	char *temp;
	char *colon;
	char *slash;
	char *at;
	int len;
Warren Dukes's avatar
Warren Dukes committed
289

Avuton Olrich's avatar
Avuton Olrich committed
290 291
	if (strncmp("http://", url, strlen("http://")) != 0)
		return -1;
Warren Dukes's avatar
Warren Dukes committed
292

Avuton Olrich's avatar
Avuton Olrich committed
293
	temp = url + strlen("http://");
Warren Dukes's avatar
Warren Dukes committed
294

Avuton Olrich's avatar
Avuton Olrich committed
295
	colon = strchr(temp, ':');
296 297
	at = strchr(temp, '@');

Avuton Olrich's avatar
Avuton Olrich committed
298
	if (data->httpAuth) {
299 300 301 302
		free(data->httpAuth);
		data->httpAuth = NULL;
	}

Avuton Olrich's avatar
Avuton Olrich committed
303 304 305
	if (at) {
		char *user;
		char *passwd;
306

Avuton Olrich's avatar
Avuton Olrich committed
307 308 309 310 311 312 313 314 315 316 317 318
		if (colon && colon < at) {
			user = malloc(colon - temp + 1);
			strncpy(user, temp, colon - temp);
			user[colon - temp] = '\0';

			passwd = malloc(at - colon);
			strncpy(passwd, colon + 1, at - colon - 1);
			passwd[at - colon - 1] = '\0';
		} else {
			user = malloc(at - temp + 1);
			strncpy(user, temp, at - temp);
			user[at - temp] = '\0';
319 320 321 322 323 324 325 326 327

			passwd = strdup("");
		}

		data->httpAuth = httpAuthString(user, passwd);

		free(user);
		free(passwd);

Avuton Olrich's avatar
Avuton Olrich committed
328 329
		temp = at + 1;
		colon = strchr(temp, ':');
330 331
	}

Avuton Olrich's avatar
Avuton Olrich committed
332
	slash = strchr(temp, '/');
Warren Dukes's avatar
Warren Dukes committed
333

Avuton Olrich's avatar
Avuton Olrich committed
334 335
	if (slash && colon && slash <= colon)
		return -1;
Warren Dukes's avatar
Warren Dukes committed
336

Avuton Olrich's avatar
Avuton Olrich committed
337 338 339 340 341 342 343
	/* fetch the host portion */
	if (colon)
		len = colon - temp + 1;
	else if (slash)
		len = slash - temp + 1;
	else
		len = strlen(temp) + 1;
Warren Dukes's avatar
Warren Dukes committed
344

Avuton Olrich's avatar
Avuton Olrich committed
345 346
	if (len <= 1)
		return -1;
Warren Dukes's avatar
Warren Dukes committed
347

Avuton Olrich's avatar
Avuton Olrich committed
348 349 350 351 352 353 354 355 356 357 358
	data->host = malloc(len);
	strncpy(data->host, temp, len - 1);
	data->host[len - 1] = '\0';
	/* fetch the port */
	if (colon && (!slash || slash != colon + 1)) {
		len = strlen(colon) - 1;
		if (slash)
			len -= strlen(slash);
		data->port = malloc(len + 1);
		strncpy(data->port, colon + 1, len);
		data->port[len] = '\0';
359
		DEBUG(__FILE__ ": Port: %s\n", data->port);
Avuton Olrich's avatar
Avuton Olrich committed
360
	} else {
361 362
		data->port = strdup("80");
	}
Warren Dukes's avatar
Warren Dukes committed
363

Avuton Olrich's avatar
Avuton Olrich committed
364 365 366 367 368 369 370
	/* fetch the path */
	if (proxyHost)
		data->path = strdup(url);
	else
		data->path = strdup(slash ? slash : "/");

	return 0;
Warren Dukes's avatar
Warren Dukes committed
371 372
}

Avuton Olrich's avatar
Avuton Olrich committed
373 374
static int initHTTPConnection(InputStream * inStream)
{
375 376 377 378 379 380
	char *connHost;
	char *connPort;
	struct addrinfo *ans = NULL;
	struct addrinfo *ap = NULL;
	struct addrinfo hints;
	int error, flags;
Avuton Olrich's avatar
Avuton Olrich committed
381
	InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data;
382 383 384
	/**
	 * Setup hints
	 */
Avuton Olrich's avatar
Avuton Olrich committed
385 386 387 388 389 390 391 392 393 394
	hints.ai_flags = 0;
	hints.ai_family = PF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	hints.ai_addrlen = 0;
	hints.ai_addr = NULL;
	hints.ai_canonname = NULL;
	hints.ai_next = NULL;

	if (proxyHost) {
395 396
		connHost = proxyHost;
		connPort = proxyPort;
Avuton Olrich's avatar
Avuton Olrich committed
397
	} else {
398 399 400 401
		connHost = data->host;
		connPort = data->port;
	}

402
	error = getaddrinfo(connHost, connPort, &hints, &ans);
Avuton Olrich's avatar
Avuton Olrich committed
403 404 405
	if (error) {
		DEBUG(__FILE__ ": Error getting address info: %s\n",
		      gai_strerror(error));
406 407
		return -1;
	}
Avuton Olrich's avatar
Avuton Olrich committed
408

409
	/* loop through possible addresses */
Avuton Olrich's avatar
Avuton Olrich committed
410 411 412 413 414
	for (ap = ans; ap != NULL; ap = ap->ai_next) {
		if ((data->sock = socket(ap->ai_family, ap->ai_socktype,
					 ap->ai_protocol)) < 0) {
			DEBUG(__FILE__ ": unable to connect: %s\n",
			      strerror(errno));
415 416 417
			freeaddrinfo(ans);
			return -1;
		}
Avuton Olrich's avatar
Avuton Olrich committed
418

419 420
		flags = fcntl(data->sock, F_GETFL, 0);
		fcntl(data->sock, F_SETFL, flags | O_NONBLOCK);
Avuton Olrich's avatar
Avuton Olrich committed
421 422 423

		if (connect(data->sock, ap->ai_addr, ap->ai_addrlen) >= 0
		    || errno == EINPROGRESS) {
424 425 426
			data->connState = HTTP_CONN_STATE_INIT;
			data->buflen = 0;
			freeaddrinfo(ans);
Avuton Olrich's avatar
Avuton Olrich committed
427
			return 0;	/* success */
428
		}
Avuton Olrich's avatar
Avuton Olrich committed
429

430
		/* failed, get the next one */
Avuton Olrich's avatar
Avuton Olrich committed
431

432
		DEBUG(__FILE__ ": unable to connect: %s\n", strerror(errno));
Avuton Olrich's avatar
Avuton Olrich committed
433 434
		close(data->sock);
	}
Warren Dukes's avatar
Warren Dukes committed
435

436
	freeaddrinfo(ans);
Avuton Olrich's avatar
Avuton Olrich committed
437
	return -1;	/* failed */
Warren Dukes's avatar
Warren Dukes committed
438 439
}

Avuton Olrich's avatar
Avuton Olrich committed
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
static int finishHTTPInit(InputStream * inStream)
{
	InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data;
	struct timeval tv;
	fd_set writeSet;
	fd_set errorSet;
	int error;
	socklen_t error_len = sizeof(int);
	int ret;
	char request[2049];

	tv.tv_sec = 0;
	tv.tv_usec = 0;

	FD_ZERO(&writeSet);
	FD_ZERO(&errorSet);
	FD_SET(data->sock, &writeSet);
	FD_SET(data->sock, &errorSet);

	ret = select(data->sock + 1, NULL, &writeSet, &errorSet, &tv);

	if (ret == 0 || (ret < 0 && errno == EINTR))
		return 0;

	if (ret < 0) {
		DEBUG(__FILE__ ": problem select'ing: %s\n", strerror(errno));
		close(data->sock);
		data->connState = HTTP_CONN_STATE_CLOSED;
		return -1;
	}

	getsockopt(data->sock, SOL_SOCKET, SO_ERROR, &error, &error_len);
	if (error) {
		close(data->sock);
		data->connState = HTTP_CONN_STATE_CLOSED;
		return -1;
	}

	memset(request, 0, 2049);
479
	/* deal with ICY metadata later, for now its fucking up stuff! */
Avuton Olrich's avatar
Avuton Olrich committed
480 481 482 483
	snprintf(request, 2048, "GET %s HTTP/1.0\r\n" "Host: %s\r\n"
		 /*"Connection: close\r\n" */
		 "User-Agent: %s/%s\r\n"
		 /*"Range: bytes=%ld-\r\n" */
Avuton Olrich's avatar
Avuton Olrich committed
484
		 "%s"	/* authorization */
Avuton Olrich's avatar
Avuton Olrich committed
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
		 "Icy-Metadata:1\r\n"
		 "\r\n", data->path, data->host, PACKAGE_NAME, PACKAGE_VERSION,
		 /*inStream->offset, */
		 data->proxyAuth ? data->proxyAuth :
		 (data->httpAuth ? data->httpAuth : "")
	    );

	ret = write(data->sock, request, strlen(request));
	if (ret != strlen(request)) {
		close(data->sock);
		data->connState = HTTP_CONN_STATE_CLOSED;
		return -1;
	}

	data->connState = HTTP_CONN_STATE_HELLO;

	return 0;
Warren Dukes's avatar
Warren Dukes committed
502 503
}

Avuton Olrich's avatar
Avuton Olrich committed
504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609
static int getHTTPHello(InputStream * inStream)
{
	InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data;
	fd_set readSet;
	struct timeval tv;
	int ret;
	char *needle;
	char *cur = data->buffer;
	int rc;
	long readed;

	FD_ZERO(&readSet);
	FD_SET(data->sock, &readSet);

	tv.tv_sec = 0;
	tv.tv_usec = 0;

	ret = select(data->sock + 1, &readSet, NULL, NULL, &tv);

	if (ret == 0 || (ret < 0 && errno == EINTR))
		return 0;

	if (ret < 0) {
		data->connState = HTTP_CONN_STATE_CLOSED;
		close(data->sock);
		data->buflen = 0;
		return -1;
	}

	if (data->buflen >= bufferSize - 1) {
		data->connState = HTTP_CONN_STATE_CLOSED;
		close(data->sock);
		return -1;
	}

	readed = recv(data->sock, data->buffer + data->buflen,
		      bufferSize - 1 - data->buflen, 0);

	if (readed < 0 && (errno == EAGAIN || errno == EINTR))
		return 0;

	if (readed <= 0) {
		data->connState = HTTP_CONN_STATE_CLOSED;
		close(data->sock);
		data->buflen = 0;
		return -1;
	}

	data->buffer[data->buflen + readed] = '\0';
	data->buflen += readed;

	needle = strstr(data->buffer, "\r\n\r\n");

	if (!needle)
		return 0;

	if (0 == strncmp(cur, "HTTP/1.0 ", 9)) {
		inStream->seekable = 0;
		rc = atoi(cur + 9);
	} else if (0 == strncmp(cur, "HTTP/1.1 ", 9)) {
		inStream->seekable = 1;
		rc = atoi(cur + 9);
	} else if (0 == strncmp(cur, "ICY 200 OK", 10)) {
		inStream->seekable = 0;
		rc = 200;
	} else if (0 == strncmp(cur, "ICY 400 Server Full", 19))
		rc = 400;
	else if (0 == strncmp(cur, "ICY 404", 7))
		rc = 404;
	else {
		close(data->sock);
		data->connState = HTTP_CONN_STATE_CLOSED;
		return -1;
	}

	switch (rc) {
	case 200:
	case 206:
		break;
	case 301:
	case 302:
		cur = strstr(cur, "Location: ");
		if (cur) {
			char *url;
			int curlen = 0;
			cur += strlen("Location: ");
			while (*(cur + curlen) != '\0'
			       && *(cur + curlen) != '\r') {
				curlen++;
			}
			url = malloc(curlen + 1);
			memcpy(url, cur, curlen);
			url[curlen] = '\0';
			ret = parseUrl(data, url);
			free(url);
			if (ret == 0 && data->timesRedirected <
			    HTTP_REDIRECT_MAX) {
				data->timesRedirected++;
				close(data->sock);
				data->connState = HTTP_CONN_STATE_REOPEN;
				data->buflen = 0;
				return 0;
			}
		}
	case 400:
	case 401:
610
	case 403:
Avuton Olrich's avatar
Avuton Olrich committed
611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674
	case 404:
	default:
		close(data->sock);
		data->connState = HTTP_CONN_STATE_CLOSED;
		data->buflen = 0;
		return -1;
	}

	cur = strstr(data->buffer, "\r\n");
	while (cur && cur != needle) {
		if (0 == strncmp(cur, "\r\nContent-Length: ", 18)) {
			if (!inStream->size)
				inStream->size = atol(cur + 18);
		} else if (0 == strncmp(cur, "\r\nicy-metaint:", 14)) {
			data->icyMetaint = atoi(cur + 14);
		} else if (0 == strncmp(cur, "\r\nicy-name:", 11) ||
			   0 == strncmp(cur, "\r\nice-name:", 11)) {
			int incr = 11;
			char *temp = strstr(cur + incr, "\r\n");
			if (!temp)
				break;
			*temp = '\0';
			if (inStream->metaName)
				free(inStream->metaName);
			while (*(incr + cur) == ' ')
				incr++;
			inStream->metaName = strdup(cur + incr);
			*temp = '\r';
			DEBUG("inputStream_http: metaName: %s\n",
			      inStream->metaName);
		} else if (0 == strncmp(cur, "\r\nx-audiocast-name:", 19)) {
			int incr = 19;
			char *temp = strstr(cur + incr, "\r\n");
			if (!temp)
				break;
			*temp = '\0';
			if (inStream->metaName)
				free(inStream->metaName);
			while (*(incr + cur) == ' ')
				incr++;
			inStream->metaName = strdup(cur + incr);
			*temp = '\r';
			DEBUG("inputStream_http: metaName: %s\n",
			      inStream->metaName);
		} else if (0 == strncmp(cur, "\r\nContent-Type:", 15)) {
			int incr = 15;
			char *temp = strstr(cur + incr, "\r\n");
			if (!temp)
				break;
			*temp = '\0';
			if (inStream->mime)
				free(inStream->mime);
			while (*(incr + cur) == ' ')
				incr++;
			inStream->mime = strdup(cur + incr);
			*temp = '\r';
		}

		cur = strstr(cur + 2, "\r\n");
	}

	if (inStream->size <= 0)
		inStream->seekable = 0;

Avuton Olrich's avatar
Avuton Olrich committed
675
	needle += 4;	/* 4 == strlen("\r\n\r\n") */
Avuton Olrich's avatar
Avuton Olrich committed
676 677 678 679 680
	data->buflen -= (needle - data->buffer);
	/*fwrite(data->buffer, 1, data->buflen, stdout); */
	memmove(data->buffer, needle, data->buflen);

	data->connState = HTTP_CONN_STATE_OPEN;
Warren Dukes's avatar
Warren Dukes committed
681

Warren Dukes's avatar
Warren Dukes committed
682
	data->prebuffer = 1;
683

Avuton Olrich's avatar
Avuton Olrich committed
684
	/*mark as unseekable till we actually implement seeking */
Warren Dukes's avatar
Warren Dukes committed
685 686
	inStream->seekable = 0;

Avuton Olrich's avatar
Avuton Olrich committed
687
	return 0;
Warren Dukes's avatar
Warren Dukes committed
688 689
}

Avuton Olrich's avatar
Avuton Olrich committed
690 691 692
int inputStream_httpOpen(InputStream * inStream, char *url)
{
	InputStreamHTTPData *data = newInputStreamHTTPData();
Warren Dukes's avatar
Warren Dukes committed
693

Avuton Olrich's avatar
Avuton Olrich committed
694
	inStream->data = data;
Warren Dukes's avatar
Warren Dukes committed
695

Avuton Olrich's avatar
Avuton Olrich committed
696 697 698 699
	if (parseUrl(data, url) < 0) {
		freeInputStreamHTTPData(data);
		return -1;
	}
Warren Dukes's avatar
Warren Dukes committed
700

Avuton Olrich's avatar
Avuton Olrich committed
701 702 703 704
	if (initHTTPConnection(inStream) < 0) {
		freeInputStreamHTTPData(data);
		return -1;
	}
Warren Dukes's avatar
Warren Dukes committed
705

Avuton Olrich's avatar
Avuton Olrich committed
706 707 708 709 710
	inStream->seekFunc = inputStream_httpSeek;
	inStream->closeFunc = inputStream_httpClose;
	inStream->readFunc = inputStream_httpRead;
	inStream->atEOFFunc = inputStream_httpAtEOF;
	inStream->bufferFunc = inputStream_httpBuffer;
Warren Dukes's avatar
Warren Dukes committed
711 712 713 714

	return 0;
}

Avuton Olrich's avatar
Avuton Olrich committed
715 716
int inputStream_httpSeek(InputStream * inStream, long offset, int whence)
{
717 718 719
	/* hack to reopen an HTTP stream if we're trying to seek to
	 * the beginning */
	if ((whence == SEEK_SET) && (offset == 0)) {
Avuton Olrich's avatar
Avuton Olrich committed
720
		InputStreamHTTPData *data;
721

Avuton Olrich's avatar
Avuton Olrich committed
722
		data = (InputStreamHTTPData *) inStream->data;
723 724 725 726 727 728
		close(data->sock);
		data->connState = HTTP_CONN_STATE_REOPEN;
		data->buflen = 0;
		inStream->offset = 0;
		return 0;
	}
Avuton Olrich's avatar
Avuton Olrich committed
729

730
	/* otherwise, we don't know how to seek in HTTP yet */
Warren Dukes's avatar
Warren Dukes committed
731 732 733
	return -1;
}

Avuton Olrich's avatar
Avuton Olrich committed
734
static void parseIcyMetadata(InputStream * inStream, char *metadata, int size)
Warren Dukes's avatar
Warren Dukes committed
735
{
Avuton Olrich's avatar
Avuton Olrich committed
736 737 738
	char *r;
	char *s;
	char *temp = malloc(size + 1);
Warren Dukes's avatar
Warren Dukes committed
739 740 741
	memcpy(temp, metadata, size);
	temp[size] = '\0';
	s = strtok_r(temp, ";", &r);
Avuton Olrich's avatar
Avuton Olrich committed
742 743
	while (s) {
		if (0 == strncmp(s, "StreamTitle=", 12)) {
744
			int cur = 12;
Avuton Olrich's avatar
Avuton Olrich committed
745 746 747 748 749 750
			if (inStream->metaTitle)
				free(inStream->metaTitle);
			if (*(s + cur) == '\'')
				cur++;
			if (s[strlen(s) - 1] == '\'') {
				s[strlen(s) - 1] = '\0';
751
			}
Avuton Olrich's avatar
Avuton Olrich committed
752 753 754
			inStream->metaTitle = strdup(s + cur);
			DEBUG("inputStream_http: metaTitle: %s\n",
			      inStream->metaTitle);
Warren Dukes's avatar
Warren Dukes committed
755 756 757 758 759 760
		}
		s = strtok_r(NULL, ";", &r);
	}
	free(temp);
}

Avuton Olrich's avatar
Avuton Olrich committed
761 762
size_t inputStream_httpRead(InputStream * inStream, void *ptr, size_t size,
			    size_t nmemb)
Warren Dukes's avatar
Warren Dukes committed
763
{
Avuton Olrich's avatar
Avuton Olrich committed
764 765 766
	InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data;
	long tosend = 0;
	long inlen = size * nmemb;
Warren Dukes's avatar
Warren Dukes committed
767
	long maxToSend = data->buflen;
768

Avuton Olrich's avatar
Avuton Olrich committed
769
	inputStream_httpBuffer(inStream);
770

Avuton Olrich's avatar
Avuton Olrich committed
771 772 773 774
	switch (data->connState) {
	case HTTP_CONN_STATE_OPEN:
		if (data->prebuffer || data->buflen < data->icyMetaint)
			return 0;
775 776

		break;
Avuton Olrich's avatar
Avuton Olrich committed
777 778 779 780 781 782 783 784 785
	case HTTP_CONN_STATE_CLOSED:
		if (data->buflen)
			break;
	default:
		return 0;
	}

	if (data->icyMetaint > 0) {
		if (data->icyOffset >= data->icyMetaint) {
Warren Dukes's avatar
Warren Dukes committed
786 787
			int metalen = *(data->buffer);
			metalen <<= 4;
Avuton Olrich's avatar
Avuton Olrich committed
788 789 790
			if (metalen < 0)
				metalen = 0;
			if (metalen + 1 > data->buflen) {
Warren Dukes's avatar
Warren Dukes committed
791
				/* damn that's some fucking big metadata! */
Avuton Olrich's avatar
Avuton Olrich committed
792 793 794 795
				if (bufferSize < metalen + 1) {
					data->connState =
					    HTTP_CONN_STATE_CLOSED;
					close(data->sock);
Warren Dukes's avatar
Warren Dukes committed
796 797 798 799
					data->buflen = 0;
				}
				return 0;
			}
Avuton Olrich's avatar
Avuton Olrich committed
800 801 802
			if (metalen > 0) {
				parseIcyMetadata(inStream, data->buffer + 1,
						 metalen);
Warren Dukes's avatar
Warren Dukes committed
803
			}
Avuton Olrich's avatar
Avuton Olrich committed
804 805 806
			data->buflen -= metalen + 1;
			memmove(data->buffer, data->buffer + metalen + 1,
				data->buflen);
Warren Dukes's avatar
Warren Dukes committed
807 808
			data->icyOffset = 0;
		}
Avuton Olrich's avatar
Avuton Olrich committed
809
		maxToSend = data->icyMetaint - data->icyOffset;
Warren Dukes's avatar
Warren Dukes committed
810 811
		maxToSend = maxToSend > data->buflen ? data->buflen : maxToSend;
	}
812

Avuton Olrich's avatar
Avuton Olrich committed
813 814 815
	if (data->buflen > 0) {
		tosend = inlen > maxToSend ? maxToSend : inlen;
		tosend = (tosend / size) * size;
816

Avuton Olrich's avatar
Avuton Olrich committed
817 818 819 820 821 822 823 824 825
		memcpy(ptr, data->buffer, tosend);
		/*fwrite(ptr,1,readed,stdout); */
		data->buflen -= tosend;
		data->icyOffset += tosend;
		/*fwrite(data->buffer,1,readed,stdout); */
		memmove(data->buffer, data->buffer + tosend, data->buflen);

		inStream->offset += tosend;
	}
826

Avuton Olrich's avatar
Avuton Olrich committed
827
	return tosend / size;
828 829
}

Avuton Olrich's avatar
Avuton Olrich committed
830 831 832
int inputStream_httpClose(InputStream * inStream)
{
	InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data;
833

Avuton Olrich's avatar
Avuton Olrich committed
834 835 836 837 838 839
	switch (data->connState) {
	case HTTP_CONN_STATE_CLOSED:
		break;
	default:
		close(data->sock);
	}
840

Avuton Olrich's avatar
Avuton Olrich committed
841
	freeInputStreamHTTPData(data);
842

Avuton Olrich's avatar
Avuton Olrich committed
843
	return 0;
844 845
}

Avuton Olrich's avatar
Avuton Olrich committed
846 847 848 849 850 851 852 853 854 855
int inputStream_httpAtEOF(InputStream * inStream)
{
	InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data;
	switch (data->connState) {
	case HTTP_CONN_STATE_CLOSED:
		if (data->buflen == 0)
			return 1;
	default:
		return 0;
	}
856 857
}

Avuton Olrich's avatar
Avuton Olrich committed
858 859 860 861
int inputStream_httpBuffer(InputStream * inStream)
{
	InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data;
	ssize_t readed = 0;
Warren Dukes's avatar
Warren Dukes committed
862

Avuton Olrich's avatar
Avuton Olrich committed
863 864 865 866
	if (data->connState == HTTP_CONN_STATE_REOPEN) {
		if (initHTTPConnection(inStream) < 0)
			return -1;
	}
Warren Dukes's avatar
Warren Dukes committed
867

Avuton Olrich's avatar
Avuton Olrich committed
868 869 870 871
	if (data->connState == HTTP_CONN_STATE_INIT) {
		if (finishHTTPInit(inStream) < 0)
			return -1;
	}
Warren Dukes's avatar
Warren Dukes committed
872

Avuton Olrich's avatar
Avuton Olrich committed
873 874 875 876
	if (data->connState == HTTP_CONN_STATE_HELLO) {
		if (getHTTPHello(inStream) < 0)
			return -1;
	}
Warren Dukes's avatar
Warren Dukes committed
877

Avuton Olrich's avatar
Avuton Olrich committed
878 879 880 881 882 883 884
	switch (data->connState) {
	case HTTP_CONN_STATE_OPEN:
	case HTTP_CONN_STATE_CLOSED:
		break;
	default:
		return -1;
	}
Warren Dukes's avatar
Warren Dukes committed
885

Avuton Olrich's avatar
Avuton Olrich committed
886
	if (data->buflen == 0 || data->buflen < data->icyMetaint) {
Warren Dukes's avatar
Warren Dukes committed
887
		data->prebuffer = 1;
Avuton Olrich's avatar
Avuton Olrich committed
888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903
	} else if (data->buflen > prebufferSize)
		data->prebuffer = 0;

	if (data->connState == HTTP_CONN_STATE_OPEN &&
	    data->buflen < bufferSize - 1) {
		readed = read(data->sock, data->buffer + data->buflen,
			      (size_t) (bufferSize - 1 - data->buflen));

		if (readed < 0 && (errno == EAGAIN || errno == EINTR)) {
			readed = 0;
		} else if (readed <= 0) {
			close(data->sock);
			data->connState = HTTP_CONN_STATE_CLOSED;
			readed = 0;
		}
		/*fwrite(data->buffer+data->buflen,1,readed,stdout); */
904
		data->buflen += readed;
Avuton Olrich's avatar
Avuton Olrich committed
905
	}
Warren Dukes's avatar
Warren Dukes committed
906

Avuton Olrich's avatar
Avuton Olrich committed
907 908
	if (data->buflen > prebufferSize)
		data->prebuffer = 0;
909

Avuton Olrich's avatar
Avuton Olrich committed
910
	return (readed ? 1 : 0);
Warren Dukes's avatar
Warren Dukes committed
911
}