1/*
2 * $Id: webserver.c,v 1.1 2009-06-30 02:31:09 steven Exp $
3 * Webserver library
4 *
5 * Copyright (C) 2003 Ron Pedde (ron@pedde.com)
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20 */
21
22#ifdef HAVE_CONFIG_H
23#  include "config.h"
24#endif
25
26#include <ctype.h>
27#include <errno.h>
28#include <fcntl.h>
29#include <limits.h>
30#include <pthread.h>
31#include <regex.h>
32#include <restart.h>
33#include <stdarg.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <time.h>
38#include <uici.h>
39
40#include <sys/param.h>
41#include <sys/types.h>
42#include <sys/socket.h>
43
44#include "err.h"
45#include "webserver.h"
46
47/*
48 * Defines
49 */
50
51#define MAX_HOSTNAME 256
52#define MAX_LINEBUFFER 4096
53
54/*
55 * Local (private) typedefs
56 */
57
58typedef struct tag_ws_handler {
59    regex_t regex;
60    void (*req_handler)(WS_CONNINFO*);
61    int(*auth_handler)(char *, char *);
62    int addheaders;
63    struct tag_ws_handler *next;
64} WS_HANDLER;
65
66typedef struct tag_ws_connlist {
67    WS_CONNINFO *pwsc;
68    char *status;
69    struct tag_ws_connlist *next;
70} WS_CONNLIST;
71
72typedef struct tag_ws_private {
73    WSCONFIG wsconfig;
74    WS_HANDLER handlers;
75    WS_CONNLIST connlist;
76    int server_fd;
77    int stop;
78    int running;
79    int threadno;
80    int dispatch_threads;
81    pthread_t server_tid;
82    pthread_cond_t exit_cond;
83    pthread_mutex_t exit_mutex;
84} WS_PRIVATE;
85
86
87/*
88 * Forwards
89 */
90void *ws_mainthread(void*);
91void *ws_dispatcher(void*);
92int ws_lock_unsafe(void);
93int ws_unlock_unsafe(void);
94void ws_defaulthandler(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc);
95int ws_addarg(ARGLIST *root, char *key, char *fmt, ...);
96void ws_freearglist(ARGLIST *root);
97char *ws_urldecode(char *string, int space_as_plus);
98int ws_getheaders(WS_CONNINFO *pwsc);
99int ws_getpostvars(WS_CONNINFO *pwsc);
100int ws_getgetvars(WS_CONNINFO *pwsc, char *string);
101char *ws_getarg(ARGLIST *root, char *key);
102int ws_testarg(ARGLIST *root, char *key, char *value);
103int ws_findhandler(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc,
104                   void(**preq)(WS_CONNINFO*),
105                   int(**pauth)(char *, char *),
106                   int *addheaders);
107int ws_registerhandler(WSHANDLE ws, char *regex,
108                       void(*handler)(WS_CONNINFO*),
109                       int(*auth)(char *, char *),
110                       int addheaders);
111int ws_decodepassword(char *header, char **username, char **password);
112int ws_testrequestheader(WS_CONNINFO *pwsc, char *header, char *value);
113char *ws_getrequestheader(WS_CONNINFO *pwsc, char *header);
114static void ws_add_dispatch_thread(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc);
115static void ws_remove_dispatch_thread(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc);
116static int ws_encoding_hack(WS_CONNINFO *pwsc);
117
118/*
119 * Globals
120 */
121pthread_mutex_t ws_unsafe=PTHREAD_MUTEX_INITIALIZER;
122
123char *ws_dow[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
124char *ws_moy[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
125                   "Aug", "Sep", "Oct", "Nov", "Dec" };
126
127/*
128 * ws_lock_unsafe
129 *
130 * Lock non-thread-safe functions
131 *
132 * returns 0 on success,
133 * returns -1 on failure, with errno set
134 */
135int ws_lock_unsafe(void) {
136    int err;
137    int retval=0;
138
139    DPRINTF(E_SPAM,L_WS,"Entering ws_lock_unsafe\n");
140
141    if((err=pthread_mutex_lock(&ws_unsafe))) {
142        errno=err;
143        retval=-1;
144    }
145
146    DPRINTF(E_SPAM,L_WS,"Exiting ws_lock_unsafe with retval of %d\n",retval);
147    return retval;
148}
149
150/*
151 * ws_unlock_unsafe
152 *
153 * Lock non-thread-safe functions
154 *
155 * returns 0 on success,
156 * returns -1 on failure, with errno set
157 */
158int ws_unlock_unsafe(void) {
159    int err;
160    int retval=0;
161
162    DPRINTF(E_SPAM,L_WS,"Entering ws_unlock_unsafe\n");
163
164    if((err=pthread_mutex_unlock(&ws_unsafe))) {
165        errno=err;
166        retval=-1;
167    }
168
169    DPRINTF(E_SPAM,L_WS,"Exiting ws_unlock_unsafe with a retval of %d\n",retval);
170    return retval;
171}
172
173/*
174 * ws_start
175 *
176 * Start the main webserver thread.  Should really
177 * bind and listen to the port before spawning the thread,
178 * since it will be hard to detect and return the error unless
179 * we listen first
180 *
181 * RETURNS
182 *   Success: WSHANDLE
183 *   Failure: NULL, with errno set
184 *
185 */
186WSHANDLE ws_start(WSCONFIG *config) {
187    int err;
188    WS_PRIVATE *pwsp;
189
190    DPRINTF(E_SPAM,L_WS,"Entering ws_start\n");
191
192    if((pwsp=(WS_PRIVATE*)malloc(sizeof(WS_PRIVATE))) == NULL) {
193        DPRINTF(E_SPAM,L_WS,"Malloc error: %s\n",strerror(errno));
194        return NULL;
195    }
196
197    memcpy(&pwsp->wsconfig,config,sizeof(WS_PRIVATE));
198    pwsp->connlist.next=NULL;
199    pwsp->running=0;
200    pwsp->threadno=0;
201    pwsp->stop=0;
202    pwsp->dispatch_threads=0;
203    pwsp->handlers.next=NULL;
204
205    if((err=pthread_cond_init(&pwsp->exit_cond, NULL))) {
206        errno=err;
207        DPRINTF(E_LOG,L_WS,"Error in pthread_cond_init: %s\n",strerror(errno));
208        return NULL;
209    }
210
211    if((err=pthread_mutex_init(&pwsp->exit_mutex,NULL))) {
212        errno=err;
213        DPRINTF(E_LOG,L_WS,"Error in pthread_mutex_init: %s\n",strerror(errno));
214        return NULL;
215    }
216
217    DPRINTF(E_INF,L_WS,"Preparing to listen on port %d\n",pwsp->wsconfig.port);
218
219    if((pwsp->server_fd = u_open(pwsp->wsconfig.port)) == -1) {
220        err=errno;
221        DPRINTF(E_LOG,L_WS,"Could not open port: %s\n",strerror(errno));
222        errno=err;
223        return NULL;
224    }
225
226    DPRINTF(E_INF,L_WS,"Starting server thread\n");
227    if((err=pthread_create(&pwsp->server_tid,NULL,ws_mainthread,(void*)pwsp))) {
228        DPRINTF(E_LOG,L_WS,"Could not spawn thread: %s\n",strerror(err));
229        r_close(pwsp->server_fd);
230        errno=err;
231        return NULL;
232    }
233
234    /* we're really running */
235    pwsp->running=1;
236
237    DPRINTF(E_SPAM,L_WS,"Exiting ws_start\n");
238    return (WSHANDLE)pwsp;
239}
240
241
242/*
243 * ws_remove_dispatch_thread
244 *
245 * remove a dispatch thread from the thread list
246 */
247void ws_remove_dispatch_thread(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc) {
248    WS_CONNLIST *pHead, *pTail;
249
250    DPRINTF(E_SPAM,L_WS,"Entering ws_remove_dispatch_thread\n");
251
252    if(pthread_mutex_lock(&pwsp->exit_mutex))
253        DPRINTF(E_FATAL,L_WS,"Cannot lock condition mutex\n");
254
255    pTail=&(pwsp->connlist);
256    pHead=pwsp->connlist.next;
257
258    while((pHead) && (pHead->pwsc != pwsc)) {
259        pTail=pHead;
260        pHead=pHead->next;
261    }
262
263    if(pHead) {
264        pwsp->dispatch_threads--;
265        DPRINTF(E_DBG,L_WS,"With thread %d exiting, %d are still running\n",
266                pwsc->threadno,pwsp->dispatch_threads);
267
268        pTail->next = pHead->next;
269
270        if(pHead->status)
271            free(pHead->status);
272        free(pHead);
273
274        /* signal condition in case something is waiting */
275        pthread_cond_signal(&pwsp->exit_cond);
276    }
277
278    pthread_mutex_unlock(&pwsp->exit_mutex);
279    DPRINTF(E_SPAM,L_WS,"Exiting ws_remote_dispatch_thread\n");
280}
281
282
283/*
284 * ws_add_dispatch_thread
285 *
286 * Add a thread to the dispatch thread list
287 */
288void ws_add_dispatch_thread(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc) {
289    WS_CONNLIST *pNew;
290
291    DPRINTF(E_SPAM,L_WS,"Entering ws_add_dispatch_thread\n");
292
293    pNew=(WS_CONNLIST*)malloc(sizeof(WS_CONNLIST));
294    pNew->next=NULL;
295    pNew->pwsc=pwsc;
296    pNew->status=strdup("Initializing");
297
298    if(!pNew)
299        DPRINTF(E_FATAL,L_WS,"Malloc: %s\n",strerror(errno));
300
301    if(pthread_mutex_lock(&pwsp->exit_mutex))
302        DPRINTF(E_FATAL,L_WS,"Cannot lock condition mutex\n");
303
304    /* list is locked... */
305    pwsp->dispatch_threads++;
306    pNew->next = pwsp->connlist.next;
307    pwsp->connlist.next = pNew;
308
309    pthread_mutex_unlock(&pwsp->exit_mutex);
310    DPRINTF(E_SPAM,L_WS,"Exiting ws_add_dispatch_thread\n");
311}
312
313/*
314 * ws_stop
315 *
316 * Stop the web server and all the child threads
317 */
318extern int ws_stop(WSHANDLE ws) {
319    WS_PRIVATE *pwsp = (WS_PRIVATE*)ws;
320    WS_HANDLER *current;
321    WS_CONNLIST *pcl;
322    void *result;
323
324    DPRINTF(E_DBG,L_WS,"Entering ws_stop: %d threads\n",pwsp->dispatch_threads);
325
326    /* free the ws_handlers */
327    while(pwsp->handlers.next) {
328        current=pwsp->handlers.next;
329        pwsp->handlers.next=current->next;
330        regfree(&current->regex);
331        free(current);
332    }
333
334    pwsp->stop=1;
335    pwsp->running=0;
336
337    DPRINTF(E_DBG,L_WS,"ws_stop: closing the server fd\n");
338    shutdown(pwsp->server_fd,SHUT_RDWR);
339    r_close(pwsp->server_fd); /* this should tick off the listener */
340
341    /* wait for the server thread to terminate.  SHould be quick! */
342    pthread_join(pwsp->server_tid,&result);
343
344    /* Give the threads an extra push */
345    if(pthread_mutex_lock(&pwsp->exit_mutex))
346        DPRINTF(E_FATAL,L_WS,"Cannot lock condition mutex\n");
347
348    pcl=pwsp->connlist.next;
349
350    /* Closing the client sockets out from under the dispatch threads
351     * should cause the dispatch threads to exit out with an error.
352     */
353    while(pcl) {
354        if(pcl->pwsc->fd) {
355            shutdown(pcl->pwsc->fd,SHUT_RDWR);
356            r_close(pcl->pwsc->fd);
357        }
358        pcl=pcl->next;
359    }
360
361    /* wait for the threads to be done */
362    while(pwsp->dispatch_threads) {
363        DPRINTF(E_DBG,L_WS,"ws_stop: I still see %d threads\n",pwsp->dispatch_threads);
364        pthread_cond_wait(&pwsp->exit_cond, &pwsp->exit_mutex);
365    }
366
367    pthread_mutex_unlock(&pwsp->exit_mutex);
368
369    free(pwsp);
370
371    DPRINTF(E_SPAM,L_WS,"Exiting ws_stop\n");
372    return 0;
373}
374
375/*
376 * ws_mainthread
377 *
378 * Main thread for webserver - this accepts connections
379 * and spawns a handler thread for each incoming connection.
380 *
381 * For a persistant connection, these threads will be
382 * long-lived, otherwise, they will terminate as soon as
383 * the request has been honored.
384 *
385 * These client threads will, of course, be detached
386 */
387void *ws_mainthread(void *arg) {
388    int fd;
389    int err;
390    WS_PRIVATE *pwsp = (WS_PRIVATE*)arg;
391    WS_CONNINFO *pwsc;
392    pthread_t tid;
393    char hostname[MAX_HOSTNAME];
394
395    DPRINTF(E_SPAM,L_WS,"Entering ws_mainthread\n");
396
397    while(1) {
398        pwsc=(WS_CONNINFO*)malloc(sizeof(WS_CONNINFO));
399        if(!pwsc) {
400            /* can't very well service any more threads! */
401            DPRINTF(E_FATAL,L_WS,"Error: %s\n",strerror(errno));
402            pwsp->running=0;
403            return NULL;
404        }
405
406        memset(pwsc,0,sizeof(WS_CONNINFO));
407
408        if((fd=u_accept(pwsp->server_fd,hostname,MAX_HOSTNAME)) == -1) {
409            DPRINTF(E_LOG,L_WS,"Dispatcher: accept failed: %s\n",strerror(errno));
410            shutdown(pwsp->server_fd,SHUT_RDWR);
411            r_close(pwsp->server_fd);
412            pwsp->running=0;
413            free(pwsc);
414
415            DPRINTF(E_FATAL,L_WS,"Dispatcher: Aborting\n");
416            return NULL;
417        }
418
419        pwsc->hostname=strdup(hostname);
420        pwsc->fd=fd;
421        pwsc->pwsp = pwsp;
422
423        /* Spawn off a dispatcher to decide what to do with
424         * the request
425         */
426
427        /* don't really care if it locks or not */
428        ws_lock_unsafe();
429        pwsc->threadno=pwsp->threadno;
430        pwsp->threadno++;
431        ws_unlock_unsafe();
432
433        /* now, throw off a dispatch thread */
434        if((err=pthread_create(&tid,NULL,ws_dispatcher,(void*)pwsc))) {
435            pwsc->error=err;
436            DPRINTF(E_FATAL,L_WS,"Could not spawn thread: %s\n",strerror(err));
437            ws_close(pwsc);
438        } else {
439            ws_add_dispatch_thread(pwsp,pwsc);
440            pthread_detach(tid);
441        }
442    }
443
444    DPRINTF(E_SPAM,L_WS,"Exiting ws_mainthred\n");
445}
446
447
448/*
449 * ws_close
450 *
451 * Close the connection.  This might be called when things
452 * are already in bad shape, so we'll ignore errors and let
453 * them be detected back in either the dispatch
454 * thread or the main server thread
455 *
456 * Mainly, we just want to make sure that any
457 * allocated memory has been freed
458 */
459void ws_close(WS_CONNINFO *pwsc) {
460    WS_PRIVATE *pwsp = (WS_PRIVATE *)pwsc->pwsp;
461
462
463    DPRINTF(E_SPAM,L_WS,"Entering ws_close\n");
464
465    /* DWB: update the status so it doesn't fill up with no longer
466       relevant entries  */
467    config_set_status(pwsc, 0, NULL);
468
469    DPRINTF(E_DBG,L_WS,"Thread %d: Terminating\n",pwsc->threadno);
470    DPRINTF(E_DBG,L_WS,"Thread %d: Freeing request headers\n",pwsc->threadno);
471    ws_freearglist(&pwsc->request_headers);
472    DPRINTF(E_DBG,L_WS,"Thread %d: Freeing response headers\n",pwsc->threadno);
473    ws_freearglist(&pwsc->response_headers);
474    DPRINTF(E_DBG,L_WS,"Thread %d: Freeing request vars\n",pwsc->threadno);
475    ws_freearglist(&pwsc->request_vars);
476    if(pwsc->uri) {
477        free(pwsc->uri);
478        pwsc->uri=NULL;
479    }
480
481    if((pwsc->close)||(pwsc->error)) {
482        DPRINTF(E_DBG,L_WS,"Thread %d: Closing fd\n",pwsc->threadno);
483        shutdown(pwsc->fd,SHUT_RDWR);
484        r_close(pwsc->fd);
485        free(pwsc->hostname);
486
487        /* this thread is done */
488        ws_remove_dispatch_thread(pwsp, pwsc);
489
490        free(pwsc);
491        DPRINTF(E_SPAM,L_WS,"Exiting ws_close (thread terminating)\n");
492        pthread_exit(NULL);
493    }
494    DPRINTF(E_SPAM,L_WS,"Exiting ws_close (thread continuing)\n");
495}
496
497/*
498 * ws_freearglist
499 *
500 * Walks through an arg list freeing as it goes
501 */
502void ws_freearglist(ARGLIST *root) {
503    ARGLIST *current;
504
505    DPRINTF(E_SPAM,L_WS,"Entering ws_freearglist\n");
506
507    while(root->next) {
508        free(root->next->key);
509        free(root->next->value);
510        current=root->next;
511        root->next=current->next;
512        free(current);
513    }
514
515    DPRINTF(E_SPAM,L_WS,"Exiting ws_freearglist\n");
516}
517
518
519void ws_emitheaders(WS_CONNINFO *pwsc) {
520    ARGLIST *pcurrent=pwsc->response_headers.next;
521
522    DPRINTF(E_SPAM,L_WS,"Entering ws_emitheaders\n");
523    while(pcurrent) {
524        DPRINTF(E_DBG,L_WS,"Emitting reponse header %s: %s\n",pcurrent->key,
525                pcurrent->value);
526        ws_writefd(pwsc,"%s: %s\r\n",pcurrent->key,pcurrent->value);
527        pcurrent=pcurrent->next;
528    }
529
530    ws_writefd(pwsc,"\r\n");
531    DPRINTF(E_SPAM,L_WS,"Exitin ws_emitheaders\n");
532}
533
534
535/*
536 * ws_getpostvars
537 *
538 * Receive and parse headers.  These will trump
539 * get headers
540 */
541int ws_getpostvars(WS_CONNINFO *pwsc) {
542    char *content_length;
543    int length;
544    char *buffer;
545
546    DPRINTF(E_SPAM,L_WS,"Entering ws_getpostvars\n");
547
548    content_length = ws_getarg(&pwsc->request_headers,"Content-Length");
549    if(!content_length) {
550        pwsc->error = EINVAL;
551        return -1;
552    }
553
554    length=strtol(content_length, NULL, 10);
555    if(EINVAL == errno || UINT_MAX -1 <= length) {
556        DPRINTF(E_WARN,L_WS, "Thread %d: Suspicious Content-Length value, ignoring request\n",pwsc->threadno);
557        return -1;
558    }
559
560    DPRINTF(E_DBG,L_WS,"Thread %d: Post var length: %d\n",
561            pwsc->threadno,length);
562
563    buffer=(char*)malloc(length+1);
564
565    if(!buffer) {
566        pwsc->error = errno;
567        DPRINTF(E_INF,L_WS,"Thread %d: Could not malloc %d bytes\n",
568                pwsc->threadno, length);
569        return -1;
570    }
571
572    // make the read time out 30 minutes like we said in the
573    // /server-info response
574    if((readtimed(pwsc->fd, buffer, length, 1800.0)) == -1) {
575        DPRINTF(E_INF,L_WS,"Thread %d: Timeout reading post vars\n",
576                pwsc->threadno);
577        pwsc->error=errno;
578        return -1;
579    }
580
581    DPRINTF(E_DBG,L_WS,"Thread %d: Read post vars: %s\n",pwsc->threadno,buffer);
582
583    pwsc->error=ws_getgetvars(pwsc,buffer);
584
585    free(buffer);
586
587    DPRINTF(E_SPAM,L_WS,"Exiting ws_getpostvars\n");
588    return pwsc->error;
589}
590
591
592/*
593 * ws_getheaders
594 *
595 * Receive and parse headers.  This is called from
596 * ws_dispatcher
597 */
598int ws_getheaders(WS_CONNINFO *pwsc) {
599    char *first, *last;
600    int done;
601    char buffer[MAX_LINEBUFFER];
602
603    DPRINTF(E_SPAM,L_WS,"Entering ws_getheaders\n");
604
605    /* Break down the headers into some kind of header list */
606    done=0;
607    while(!done) {
608        if(readline(pwsc->fd,buffer,sizeof(buffer)) == -1) {
609            pwsc->error=errno;
610            DPRINTF(E_INF,L_WS,"Thread %d: Unexpected close\n",pwsc->threadno);
611            return -1;
612        }
613
614        DPRINTF(E_DBG,L_WS,"Thread %d: Read: %s",pwsc->threadno,buffer);
615
616        first=buffer;
617        if(buffer[0] == '\r')
618            first=&buffer[1];
619
620        /* trim the trailing \n */
621        if(first[strlen(first)-1] == '\n')
622            first[strlen(first)-1] = '\0';
623
624        if(strlen(first) == 0) {
625            DPRINTF(E_DBG,L_WS,"Thread %d: Headers parsed!\n",pwsc->threadno);
626            done=1;
627        } else {
628            /* we have a header! */
629            last=first;
630            strsep(&last,":");
631
632            if(!last) {
633                DPRINTF(E_WARN,L_WS,"Thread %d: Invalid header: %s\n",
634                        pwsc->threadno,first);
635            } else {
636                while(*last==' ')
637                    last++;
638
639                while(last[strlen(last)-1] == '\r')
640                    last[strlen(last)-1] = '\0';
641
642                DPRINTF(E_DBG,L_WS,"Thread %d: Adding header *%s=%s*\n",
643                        pwsc->threadno,first,last);
644
645                if(ws_addarg(&pwsc->request_headers,first,"%s",last)) {
646                    DPRINTF(E_FATAL,L_WS,"Thread %d: Out of memory\n",
647                            pwsc->threadno);
648                    pwsc->error=ENOMEM;
649                    return -1;
650                }
651            }
652        }
653    }
654
655    DPRINTF(E_SPAM,L_WS,"Exiting ws_getheaders\n");
656    return 0;
657}
658
659
660/*
661 * ws_encoding_hack
662 */
663int ws_encoding_hack(WS_CONNINFO *pwsc) {
664    char *user_agent;
665    int space_as_plus=1;
666
667    user_agent=ws_getrequestheader(pwsc, "user-agent");
668    if(user_agent) {
669        if(strncasecmp(user_agent,"Roku",4) == 0)
670            space_as_plus=0;
671        if(strncasecmp(user_agent,"iTunes",6) == 0)
672            space_as_plus=0;
673    }
674    return space_as_plus;
675}
676
677
678/*
679 * ws_getgetvars
680 *
681 * parse a GET string of variables (or POST)
682 *
683 */
684int ws_getgetvars(WS_CONNINFO *pwsc, char *string) {
685    char *new_string;
686    char *first, *last, *middle;
687    char *key, *value;
688    int done;
689
690    int space_as_plus;
691
692    DPRINTF(E_DBG,L_WS,"Thread %d: Entering ws_getgetvars (%s)\n",
693            pwsc->threadno,string);
694
695    space_as_plus=ws_encoding_hack(pwsc);
696
697    done=0;
698
699    first=string;
700
701    while((!done) && (first)) {
702        last=middle=first;
703        strsep(&last,"&");
704        strsep(&middle,"=");
705
706        if(!middle) {
707            DPRINTF(E_WARN,L_WS,"Thread %d: Bad arg: %s\n",
708                    pwsc->threadno,first);
709        } else {
710            key=ws_urldecode(first,space_as_plus);
711            value=ws_urldecode(middle,space_as_plus);
712
713            DPRINTF(E_DBG,L_WS,"Thread %d: Adding arg %s = %s\n",
714                    pwsc->threadno,key,value);
715            ws_addarg(&pwsc->request_vars,key,"%s",value);
716
717            free(key);
718            free(value);
719        }
720
721        if(!last) {
722            DPRINTF(E_DBG,L_WS,"Thread %d: Done parsing GET/POST args!\n",
723                    pwsc->threadno);
724            done=1;
725        } else {
726            first=last;
727        }
728    }
729
730    DPRINTF(E_SPAM,L_WS,"Exiting ws_getgetvars\n");
731    return 0;
732}
733
734
735/*
736 * ws_dispatcher
737 *
738 * Main dispatch thread.  This gets the request, reads the
739 * headers, decodes the GET'd or POST'd variables,
740 * then decides what function should service the request
741 */
742void *ws_dispatcher(void *arg) {
743    WS_CONNINFO *pwsc=(WS_CONNINFO*)arg;
744    WS_PRIVATE *pwsp=pwsc->pwsp;
745    char buffer[MAX_LINEBUFFER];
746    char *first,*last;
747    int connection_done=0;
748    int can_dispatch;
749    char *auth, *username, *password;
750    int hdrs,handler;
751    time_t now;
752    struct tm now_tm;
753    void (*req_handler)(WS_CONNINFO*);
754    int(*auth_handler)(char *, char *);
755
756    DPRINTF(E_DBG,L_WS,"Thread %d: Entering ws_dispatcher (Connection from %s)\n",
757            pwsc->threadno, pwsc->hostname);
758
759    while(!connection_done) {
760        /* Now, get the request from the other end
761         * and decide where to dispatch it
762         */
763
764        /* DWB: set timeout to 30 minutes as advertised in the
765           server-info response. */
766        if((readlinetimed(pwsc->fd,buffer,sizeof(buffer),1800.0)) < 1) {
767            pwsc->error=errno;
768            pwsc->close=1;
769            DPRINTF(E_WARN,L_WS,"Thread %d:  could not read: %s\n",
770                    pwsc->threadno,strerror(errno));
771            ws_close(pwsc);
772            return NULL;
773        }
774
775        DPRINTF(E_DBG,L_WS,"Thread %d: got request\n",pwsc->threadno);
776        DPRINTF(E_DBG - 1,L_WS, "Request: %s", buffer);
777
778        first=last=buffer;
779        strsep(&last," ");
780        if(!last) {
781            pwsc->close=1;
782            ws_returnerror(pwsc,400,"Bad request\n");
783            ws_close(pwsc);
784            DPRINTF(E_SPAM,L_WS,"Error: bad request.  Exiting ws_dispatcher\n");
785            return NULL;
786        }
787
788        if(!strcasecmp(first,"get")) {
789            pwsc->request_type = RT_GET;
790        } else if(!strcasecmp(first,"post")) {
791            pwsc->request_type = RT_POST;
792        } else {
793            /* return a 501 not implemented */
794            pwsc->error=EINVAL;
795            pwsc->close=1;
796            ws_returnerror(pwsc,501,"Not implemented");
797            ws_close(pwsc);
798            DPRINTF(E_SPAM,L_WS,"Error: not get or post.  Exiting ws_dispatcher\n");
799            return NULL;
800        }
801
802        first=last;
803        strsep(&last," ");
804        pwsc->uri=strdup(first);
805
806        /* Get headers */
807        if((ws_getheaders(pwsc)) || (!last)) { /* didn't provide a HTTP/1.x */
808            /* error already set */
809            DPRINTF(E_LOG,L_WS,"Thread %d: Couldn't parse headers - aborting\n",
810                    pwsc->threadno);
811            pwsc->close=1;
812            ws_close(pwsc);
813            return NULL;
814        }
815
816
817        /* Now that we have the headers, we can
818         * decide whether or not this is a persistant
819         * connection */
820        if(strncasecmp(last,"HTTP/1.0",8)==0) { /* defaults to non-persistant */
821            pwsc->close=!ws_testarg(&pwsc->request_headers,"connection","keep-alive");
822        } else { /* default to persistant for HTTP/1.1 and above */
823            pwsc->close=ws_testarg(&pwsc->request_headers,"connection","close");
824        }
825
826        DPRINTF(E_DBG,L_WS,"Thread %d: Connection type %s: Connection: %s\n",
827                pwsc->threadno, last, pwsc->close ? "non-persist" : "persist");
828
829        if(!pwsc->uri) {
830            pwsc->error=ENOMEM;
831            pwsc->close=1; /* force a full close */
832            DPRINTF(E_LOG,L_WS,"Thread %d: Error allocation URI\n",
833                    pwsc->threadno);
834            ws_returnerror(pwsc,500,"Internal server error");
835            ws_close(pwsc);
836            return NULL;
837        }
838
839        /* trim the URI */
840        first=pwsc->uri;
841        strsep(&first,"?");
842
843        if(first) { /* got some GET args */
844            DPRINTF(E_DBG,L_WS,"Thread %d: parsing GET args\n",pwsc->threadno);
845            ws_getgetvars(pwsc,first);
846        }
847
848        /* fix the URI by un urldecoding it */
849
850        DPRINTF(E_DBG,L_WS,"Thread %d: Original URI: %s\n",
851                pwsc->threadno,pwsc->uri);
852
853        first=ws_urldecode(pwsc->uri,ws_encoding_hack(pwsc));
854        free(pwsc->uri);
855        pwsc->uri=first;
856
857        /* Strip out the proxy stuff - iTunes 4.5 */
858        first=strstr(pwsc->uri,"://");
859        if(first) {
860            first += 3;
861            first=strchr(first,'/');
862            if(first) {
863                first=strdup(first);
864                free(pwsc->uri);
865                pwsc->uri=first;
866            }
867        }
868
869
870        DPRINTF(E_DBG,L_WS,"Thread %d: Translated URI: %s\n",pwsc->threadno,
871                pwsc->uri);
872
873        /* now, parse POST args */
874        if((pwsc->request_type == RT_POST) && (ws_getpostvars(pwsc) == -1)) {
875            DPRINTF(E_LOG,L_WS,"Couldn't process post vars.  Aborting connection\n");
876            pwsc->error=0;
877            pwsc->close=1; /* force a full close */
878            ws_returnerror(pwsc,500,"Internal server error");
879            ws_close(pwsc);
880            return NULL;
881        }
882
883        hdrs=1;
884
885        handler=ws_findhandler(pwsp,pwsc,&req_handler,&auth_handler,&hdrs);
886
887        time(&now);
888        DPRINTF(E_DBG,L_WS,"Thread %d: Time is %d seconds after epoch\n",
889                pwsc->threadno,now);
890        gmtime_r(&now,&now_tm);
891        DPRINTF(E_DBG,L_WS,"Thread %d: Setting time header\n",pwsc->threadno);
892        ws_addarg(&pwsc->response_headers,"Date",
893                  "%s, %d %s %d %02d:%02d:%02d GMT",
894                  ws_dow[now_tm.tm_wday],now_tm.tm_mday,
895                  ws_moy[now_tm.tm_mon],now_tm.tm_year + 1900,
896                  now_tm.tm_hour,now_tm.tm_min,now_tm.tm_sec);
897
898        if(hdrs) {
899            ws_addarg(&pwsc->response_headers,"Connection",
900                      pwsc->close ? "close" : "keep-alive");
901
902            ws_addarg(&pwsc->response_headers,"Server",
903                      "mt-daapd/" VERSION);
904
905            ws_addarg(&pwsc->response_headers,"Content-Type","text/html");
906            ws_addarg(&pwsc->response_headers,"Content-Language","en_us");
907        }
908
909        /* Find the appropriate handler and dispatch it */
910        if(handler == -1) {
911            DPRINTF(E_DBG,L_WS,"Thread %d: Using default handler.\n",
912                    pwsc->threadno);
913            ws_defaulthandler(pwsp,pwsc);
914        } else {
915            DPRINTF(E_DBG,L_WS,"Thread %d: Using non-default handler\n",
916                    pwsc->threadno);
917
918            can_dispatch=0;
919            /* If an auth handler is registered, but it accepts a
920             * username and password of NULL, then don't bother
921             * authing.
922             */
923            if((auth_handler) && (auth_handler(NULL,NULL)==0)) {
924                /* do the auth thing */
925                auth=ws_getarg(&pwsc->request_headers,"Authorization");
926                if((auth) && (ws_decodepassword(auth,&username,&password) != -1)) {
927                    if(auth_handler(username,password))
928                        can_dispatch=1;
929                    ws_addarg(&pwsc->request_vars,"HTTP_USER","%s",username);
930                    ws_addarg(&pwsc->request_vars,"HTTP_PASSWD","%s",password);
931                    free(username); /* this frees password too */
932                }
933
934                if(!can_dispatch) { /* auth failed, or need auth */
935                    //ws_addarg(&pwsc->response_headers,"Connection","close");
936                    ws_addarg(&pwsc->response_headers,"WWW-Authenticate",
937                              "Basic realm=\"webserver\"");
938                    ws_returnerror(pwsc,401,"Unauthorized");
939                    pwsc->error=0;
940                }
941            } else {
942                can_dispatch=1;
943            }
944
945            if(can_dispatch) {
946                if(req_handler)
947                    req_handler(pwsc);
948                else
949                    ws_defaulthandler(pwsp,pwsc);
950            }
951        }
952
953        if((pwsc->close) || (pwsc->error) || (pwsp->stop)) {
954            pwsc->close=1;
955            connection_done=1;
956        }
957        ws_close(pwsc);
958    }
959    DPRINTF(E_SPAM,L_WS,"Exiting ws_dispatcher\n");
960    return NULL;
961}
962
963
964/*
965 * ws_writefd
966 *
967 * Write a printf-style output to a connfd
968 */
969int ws_writefd(WS_CONNINFO *pwsc, char *fmt, ...) {
970    char buffer[1024];
971    va_list ap;
972
973    DPRINTF(E_SPAM,L_WS,"Entering ws_writefd\n");
974
975    va_start(ap, fmt);
976    vsnprintf(buffer, 1024, fmt, ap);
977    va_end(ap);
978
979    DPRINTF(E_SPAM,L_WS,"Exiting ws_writefd\n");
980    return r_write(pwsc->fd,buffer,strlen(buffer));
981}
982
983
984/*
985 * ws_returnerror
986 *
987 * return a particular error code to the requesting
988 * agent
989 *
990 * This will always succeed.  If it cannot, it will
991 * just close the connection with prejudice.
992 */
993int ws_returnerror(WS_CONNINFO *pwsc,int error, char *description) {
994    char *useragent;
995
996    DPRINTF(E_WARN,L_WS,"Thread %d: Entering ws_returnerror (%d: %s)\n",
997            pwsc->threadno,error,description);
998    ws_writefd(pwsc,"HTTP/1.1 %d %s\r\n",error,description);
999
1000    /* we'll force a close here unless the user agent is
1001       iTunes, which seems to get pissy about it */
1002    useragent = ws_getarg(&pwsc->request_headers,"User-Agent");
1003    if(useragent && (strncmp(useragent,"iTunes",6))) {
1004        pwsc->close=1;
1005        ws_addarg(&pwsc->response_headers,"Connection","close");
1006    }
1007
1008    ws_emitheaders(pwsc);
1009
1010    ws_writefd(pwsc,"<HTML>\r\n<TITLE>");
1011    ws_writefd(pwsc,"%d %s</TITLE>\r\n<BODY>",error,description);
1012    ws_writefd(pwsc,"\r\n<H1>%s</H1>\r\n",description);
1013    ws_writefd(pwsc,"Error %d\r\n<hr>\r\n",error);
1014    ws_writefd(pwsc,"<i>mt-daapd: %s\r\n<br>",VERSION);
1015    if(errno)
1016        ws_writefd(pwsc,"Error: %s\r\n",strerror(errno));
1017
1018    ws_writefd(pwsc,"</i></BODY>\r\n</HTML>\r\n");
1019
1020    DPRINTF(E_SPAM,L_WS,"Exiting ws_returnerror\n");
1021    return 0;
1022}
1023
1024/*
1025 * ws_defaulthandler
1026 *
1027 * default URI handler.  This simply finds the file
1028 * and serves it up
1029 */
1030void ws_defaulthandler(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc) {
1031    char path[MAXPATHLEN];
1032    char resolved_path[MAXPATHLEN];
1033    int file_fd;
1034    off_t len;
1035
1036    DPRINTF(E_SPAM,L_WS,"Entering ws_defaulthandler\n");
1037
1038    snprintf(path,MAXPATHLEN,"%s/%s",pwsp->wsconfig.web_root,pwsc->uri);
1039    if(!realpath(path,resolved_path)) {
1040        pwsc->error=errno;
1041        DPRINTF(E_WARN,L_WS,"Exiting ws_defaulthandler: Cannot resolve %s\n",path);
1042        ws_returnerror(pwsc,404,"Not found");
1043        ws_close(pwsc);
1044        return;
1045    }
1046
1047    DPRINTF(E_DBG,L_WS,"Thread %d: Preparing to serve %s\n",
1048            pwsc->threadno, resolved_path);
1049
1050    if(strncmp(resolved_path,pwsp->wsconfig.web_root,
1051               strlen(pwsp->wsconfig.web_root))) {
1052        pwsc->error=EINVAL;
1053        DPRINTF(E_WARN,L_WS,"Exiting ws_defaulthandler: Thread %d: "
1054                "Requested file %s out of root\n",
1055                pwsc->threadno,resolved_path);
1056        ws_returnerror(pwsc,403,"Forbidden");
1057        ws_close(pwsc);
1058        return;
1059    }
1060
1061    file_fd=open(resolved_path,O_RDONLY);
1062    if(file_fd == -1) {
1063        pwsc->error=errno;
1064        DPRINTF(E_WARN,L_WS,"Exiting ws_defaulthandler: Thread %d: "
1065                "Error opening %s: %s\n",
1066                pwsc->threadno,resolved_path,strerror(errno));
1067        ws_returnerror(pwsc,404,"Not found");
1068        ws_close(pwsc);
1069        return;
1070    }
1071
1072    /* set the Content-Length response header */
1073    len=lseek(file_fd,0,SEEK_END);
1074
1075    /* FIXME: assumes off_t == long */
1076    if(len != -1) {
1077        /* we have a real length */
1078        DPRINTF(E_DBG,L_WS,"Length of file is %ld\n",(long)len);
1079        ws_addarg(&pwsc->response_headers,"Content-Length","%ld",(long)len);
1080        lseek(file_fd,0,SEEK_SET);
1081    }
1082
1083
1084    ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n");
1085    ws_emitheaders(pwsc);
1086
1087    /* now throw out the file */
1088    copyfile(file_fd,pwsc->fd);
1089
1090    r_close(file_fd);
1091    DPRINTF(E_DBG,L_WS,"Exiting ws_defaulthandler: "
1092            "Thread %d: Served successfully\n",
1093            pwsc->threadno);
1094    return;
1095}
1096
1097
1098/*
1099 * ws_testrequestheader
1100 *
1101 * Check to see if a request header is a particular value
1102 *
1103 * Example:
1104 *
1105 */
1106int ws_testrequestheader(WS_CONNINFO *pwsc, char *header, char *value) {
1107    return ws_testarg(&pwsc->request_headers,header,value);
1108}
1109
1110/*
1111 * ws_testarg
1112 *
1113 * Check an arg for a particular value
1114 *
1115 * Example:
1116 *
1117 * pwsc->close=ws_queryarg(&pwsc->request_headers,"Connection","close");
1118 *
1119 */
1120int ws_testarg(ARGLIST *root, char *key, char *value) {
1121    char *retval;
1122
1123    DPRINTF(E_DBG,L_WS,"Checking to see if %s matches %s\n",key,value);
1124
1125    retval=ws_getarg(root,key);
1126    if(!retval)
1127        return 0;
1128
1129    return !strcasecmp(value,retval);
1130}
1131
1132/*
1133 * ws_getarg
1134 *
1135 * Find an argument in an argument list
1136 *
1137 * returns a pointer to the value if successful,
1138 * NULL otherwise.
1139 *
1140 * This should be passed the pointer to the
1141 * stub in the pwsc.
1142 *
1143 * Ex: ws_getarg(pwsc->request_headers,"Connection");
1144 */
1145char *ws_getarg(ARGLIST *root, char *key) {
1146    ARGLIST *pcurrent=root->next;
1147
1148    while((pcurrent)&&(strcasecmp(pcurrent->key,key)))
1149        pcurrent=pcurrent->next;
1150
1151    if(pcurrent)
1152        return pcurrent->value;
1153
1154    return NULL;
1155}
1156
1157
1158/*
1159 * ws_addarg
1160 *
1161 * Add an argument to an arg list
1162 *
1163 * This will strdup the passed key and value.
1164 * The arglist will then have to be freed.  This should
1165 * be done in ws_close, and handler functions will
1166 * have to remember to ws_close them.
1167 *
1168 * RETURNS
1169 *   -1 on failure, with errno set (ENOMEM)
1170 *    0 on success
1171 */
1172int ws_addarg(ARGLIST *root, char *key, char *fmt, ...) {
1173    char *newkey;
1174    char *newvalue;
1175    ARGLIST *pnew;
1176    ARGLIST *current;
1177    va_list ap;
1178    char value[MAX_LINEBUFFER];
1179
1180    va_start(ap,fmt);
1181    vsnprintf(value,sizeof(value),fmt,ap);
1182    va_end(ap);
1183
1184    newkey=strdup(key);
1185    newvalue=strdup(value);
1186    pnew=(ARGLIST*)malloc(sizeof(ARGLIST));
1187
1188    if((!pnew)||(!newkey)||(!newvalue))
1189        return -1;
1190
1191    pnew->key=newkey;
1192    pnew->value=newvalue;
1193
1194    /* first, see if the key exists... if it does, simply
1195     * replace it rather than adding a duplicate key
1196     */
1197
1198    current=root->next;
1199    while(current) {
1200        if(!strcmp(current->key,key)) {
1201            /* got a match! */
1202            DPRINTF(E_DBG,L_WS,"Updating %s from %s to %s\n",
1203                    key,current->value,value);
1204            free(current->value);
1205            current->value = newvalue;
1206            free(newkey);
1207            free(pnew);
1208            return 0;
1209        }
1210        current=current->next;
1211    }
1212
1213
1214    pnew->next=root->next;
1215    DPRINTF(E_DBG,L_WS,"Added *%s=%s*\n",newkey,newvalue);
1216    root->next=pnew;
1217
1218    return 0;
1219}
1220
1221/*
1222 * ws_urldecode
1223 *
1224 * decode a urlencoded string
1225 *
1226 * the returned char will be malloced -- it must be
1227 * freed by the caller
1228 *
1229 * returns NULL on error (ENOMEM)
1230 */
1231char *ws_urldecode(char *string, int space_as_plus) {
1232    char *pnew;
1233    char *src,*dst;
1234    int val=0;
1235
1236    pnew=(char*)malloc(strlen(string)+1);
1237    if(!pnew)
1238        return NULL;
1239
1240    src=string;
1241    dst=pnew;
1242
1243    while(*src) {
1244        switch(*src) {
1245            /* DWB - space gets converted to %20, not +, this definitely breaks compatibility with iTunes */
1246            /* But the browsers encode space as plus, so when using the web interface,
1247             * anything with a plus is broken.  This will end up having to be sniffed
1248             * by remote agent */
1249        case '+':
1250            if(space_as_plus) {
1251                *dst++=' ';
1252            } else {
1253                *dst++=*src;
1254            }
1255            src++;
1256            break;
1257        case '%':
1258            /* this is hideous */
1259            src++;
1260            if(*src) {
1261                if((*src <= '9') && (*src >='0'))
1262                    val=(*src - '0');
1263                else if((tolower(*src) <= 'f')&&(tolower(*src) >= 'a'))
1264                    val=10+(tolower(*src) - 'a');
1265                src++;
1266            }
1267            if(*src) {
1268                val *= 16;
1269                if((*src <= '9') && (*src >='0'))
1270                    val+=(*src - '0');
1271                else if((tolower(*src) <= 'f')&&(tolower(*src) >= 'a'))
1272                    val+=(10+(tolower(*src) - 'a'));
1273                src++;
1274            }
1275            *dst++=val;
1276            break;
1277        default:
1278            *dst++=*src++;
1279            break;
1280        }
1281    }
1282
1283    *dst='\0';
1284    return pnew;
1285}
1286
1287
1288/*
1289 * ws_registerhandler
1290 *
1291 * Register a page and auth handler.  Returns 0 on success,
1292 * returns -1 on failure.
1293 *
1294 * If the regex is not well-formed, it returns -1 iwth
1295 * errno set to EINVAL.  It is up to the caller to use
1296 * regerror to display a more interesting error message,
1297 * if appropriate.
1298 */
1299int ws_registerhandler(WSHANDLE ws, char *regex,
1300                       void(*handler)(WS_CONNINFO*),
1301                       int(*auth)(char *, char *),
1302                       int addheaders) {
1303    WS_HANDLER *phandler;
1304    WS_PRIVATE *pwsp = (WS_PRIVATE *)ws;
1305
1306    phandler=(WS_HANDLER *)malloc(sizeof(WS_HANDLER));
1307    if(!phandler)
1308        return -1;
1309
1310    if(regcomp(&phandler->regex,regex,REG_EXTENDED | REG_NOSUB)) {
1311        free(phandler);
1312        errno=EINVAL;
1313        return -1;
1314    }
1315
1316    phandler->req_handler=handler;
1317    phandler->auth_handler=auth;
1318    phandler->addheaders=addheaders;
1319
1320    ws_lock_unsafe();
1321    phandler->next=pwsp->handlers.next;
1322    pwsp->handlers.next=phandler;
1323    ws_unlock_unsafe();
1324
1325    return 0;
1326}
1327
1328/*
1329 * ws_findhandler
1330 *
1331 * Given a URI, determine the appropriate handler.
1332 *
1333 * If a handler is found, it returns 0, otherwise, returns
1334 * -1
1335 */
1336int ws_findhandler(WS_PRIVATE *pwsp, WS_CONNINFO *pwsc,
1337                   void(**preq)(WS_CONNINFO*),
1338                   int(**pauth)(char *, char *),
1339                   int *addheaders) {
1340    WS_HANDLER *phandler=pwsp->handlers.next;
1341
1342    ws_lock_unsafe();
1343
1344    *preq=NULL;
1345
1346    DPRINTF(E_DBG,L_WS,"Thread %d: Preparing to find handler\n",
1347            pwsc->threadno);
1348
1349    while(phandler) {
1350        if(!regexec(&phandler->regex,pwsc->uri,0,NULL,0)) {
1351            /* that's a match */
1352            DPRINTF(E_DBG,L_WS,"Thread %d: URI Match!\n",pwsc->threadno);
1353            *preq=phandler->req_handler;
1354            *pauth=phandler->auth_handler;
1355            *addheaders=phandler->addheaders;
1356            ws_unlock_unsafe();
1357            return 0;
1358        }
1359        phandler=phandler->next;
1360    }
1361
1362    ws_unlock_unsafe();
1363    return -1;
1364}
1365
1366/*
1367 * ws_decodepassword
1368 *
1369 * Given a base64 encoded Authentication request header,
1370 * decode it into the username and password
1371 */
1372int ws_decodepassword(char *header, char **username, char **password) {
1373    static char ws_xlat[256];
1374    static int ws_xlat_init=0;
1375    int index;
1376    int len;
1377    int rack=0;
1378    int pads=0;
1379    unsigned char *decodebuffer;
1380    unsigned char *pin, *pout;
1381    char *type,*base64;
1382    int lookup;
1383
1384    *username=NULL;
1385    *password=NULL;
1386
1387    if(ws_lock_unsafe() == -1)
1388        return -1;
1389
1390    if(!ws_xlat_init) {
1391        ws_xlat_init=1;
1392
1393        memset((char*)&ws_xlat,0xFF,sizeof(ws_xlat));
1394        for(index=0; index < 26; index++) {
1395            ws_xlat['A' + index] = index;
1396            ws_xlat['a' + index] = index + 26;
1397        }
1398
1399        for(index=0; index < 10; index++) {
1400            ws_xlat['0' + index] = index + 52;
1401        }
1402
1403        ws_xlat['+'] = 62;
1404        ws_xlat['/'] = 63;
1405    }
1406    if(ws_unlock_unsafe() == -1)
1407        return -1;
1408
1409    /* xlat table is initialized */
1410
1411    // Trim leading spaces
1412    while((*header) && (*header != ' '))
1413        header++;
1414
1415     // Should be in the form "Basic <base-64 enc username/pw>"
1416    type=header;
1417    base64 = strchr(header,' ');
1418    if(!base64) {
1419        // invalid auth header
1420        DPRINTF(E_DBG,L_WS,"Bad authentication header: %s\n",header);
1421        return -1;
1422    }
1423
1424    *base64 = '\0';
1425    base64++;
1426
1427    decodebuffer=(unsigned char *)malloc(strlen(base64));
1428    if(!decodebuffer)
1429        return -1;
1430
1431    DPRINTF(E_DBG,L_WS,"Preparing to decode %s\n",base64);
1432
1433    memset(decodebuffer,0,strlen(base64));
1434    len=0;
1435    pout=decodebuffer;
1436    pin=base64;
1437
1438    /* this is more than a little sloppy */
1439    while(pin[rack]) {
1440        if(pin[rack] != '=') {
1441            lookup=ws_xlat[pin[rack]];
1442            if(lookup == 0xFF) {
1443                DPRINTF(E_WARN,L_WS,"Got garbage Authenticate header\n");
1444                return -1;
1445            }
1446
1447            /* valid character */
1448            switch(rack) {
1449            case 0:
1450                pout[0]=(lookup << 2);
1451                break;
1452            case 1:
1453                pout[0] |= (lookup >> 4);
1454                pout[1] = (lookup << 4);
1455                break;
1456            case 2:
1457                pout[1] |= (lookup >> 2);
1458                pout[2] = (lookup << 6);
1459                break;
1460            case 3:
1461                pout[2] |= lookup;
1462                break;
1463            }
1464            rack++;
1465        } else {
1466            /* padding char */
1467            pads++;
1468            rack++;
1469        }
1470
1471        if(rack == 4) {
1472            pin += 4;
1473            pout += 3;
1474
1475            len += (3-pads);
1476            rack=0;
1477        }
1478    }
1479
1480    /* we now have the decoded string */
1481    DPRINTF(E_DBG,L_WS,"Decoded %s\n",decodebuffer);
1482
1483    *username = decodebuffer;
1484    *password = *username;
1485
1486    strsep(password,":");
1487
1488    DPRINTF(E_DBG,L_WS,"Decoded user=%s, pw=%s\n",*username,*password);
1489    return 0;
1490}
1491
1492/*
1493 * ws_addreponseheader
1494 *
1495 * Simple wrapper around the CONNINFO response headers
1496 */
1497int ws_addresponseheader(WS_CONNINFO *pwsc, char *header, char *fmt, ...) {
1498    va_list ap;
1499    char value[MAX_LINEBUFFER];
1500
1501    va_start(ap,fmt);
1502    vsnprintf(value,sizeof(value),fmt,ap);
1503    va_end(ap);
1504
1505    return ws_addarg(&pwsc->response_headers,header,value);
1506}
1507
1508/*
1509 * ws_getvar
1510 *
1511 * Simple wrapper around the CONNINFO request vars
1512 */
1513char *ws_getvar(WS_CONNINFO *pwsc, char *var) {
1514    return ws_getarg(&(pwsc->request_vars),var);
1515}
1516
1517char *ws_getrequestheader(WS_CONNINFO *pwsc, char *header) {
1518    return ws_getarg(&pwsc->request_headers,header);
1519}
1520