1 /*
2 * Copyright (C) 2009-2015 Christian Heckendorf <heckendorfc@gmail.com>
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "../plugin.h"
19 #include <sys/socket.h>
20 #include <sys/types.h>
21 #include <netdb.h>
22 #include <sys/select.h>
23
24 #define S_DEF_PORT_STR "80"
25
26 static struct streamHandles{
27 FILE *rfd;
28 FILE *wfd;
29 int sfd;
30 FILE *ffd;
31 struct pluginitem *dec;
32 int metaint;
33 int bytecount;
34 int print_meta;
35 volatile char go;
36 }h;
37
38 #define S_BUFSIZE (1024)
39 #define S_DEF_TITLE "UnknownStream"
40
41 #define SI_NAME_SIZE (100)
42 #define SI_GENRE_SIZE (100)
43 #define SI_DESCRIPTION_SIZE (100)
44 #define SI_TYPE_SIZE (30)
45
46 struct streamInfo{
47 char *name;
48 char *genre;
49 char *description;
50 char *type;
51 int bitrate;
52 };
53
open_pipe()54 static int open_pipe(){
55 int pipefd[2];
56 if(pipe(pipefd)){
57 fprintf(stderr,"pipe error\n");
58 return 1;
59 }
60 h.rfd=fdopen(pipefd[0],"rb");
61 h.wfd=fdopen(pipefd[1],"wb");
62 return 0;
63 }
64
close_pipe()65 static void close_pipe(){
66 if(h.wfd){
67 fclose(h.wfd);
68 h.wfd=NULL;
69 }
70 if(h.rfd){
71 fclose(h.rfd);
72 h.rfd=NULL;
73 }
74 }
75
plugin_close(FILE * ffd)76 static void plugin_close(FILE *ffd){
77 close_pipe();
78 close(h.sfd);
79 }
80
parse_url(const char * path,char ** orig_url,char ** orig_port,char ** orig_filename)81 static int parse_url(const char *path, char **orig_url, char **orig_port, char **orig_filename){
82 int x;
83 char *url=*orig_url;
84 char *port=*orig_port;
85
86 if(strncmp("http://",path,7)!=0){
87 //url=strdup(path);
88 debug(1,"Unknown protocol.");
89 return 1;
90 }
91 else
92 url=strdup(path+7);
93 if(!(port=malloc(10))){
94 fprintf(stderr,"Malloc failed.");
95 return 1;
96 }
97 *orig_port=port;
98 *orig_url=url;
99
100 for(x=0;url[x] && url[x]!=':';x++);
101 if(url[x]==':'){
102 url[x++]=0;
103 while(url[x] && url[x]>='0' && url[x]<='9')*(port++)=url[x++];
104 *port=0;
105 if(*(url+x))
106 *orig_filename=strdup(url+x);
107 else{
108 goto def_filename;
109 }
110
111 }
112 else{
113 strcpy(port,S_DEF_PORT_STR);
114 def_filename:
115 if(!(*orig_filename=malloc(1))){
116 fprintf(stderr,"Malloc failed.");
117 return 1;
118 }
119 **orig_filename=0;
120 }
121
122 return 0;
123 }
124
stream_hello(char * filename)125 static int stream_hello(char *filename){
126 char hello[300];
127 int hello_len;
128
129 if(*filename!=0)
130 sprintf(hello,"GET %s HTTP/1.0\r\nUser-Agent: HARP\r\nIcy-MetaData:%d\r\n\r\n",filename,h.print_meta);
131 else
132 sprintf(hello,"GET / HTTP/1.0\r\nUser-Agent: HARP\r\nIcy-MetaData:%d\r\n\r\n",h.print_meta);
133
134 hello_len=strlen(hello);
135 if(write(h.sfd,hello,hello_len)<hello_len){
136 fprintf(stderr,"Short write.\n");
137 return 1;
138 }
139 return 0;
140 }
141
plugin_open(const char * path,const char * mode)142 static FILE *plugin_open(const char *path, const char *mode){
143 int sfd;
144 int ret;
145 struct addrinfo hints,*rp,*result;
146 char *url,*port,*filename;
147
148 if(open_pipe())
149 return NULL;
150
151 if(parse_url(path,&url,&port,&filename)){
152 return NULL;
153 }
154
155 memset(&hints,0,sizeof(struct addrinfo));
156 hints.ai_family=AF_INET;
157 hints.ai_socktype=SOCK_STREAM;
158 hints.ai_protocol=0;
159 if((ret=getaddrinfo(url,port,&hints,&result))){
160 fprintf(stderr,"error (%s) - getaddrinfo: %s\n",path,gai_strerror(ret));
161 close_pipe();
162 free(port);
163 return NULL;
164 }
165 free(url);
166 free(port);
167
168 for(rp=result;rp;rp=rp->ai_next){
169 if((sfd=socket(rp->ai_family,rp->ai_socktype,rp->ai_protocol))==-1)
170 continue;
171 if(connect(sfd,rp->ai_addr,rp->ai_addrlen)!=-1){
172 h.sfd=sfd;
173 h.ffd=fdopen(sfd,mode);
174 break;
175 }
176 close(sfd);
177 }
178 if(!rp){
179 fprintf(stderr,"Cannot connect to: %s\n",path);
180 close_pipe();
181 return NULL;
182 }
183 freeaddrinfo(result);
184
185 h.print_meta=1;
186 if(stream_hello(filename)){
187 plugin_close(NULL);
188 return NULL;
189 }
190 free(filename);
191
192 return h.rfd;
193 }
194
plugin_seek(struct playerHandles * ph,int modtime)195 static void plugin_seek(struct playerHandles *ph, int modtime){
196 return;
197 }
198
filetype_by_data(FILE * ffd)199 static int filetype_by_data(FILE *ffd){
200 if(ffd==h.rfd)
201 return 1;
202 return 0;
203 }
204
print_stream_meta(struct streamInfo * si)205 static void print_stream_meta(struct streamInfo *si){
206 if(si->name && *si->name){
207 printf("Name: %s\n",si->name);
208 free(si->name);
209 }
210 if(si->description && *si->description){
211 printf("Description: %s\n",si->description);
212 free(si->description);
213 }
214 if(si->genre && *si->genre){
215 printf("Genre: %s\n",si->genre);
216 free(si->genre);
217 }
218 if(si->bitrate)
219 printf("Bitrate: %d\n",si->bitrate);
220 if(si->type && *si->type){
221 printf("Type: %s\n",si->type);
222 free(si->type);
223 }
224 printf("---------------------\n");
225 }
226
get_line(char * buf,int len)227 static char *get_line(char *buf, int len){
228 int x;
229 int nl=0;
230 char *ptr=buf;
231
232 if(len<1)return NULL;
233
234 for(x=0;x<len;x++){
235 if(!nl && *(ptr-1)==0){
236 buf=ptr;
237 if(len-x>=2 && strncmp(ptr,"\r\n",2)==0)
238 break;
239 }
240 if(*ptr=='\r'){
241 nl=1;
242 *ptr=0;
243 }
244 else if(*ptr=='\n'){
245 nl=1;
246 *ptr=0;
247 break;
248 }
249 ptr++;
250 }
251
252 return buf;
253 }
254
split_icy(char * buf)255 static char *split_icy(char *buf){
256 if(!*buf)return NULL;
257
258 while(*(++buf)){
259 if(*buf==':'){
260 *buf=0;
261 return buf+1;
262 }
263 }
264
265 return NULL;
266 }
267
parse_meta_si(char * buf,int * orig_len,void * data)268 static int parse_meta_si(char *buf, int *orig_len, void *data){
269 static int ret=1; /* use 1 additional block */
270 static void *last_data=NULL;
271 int len=*orig_len;
272 char *ptr=buf;
273 char *val;
274 struct streamInfo *si=(struct streamInfo*)data;
275
276 if(*buf<32 || *buf>126)return 0;
277
278 if(last_data!=data)ret=1; /* Reset block counter if writing to new data */
279 last_data=data;
280
281 while((ptr=get_line(ptr,len-(ptr-buf)))){
282 if(len-(ptr-buf) && !*ptr){
283 *orig_len=len-((ptr)-buf);
284 break;
285 }
286 if(strncmp(ptr,"\r\n",2)==0){
287 *orig_len=len-((ptr+2)-buf);
288 return 0;
289 }
290 if(!(val=split_icy(ptr))){
291 continue;
292 }
293 if(strcmp(ptr,"icy-name")==0){
294 strncpy(si->name,val,SI_NAME_SIZE);
295 }
296 if(strcmp(ptr,"icy-description")==0){
297 strncpy(si->description,val,SI_DESCRIPTION_SIZE);
298 }
299 if(strcmp(ptr,"icy-genre")==0){
300 strncpy(si->genre,val,SI_GENRE_SIZE);
301 }
302 if(strcmp(ptr,"icy-br")==0){
303 si->bitrate=(int)strtol(val,NULL,10);
304 }
305 if(strcmp(ptr,"icy-metaint")==0){
306 h.metaint=(int)strtol(val,NULL,10);
307 }
308 if(strcasecmp(ptr,"Content-Type")==0){
309 while(*val){
310 if(*val==' ')val++;
311 else break;
312 }
313 while(*val && *(val++)!='/');
314 strncpy(si->type,val,SI_TYPE_SIZE);
315 }
316 }
317 len-=(ptr-buf);
318 return ret--;
319 }
320
parse_meta_mi(char * buf,int * orig_len,void * data)321 static int parse_meta_mi(char *buf, int *orig_len, void *data){
322 static int ret=1; /* use 1 additional block */
323 static void *last_data=NULL;
324 int len=*orig_len;
325 char *ptr=buf;
326 char *val;
327 struct musicInfo *mi=(struct musicInfo*)data;
328
329 if(*buf<32 || *buf>126)return 0;
330
331 if(last_data!=data)ret=1; /* Reset block counter if writing to new data */
332 last_data=data;
333
334 while((ptr=get_line(ptr,len-(ptr-buf)))){
335 if(len-(ptr-buf) && !*ptr){
336 break;
337 }
338 if(strncmp(ptr,"\r\n",2)==0){
339 len=len-((ptr+2)-buf);
340 return 0;
341 }
342 if(!(val=split_icy(ptr))){
343 continue;
344 }
345 if(strcmp(ptr,"icy-name")==0){
346 strncpy(mi->title,val,MI_TITLE_SIZE);
347 }
348 }
349 len-=(ptr-buf);
350 return ret--;
351 }
352
print_streamtitle(char * buf,int buf_size,int * meta_len)353 static char *print_streamtitle(char *buf, int buf_size, int *meta_len){
354 if(*meta_len>buf_size)
355 buf_size-=*meta_len;
356 else
357 buf_size=0;
358 for(;*meta_len>buf_size;(*meta_len)--,buf++)
359 if(*buf)
360 printf("%c",*buf<32?'?':*buf);
361 if(!*meta_len)
362 putchar('\n');
363 else putchar('$');
364 return buf;
365 }
366
handle_streamtitle(char * buf,int * len,int * metasize)367 static char *handle_streamtitle(char *buf, int *len, int *metasize){
368 int temp=*metasize;
369 buf=print_streamtitle(buf,*len,metasize);
370 *len-=temp-*metasize;
371 return buf;
372 }
373
write_pipe_parse_meta(char * buf,int * len,void * data)374 static int write_pipe_parse_meta(char *buf, int *len, void *data){
375 int ret,temp;
376 static int metasize=0;
377 static int save;
378 char *ptr=buf;
379
380 if(!h.wfd){
381 fprintf(stderr,"No wfd\n");
382 return 0;
383 }
384 if(h.bytecount+*len>h.metaint){
385 volatile int *up=&(((struct playerHandles *)data)->pflag->update);
386
387 temp=h.metaint-h.bytecount;
388 if((ret=fwrite(ptr,1,temp,h.wfd))<temp)
389 fprintf(stderr,"Short write to pipe\n");
390 ptr+=ret+1;
391 *len-=ret+1;
392 h.bytecount+=ret; // Should = metaint now
393
394 if(!metasize){
395 save=*up;
396 *up=0;
397 metasize=(int)((char)*(ptr-1))*16;
398 if(!h.print_meta || metasize<0) // Cancel all further printing
399 metasize=h.print_meta=0;
400 }
401
402 if(metasize)
403 ptr=handle_streamtitle(ptr,len,&metasize);
404
405 if(metasize<=0){
406 *up=save;
407 metasize=h.bytecount=0;
408 }
409 if(*len<1)return 1;
410 }
411
412 if((ret=fwrite(ptr,1,*len,h.wfd))<*len){
413 fprintf(stderr,"Short write to pipe\n");
414 }
415 h.bytecount+=ret;
416 return 1;
417 }
418
write_pipe(char * buf,int * len,void * data)419 static int write_pipe(char *buf, int *len, void *data){
420 int ret;
421
422 if(!h.wfd){
423 fprintf(stderr,"No wfd\n");
424 return 0;
425 }
426
427 if((ret=fwrite(buf,1,*len,h.wfd))<*len){
428 fprintf(stderr,"Short write to pipe\n");
429 }
430 return 1;
431 }
432
streamIO(int parse (char *,int *,void *),void * data)433 static void streamIO(int parse(char*,int*,void*),void *data){
434 int len,ret;
435 char buf[S_BUFSIZE+1];
436 buf[S_BUFSIZE]=0;
437
438 while(1){
439 if(!h.go)
440 break;
441
442 len=ret=read(h.sfd,buf,S_BUFSIZE);
443 if(!parse(buf,&len,data))
444 break;
445 }
446 if(h.metaint){
447 h.bytecount=len;
448 }
449 }
450
stream_plugin_meta(FILE * ffd,struct musicInfo * mi)451 static void stream_plugin_meta(FILE *ffd, struct musicInfo *mi){
452 h.go=1;
453 streamIO(parse_meta_mi,mi);
454
455 if(!(*mi->title))
456 strncpy(mi->title,S_DEF_TITLE,MI_TITLE_SIZE);
457 mi->length=-1;
458 }
459
nextType(char * type)460 static char *nextType(char *type){
461 int x;
462 for(x=0;type[x];x++){
463 if(type[x]==';'){
464 return type+x+1;
465 }
466 }
467 return NULL;
468 }
469
selectPlugin(struct pluginitem ** list,char * type)470 static struct pluginitem *selectPlugin(struct pluginitem **list, char *type){
471 char *ptr;
472 int len;
473 int i;
474 if(type==NULL || *type==0)return NULL;
475 len=strlen(type);
476
477 for(i=0;i<PLUGIN_NULL;i++){
478 ptr=list[i]->contenttype;
479 while(ptr){
480 if(strncmp(ptr,type,len)==0)
481 return list[i];
482 ptr=nextType(ptr);
483 }
484 }
485 return NULL;
486 }
487
sio_thread(void * data)488 static void* sio_thread(void *data){
489 if(h.print_meta && h.metaint)
490 streamIO(write_pipe_parse_meta,data);
491 else
492 streamIO(write_pipe,data);
493
494 return (void*)0;
495 }
496
plugin_run(struct playerHandles * ph,char * key,int * totaltime)497 static int plugin_run(struct playerHandles *ph, char *key, int *totaltime){
498 int ret,temp=-1;
499 struct pluginitem *plugin;
500 struct streamInfo si;
501 pthread_t threads;
502 char buf[S_BUFSIZE+1];
503 fd_set fdset;
504 struct timeval timeout;
505 int rfd;
506 int i;
507
508 if(!(si.name=malloc((SI_NAME_SIZE+1)*sizeof(char))) ||
509 !(si.description=malloc((SI_DESCRIPTION_SIZE+1)*sizeof(char))) ||
510 !(si.genre=malloc((SI_GENRE_SIZE+1)*sizeof(char))) ||
511 !(si.type=malloc((SI_TYPE_SIZE+1)*sizeof(char)))){
512 fprintf(stderr,"Can't malloc for si\n");
513 return DEC_RET_ERROR;
514 }
515 memset(si.name,0,SI_NAME_SIZE);
516 memset(si.genre,0,SI_GENRE_SIZE);
517 memset(si.description,0,SI_DESCRIPTION_SIZE);
518 memset(si.type,0,SI_TYPE_SIZE);
519 si.bitrate=0;
520
521 h.metaint=h.bytecount=0;
522 h.go=1;
523 streamIO(parse_meta_si,&si);
524 print_stream_meta(&si);
525 if(!(plugin=selectPlugin(ph->plugin_head,si.type))){
526 fprintf(stderr,"No plugin matches content-type. Trying first plugin.\n");
527 for(i=0;i<PLUGIN_NULL && ph->plugin_head[i]==NULL;i++)
528 plugin=ph->plugin_head[i];
529 if(plugin==NULL)
530 return -1;
531 }
532
533 ph->ffd=h.rfd;
534 pthread_create(&threads,NULL,(void *)&sio_thread,(void *)ph);
535 ret=plugin->modplay(ph,key,&temp);
536
537 h.go=0;
538
539 //#if defined(__APPLE__) || defined(Linux)
540 rfd=fileno(h.rfd);
541 do{
542 timeout.tv_sec=0;
543 timeout.tv_usec=100000;
544 FD_ZERO(&fdset);
545 FD_SET(rfd,&fdset);
546 if(select(1,&fdset,NULL,NULL,&timeout)<1)
547 break;
548 }while((temp=fread(buf,1,S_BUFSIZE,h.rfd))==S_BUFSIZE);
549
550 // OSX seems to block when closing the write end unless the read end is first closed
551 close(rfd);
552 h.rfd=NULL;
553 //#else
554 #if 0
555 if(pthread_cancel(threads)!=0)fprintf(stderr,"Failed cancel.\n");
556 if(pthread_join(threads,NULL)!=0)fprintf(stderr,"Failed join\n");
557 #endif
558
559 //usleep(100000);
560
561 return ret;
562 }
563
564 struct pluginitem streamitem={
565 .modopen=plugin_open,
566 .modclose=plugin_close,
567 .moddata=filetype_by_data,
568 .modplay=plugin_run,
569 .modseek=plugin_seek,
570 .modmeta=stream_plugin_meta,
571 .contenttype="null",
572 .extension={"///",NULL},
573 .name="STREAM",
574 };
575