1 /* the Music Player Daemon (MPD)
2 * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
3 * This project's homepage is: http://www.musicpd.org
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include "inputStream_http.h"
27 #include <sys/types.h>
28 #include <sys/socket.h>
30 #include <sys/param.h>
31 #include <netinet/in.h>
32 #include <arpa/inet.h>
39 #define HTTP_CONN_STATE_CLOSED 0
40 #define HTTP_CONN_STATE_INIT 1
41 #define HTTP_CONN_STATE_HELLO 2
42 #define HTTP_CONN_STATE_OPEN 3
43 #define HTTP_CONN_STATE_REOPEN 4
45 #define HTTP_BUFFER_SIZE_DEFAULT 131072
46 #define HTTP_PREBUFFER_SIZE_DEFAULT (HTTP_BUFFER_SIZE_DEFAULT >> 2)
48 #define HTTP_REDIRECT_MAX 10
50 static char *proxyHost
;
51 static char *proxyPort
;
52 static char *proxyUser
;
53 static char *proxyPassword
;
54 static int bufferSize
= HTTP_BUFFER_SIZE_DEFAULT
;
55 static int prebufferSize
= HTTP_PREBUFFER_SIZE_DEFAULT
;
57 typedef struct _InputStreemHTTPData
{
71 } InputStreamHTTPData
;
73 void inputStream_initHttp(void)
75 ConfigParam
*param
= getConfigParam(CONF_HTTP_PROXY_HOST
);
79 proxyHost
= param
->value
;
81 param
= getConfigParam(CONF_HTTP_PROXY_PORT
);
84 FATAL("%s specified but not %s", CONF_HTTP_PROXY_HOST
,
85 CONF_HTTP_PROXY_PORT
);
87 proxyPort
= param
->value
;
89 param
= getConfigParam(CONF_HTTP_PROXY_USER
);
92 proxyUser
= param
->value
;
94 param
= getConfigParam(CONF_HTTP_PROXY_PASSWORD
);
97 FATAL("%s specified but not %s\n",
99 CONF_HTTP_PROXY_PASSWORD
);
102 proxyPassword
= param
->value
;
104 param
= getConfigParam(CONF_HTTP_PROXY_PASSWORD
);
107 FATAL("%s specified but not %s\n",
108 CONF_HTTP_PROXY_PASSWORD
, CONF_HTTP_PROXY_USER
);
111 } else if ((param
= getConfigParam(CONF_HTTP_PROXY_PORT
))) {
112 FATAL("%s specified but not %s, line %i\n",
113 CONF_HTTP_PROXY_PORT
, CONF_HTTP_PROXY_HOST
, param
->line
);
114 } else if ((param
= getConfigParam(CONF_HTTP_PROXY_USER
))) {
115 FATAL("%s specified but not %s, line %i\n",
116 CONF_HTTP_PROXY_USER
, CONF_HTTP_PROXY_HOST
, param
->line
);
117 } else if ((param
= getConfigParam(CONF_HTTP_PROXY_PASSWORD
))) {
118 FATAL("%s specified but not %s, line %i\n",
119 CONF_HTTP_PROXY_PASSWORD
, CONF_HTTP_PROXY_HOST
,
123 param
= getConfigParam(CONF_HTTP_BUFFER_SIZE
);
126 bufferSize
= strtol(param
->value
, &test
, 10);
128 if (bufferSize
<= 0 || *test
!= '\0') {
129 FATAL("\"%s\" specified for %s at line %i is not a "
130 "positive integer\n",
131 param
->value
, CONF_HTTP_BUFFER_SIZE
, param
->line
);
136 if (prebufferSize
> bufferSize
)
137 prebufferSize
= bufferSize
;
140 param
= getConfigParam(CONF_HTTP_PREBUFFER_SIZE
);
143 prebufferSize
= strtol(param
->value
, &test
, 10);
145 if (prebufferSize
<= 0 || *test
!= '\0') {
146 FATAL("\"%s\" specified for %s at line %i is not a "
147 "positive integer\n",
148 param
->value
, CONF_HTTP_PREBUFFER_SIZE
,
152 prebufferSize
*= 1024;
155 if (prebufferSize
> bufferSize
)
156 prebufferSize
= bufferSize
;
159 /* base64 code taken from xmms */
161 #define BASE64_LENGTH(len) (4 * (((len) + 2) / 3))
163 static char *base64Dup(char *s
)
167 char *ret
= xcalloc(BASE64_LENGTH(len
) + 1, 1);
168 unsigned char *p
= (unsigned char *)ret
;
171 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
172 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
173 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
174 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
175 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
176 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
177 'w', 'x', 'y', 'z', '0', '1', '2', '3',
178 '4', '5', '6', '7', '8', '9', '+', '/'
181 /* Transform the 3x8 bits to 4x6 bits, as required by base64. */
182 for (i
= 0; i
< len
; i
+= 3) {
183 *p
++ = tbl
[s
[0] >> 2];
184 *p
++ = tbl
[((s
[0] & 3) << 4) + (s
[1] >> 4)];
185 *p
++ = tbl
[((s
[1] & 0xf) << 2) + (s
[2] >> 6)];
186 *p
++ = tbl
[s
[2] & 0x3f];
189 /* Pad the result if necessary... */
192 else if (i
== len
+ 2)
193 *(p
- 1) = *(p
- 2) = '=';
194 /* ...and zero-terminate it. */
200 static char *authString(char *header
, char *user
, char *password
)
207 if (!user
|| !password
)
210 templen
= strlen(user
) + strlen(password
) + 2;
211 temp
= xmalloc(templen
);
214 strcat(temp
, password
);
215 temp64
= base64Dup(temp
);
218 ret
= xmalloc(strlen(temp64
) + strlen(header
) + 3);
227 #define PROXY_AUTH_HEADER "Proxy-Authorization: Basic "
228 #define HTTP_AUTH_HEADER "Authorization: Basic "
230 #define proxyAuthString(x, y) authString(PROXY_AUTH_HEADER, x, y)
231 #define httpAuthString(x, y) authString(HTTP_AUTH_HEADER, x, y)
233 static InputStreamHTTPData
*newInputStreamHTTPData(void)
235 InputStreamHTTPData
*ret
= xmalloc(sizeof(InputStreamHTTPData
));
238 ret
->proxyAuth
= proxyAuthString(proxyUser
, proxyPassword
);
240 ret
->proxyAuth
= NULL
;
242 ret
->httpAuth
= NULL
;
246 ret
->connState
= HTTP_CONN_STATE_CLOSED
;
247 ret
->timesRedirected
= 0;
251 ret
->buffer
= xmalloc(bufferSize
);
256 static void freeInputStreamHTTPData(InputStreamHTTPData
* data
)
265 free(data
->proxyAuth
);
267 free(data
->httpAuth
);
274 static int parseUrl(InputStreamHTTPData
* data
, char *url
)
282 if (strncmp("http://", url
, strlen("http://")) != 0)
285 temp
= url
+ strlen("http://");
287 colon
= strchr(temp
, ':');
288 at
= strchr(temp
, '@');
290 if (data
->httpAuth
) {
291 free(data
->httpAuth
);
292 data
->httpAuth
= NULL
;
299 if (colon
&& colon
< at
) {
300 user
= xmalloc(colon
- temp
+ 1);
301 memcpy(user
, temp
, colon
- temp
);
302 user
[colon
- temp
] = '\0';
304 passwd
= xmalloc(at
- colon
);
305 memcpy(passwd
, colon
+ 1, at
- colon
- 1);
306 passwd
[at
- colon
- 1] = '\0';
308 user
= xmalloc(at
- temp
+ 1);
309 memcpy(user
, temp
, at
- temp
);
310 user
[at
- temp
] = '\0';
312 passwd
= xstrdup("");
315 data
->httpAuth
= httpAuthString(user
, passwd
);
321 colon
= strchr(temp
, ':');
324 slash
= strchr(temp
, '/');
326 if (slash
&& colon
&& slash
<= colon
)
329 /* fetch the host portion */
331 len
= colon
- temp
+ 1;
333 len
= slash
- temp
+ 1;
335 len
= strlen(temp
) + 1;
340 data
->host
= xmalloc(len
);
341 memcpy(data
->host
, temp
, len
- 1);
342 data
->host
[len
- 1] = '\0';
344 if (colon
&& (!slash
|| slash
!= colon
+ 1)) {
345 len
= strlen(colon
) - 1;
347 len
-= strlen(slash
);
348 data
->port
= xmalloc(len
+ 1);
349 memcpy(data
->port
, colon
+ 1, len
);
350 data
->port
[len
] = '\0';
351 DEBUG(__FILE__
": Port: %s\n", data
->port
);
353 data
->port
= xstrdup("80");
358 data
->path
= xstrdup(url
);
360 data
->path
= xstrdup(slash
? slash
: "/");
365 static int initHTTPConnection(InputStream
* inStream
)
369 struct addrinfo
*ans
= NULL
;
370 struct addrinfo
*ap
= NULL
;
371 struct addrinfo hints
;
373 InputStreamHTTPData
*data
= (InputStreamHTTPData
*) inStream
->data
;
378 hints
.ai_family
= PF_UNSPEC
;
379 hints
.ai_socktype
= SOCK_STREAM
;
380 hints
.ai_protocol
= IPPROTO_TCP
;
381 hints
.ai_addrlen
= 0;
382 hints
.ai_addr
= NULL
;
383 hints
.ai_canonname
= NULL
;
384 hints
.ai_next
= NULL
;
387 connHost
= proxyHost
;
388 connPort
= proxyPort
;
390 connHost
= data
->host
;
391 connPort
= data
->port
;
394 error
= getaddrinfo(connHost
, connPort
, &hints
, &ans
);
396 DEBUG(__FILE__
": Error getting address info: %s\n",
397 gai_strerror(error
));
401 /* loop through possible addresses */
402 for (ap
= ans
; ap
!= NULL
; ap
= ap
->ai_next
) {
403 if ((data
->sock
= socket(ap
->ai_family
, ap
->ai_socktype
,
404 ap
->ai_protocol
)) < 0) {
405 DEBUG(__FILE__
": unable to connect: %s\n",
411 flags
= fcntl(data
->sock
, F_GETFL
, 0);
412 fcntl(data
->sock
, F_SETFL
, flags
| O_NONBLOCK
);
414 if (connect(data
->sock
, ap
->ai_addr
, ap
->ai_addrlen
) >= 0
415 || errno
== EINPROGRESS
) {
416 data
->connState
= HTTP_CONN_STATE_INIT
;
419 return 0; /* success */
422 /* failed, get the next one */
424 DEBUG(__FILE__
": unable to connect: %s\n", strerror(errno
));
429 return -1; /* failed */
432 static int finishHTTPInit(InputStream
* inStream
)
434 InputStreamHTTPData
*data
= (InputStreamHTTPData
*) inStream
->data
;
439 socklen_t error_len
= sizeof(int);
449 FD_SET(data
->sock
, &writeSet
);
450 FD_SET(data
->sock
, &errorSet
);
452 ret
= select(data
->sock
+ 1, NULL
, &writeSet
, &errorSet
, &tv
);
454 if (ret
== 0 || (ret
< 0 && errno
== EINTR
))
458 DEBUG(__FILE__
": problem select'ing: %s\n", strerror(errno
));
462 getsockopt(data
->sock
, SOL_SOCKET
, SO_ERROR
, &error
, &error_len
);
466 /* deal with ICY metadata later, for now its fucking up stuff! */
467 length
= snprintf(request
, sizeof(request
),
468 "GET %s HTTP/1.1\r\n"
470 /* "Connection: close\r\n" */
471 "User-Agent: " PACKAGE_NAME
"/" PACKAGE_VERSION
"\r\n"
472 "Range: bytes=%ld-\r\n"
473 "%s" /* authorization */
479 data
->proxyAuth
? data
->proxyAuth
:
480 (data
->httpAuth
? data
->httpAuth
: ""));
482 if (length
>= sizeof(request
))
484 ret
= write(data
->sock
, request
, length
);
488 data
->connState
= HTTP_CONN_STATE_HELLO
;
493 data
->connState
= HTTP_CONN_STATE_CLOSED
;
497 static int getHTTPHello(InputStream
* inStream
)
499 InputStreamHTTPData
*data
= (InputStreamHTTPData
*) inStream
->data
;
504 char *cur
= data
->buffer
;
509 FD_SET(data
->sock
, &readSet
);
514 ret
= select(data
->sock
+ 1, &readSet
, NULL
, NULL
, &tv
);
516 if (ret
== 0 || (ret
< 0 && errno
== EINTR
))
520 data
->connState
= HTTP_CONN_STATE_CLOSED
;
526 if (data
->buflen
>= bufferSize
- 1) {
527 data
->connState
= HTTP_CONN_STATE_CLOSED
;
532 readed
= recv(data
->sock
, data
->buffer
+ data
->buflen
,
533 bufferSize
- 1 - data
->buflen
, 0);
535 if (readed
< 0 && (errno
== EAGAIN
|| errno
== EINTR
))
539 data
->connState
= HTTP_CONN_STATE_CLOSED
;
545 data
->buffer
[data
->buflen
+ readed
] = '\0';
546 data
->buflen
+= readed
;
548 needle
= strstr(data
->buffer
, "\r\n\r\n");
553 if (0 == strncmp(cur
, "HTTP/1.0 ", 9)) {
554 inStream
->seekable
= 0;
556 } else if (0 == strncmp(cur
, "HTTP/1.1 ", 9)) {
557 inStream
->seekable
= 1;
559 } else if (0 == strncmp(cur
, "ICY 200 OK", 10)) {
560 inStream
->seekable
= 0;
562 } else if (0 == strncmp(cur
, "ICY 400 Server Full", 19))
564 else if (0 == strncmp(cur
, "ICY 404", 7))
568 data
->connState
= HTTP_CONN_STATE_CLOSED
;
578 cur
= strstr(cur
, "Location: ");
582 cur
+= strlen("Location: ");
583 while (*(cur
+ curlen
) != '\0'
584 && *(cur
+ curlen
) != '\r') {
587 url
= xmalloc(curlen
+ 1);
588 memcpy(url
, cur
, curlen
);
590 ret
= parseUrl(data
, url
);
592 if (ret
== 0 && data
->timesRedirected
<
594 data
->timesRedirected
++;
596 data
->connState
= HTTP_CONN_STATE_REOPEN
;
607 data
->connState
= HTTP_CONN_STATE_CLOSED
;
612 cur
= strstr(data
->buffer
, "\r\n");
613 while (cur
&& cur
!= needle
) {
614 if (0 == strncasecmp(cur
, "\r\nContent-Length: ", 18)) {
616 inStream
->size
= atol(cur
+ 18);
617 } else if (0 == strncasecmp(cur
, "\r\nicy-metaint:", 14)) {
618 data
->icyMetaint
= atoi(cur
+ 14);
619 } else if (0 == strncasecmp(cur
, "\r\nicy-name:", 11) ||
620 0 == strncasecmp(cur
, "\r\nice-name:", 11)) {
622 char *temp
= strstr(cur
+ incr
, "\r\n");
626 if (inStream
->metaName
)
627 free(inStream
->metaName
);
628 while (*(incr
+ cur
) == ' ')
630 inStream
->metaName
= xstrdup(cur
+ incr
);
632 DEBUG("inputStream_http: metaName: %s\n",
634 } else if (0 == strncasecmp(cur
, "\r\nx-audiocast-name:", 19)) {
636 char *temp
= strstr(cur
+ incr
, "\r\n");
640 if (inStream
->metaName
)
641 free(inStream
->metaName
);
642 while (*(incr
+ cur
) == ' ')
644 inStream
->metaName
= xstrdup(cur
+ incr
);
646 DEBUG("inputStream_http: metaName: %s\n",
648 } else if (0 == strncasecmp(cur
, "\r\nContent-Type:", 15)) {
650 char *temp
= strstr(cur
+ incr
, "\r\n");
655 free(inStream
->mime
);
656 while (*(incr
+ cur
) == ' ')
658 inStream
->mime
= xstrdup(cur
+ incr
);
662 cur
= strstr(cur
+ 2, "\r\n");
665 if (inStream
->size
<= 0)
666 inStream
->seekable
= 0;
668 needle
+= 4; /* 4 == strlen("\r\n\r\n") */
669 data
->buflen
-= (needle
- data
->buffer
);
670 memmove(data
->buffer
, needle
, data
->buflen
);
672 data
->connState
= HTTP_CONN_STATE_OPEN
;
679 int inputStream_httpOpen(InputStream
* inStream
, char *url
)
681 InputStreamHTTPData
*data
= newInputStreamHTTPData();
683 inStream
->data
= data
;
685 if (parseUrl(data
, url
) < 0) {
686 freeInputStreamHTTPData(data
);
690 if (initHTTPConnection(inStream
) < 0) {
691 freeInputStreamHTTPData(data
);
695 inStream
->seekFunc
= inputStream_httpSeek
;
696 inStream
->closeFunc
= inputStream_httpClose
;
697 inStream
->readFunc
= inputStream_httpRead
;
698 inStream
->atEOFFunc
= inputStream_httpAtEOF
;
699 inStream
->bufferFunc
= inputStream_httpBuffer
;
701 while (!inputStream_httpAtEOF(inStream
)) {
702 if (inputStream_httpBuffer(inStream
) >= 0) {
705 /* sleep so we don't consume 100% of the cpu */
709 freeInputStreamHTTPData(data
);
713 int inputStream_httpSeek(InputStream
* inStream
, long offset
, int whence
)
715 InputStreamHTTPData
*data
;
717 if (!inStream
->seekable
)
722 inStream
->offset
= offset
;
725 inStream
->offset
+= offset
;
728 inStream
->offset
= inStream
->size
+ offset
;
733 data
= (InputStreamHTTPData
*)inStream
->data
;
735 data
->connState
= HTTP_CONN_STATE_REOPEN
;
738 while (!inputStream_httpAtEOF(inStream
)) {
739 if (inputStream_httpBuffer(inStream
) >= 0) {
742 /* sleep so we don't consume 100% of the cpu */
749 static void parseIcyMetadata(InputStream
* inStream
, char *metadata
, int size
)
753 char *temp
= xmalloc(size
+ 1);
754 memcpy(temp
, metadata
, size
);
756 s
= strtok_r(temp
, ";", &r
);
758 if (0 == strncmp(s
, "StreamTitle=", 12)) {
760 if (inStream
->metaTitle
)
761 free(inStream
->metaTitle
);
762 if (*(s
+ cur
) == '\'')
764 if (s
[strlen(s
) - 1] == '\'') {
765 s
[strlen(s
) - 1] = '\0';
767 inStream
->metaTitle
= xstrdup(s
+ cur
);
768 DEBUG("inputStream_http: metaTitle: %s\n",
769 inStream
->metaTitle
);
771 s
= strtok_r(NULL
, ";", &r
);
777 * This stuff can only handle max bufsize sized blocks in the good case.
778 * Good case means the buffer is full.
780 static size_t inputStream_httpRead_(InputStream
* inStream
, void *ptr
,
781 size_t size
, size_t nmemb
)
783 InputStreamHTTPData
*data
= (InputStreamHTTPData
*) inStream
->data
;
785 long inlen
= size
* nmemb
;
786 long maxToSend
= data
->buflen
;
788 inputStream_httpBuffer(inStream
);
790 switch (data
->connState
) {
791 case HTTP_CONN_STATE_OPEN
:
792 if (data
->prebuffer
|| data
->buflen
< data
->icyMetaint
)
796 case HTTP_CONN_STATE_CLOSED
:
803 if (data
->icyMetaint
> 0) {
804 if (data
->icyOffset
>= data
->icyMetaint
) {
805 int metalen
= *(data
->buffer
);
809 if (metalen
+ 1 > data
->buflen
) {
810 /* damn that's some fucking big metadata! */
811 if (bufferSize
< metalen
+ 1) {
813 HTTP_CONN_STATE_CLOSED
;
820 parseIcyMetadata(inStream
, data
->buffer
+ 1,
823 data
->buflen
-= metalen
+ 1;
824 memmove(data
->buffer
, data
->buffer
+ metalen
+ 1,
828 maxToSend
= data
->icyMetaint
- data
->icyOffset
;
829 maxToSend
= maxToSend
> data
->buflen
? data
->buflen
: maxToSend
;
832 if (data
->buflen
> 0) {
833 tosend
= inlen
> maxToSend
? maxToSend
: inlen
;
834 tosend
= (tosend
/ size
) * size
;
836 memcpy(ptr
, data
->buffer
, tosend
);
837 data
->buflen
-= tosend
;
838 data
->icyOffset
+= tosend
;
839 memmove(data
->buffer
, data
->buffer
+ tosend
, data
->buflen
);
841 inStream
->offset
+= tosend
;
844 return tosend
/ size
;
847 /* wrapper for the previous function */
848 size_t inputStream_httpRead(InputStream
*inStream
, void *ptr
, size_t size
,
851 size_t req
= nmemb
* size
;
855 size_t r
= inputStream_httpRead_(inStream
, ptr
+ got
, 1, req
);
858 if (inputStream_httpAtEOF(inStream
))
865 int inputStream_httpClose(InputStream
* inStream
)
867 InputStreamHTTPData
*data
= (InputStreamHTTPData
*) inStream
->data
;
869 switch (data
->connState
) {
870 case HTTP_CONN_STATE_CLOSED
:
876 freeInputStreamHTTPData(data
);
881 int inputStream_httpAtEOF(InputStream
* inStream
)
883 InputStreamHTTPData
*data
= (InputStreamHTTPData
*) inStream
->data
;
884 switch (data
->connState
) {
885 case HTTP_CONN_STATE_CLOSED
:
886 if (data
->buflen
== 0)
893 int inputStream_httpBuffer(InputStream
* inStream
)
895 InputStreamHTTPData
*data
= (InputStreamHTTPData
*) inStream
->data
;
898 if (data
->connState
== HTTP_CONN_STATE_REOPEN
) {
899 if (initHTTPConnection(inStream
) < 0)
903 if (data
->connState
== HTTP_CONN_STATE_INIT
) {
904 if (finishHTTPInit(inStream
) < 0)
908 if (data
->connState
== HTTP_CONN_STATE_HELLO
) {
909 if (getHTTPHello(inStream
) < 0)
913 switch (data
->connState
) {
914 case HTTP_CONN_STATE_OPEN
:
915 case HTTP_CONN_STATE_CLOSED
:
921 if (data
->buflen
== 0 || data
->buflen
< data
->icyMetaint
) {
923 } else if (data
->buflen
> prebufferSize
)
926 if (data
->connState
== HTTP_CONN_STATE_OPEN
&&
927 data
->buflen
< bufferSize
- 1) {
928 readed
= read(data
->sock
, data
->buffer
+ data
->buflen
,
929 (size_t) (bufferSize
- 1 - data
->buflen
));
931 if (readed
< 0 && (errno
== EAGAIN
|| errno
== EINTR
)) {
933 } else if (readed
<= 0) {
935 data
->connState
= HTTP_CONN_STATE_CLOSED
;
938 data
->buflen
+= readed
;
941 if (data
->buflen
> prebufferSize
)
944 return (readed
? 1 : 0);