fserve.c 20.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
/* Icecast
 *
 * This program is distributed under the GNU General Public License, version 2.
 * A copy of this license is included with this source.
 *
 * Copyright 2000-2004, Jack Moffitt <jack@xiph.org, 
 *                      Michael Smith <msmith@xiph.org>,
 *                      oddsock <oddsock@xiph.org>,
 *                      Karl Heyes <karl@xiph.org>
 *                      and others (see AUTHORS for details).
 */

13 14 15 16
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

17 18 19 20
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
21
#include <sys/stat.h>
22
#include <errno.h>
23 24

#ifdef HAVE_POLL
25
#include <sys/poll.h>
26
#endif
27 28 29 30 31 32 33 34

#ifndef _WIN32
#include <unistd.h>
#include <sys/time.h>
#include <sys/socket.h>
#else
#include <winsock2.h>
#include <windows.h>
35 36 37
#define fseeko fseek
#define PRIdMAX "ld"
#define SCNdMAX "ld"
38
#define snprintf _snprintf
39
#define strncasecmp _strnicmp
40
#define S_ISREG(mode)  ((mode) & _S_IFREG)
41 42
#endif

43 44 45 46
#include "thread/thread.h"
#include "avl/avl.h"
#include "httpp/httpp.h"
#include "net/sock.h"
47 48 49 50 51 52 53 54

#include "connection.h"
#include "global.h"
#include "refbuf.h"
#include "client.h"
#include "stats.h"
#include "format.h"
#include "logging.h"
55
#include "cfgfile.h"
56
#include "util.h"
57
#include "admin.h"
58
#include "compat.h"
59 60 61 62 63 64 65 66

#include "fserve.h"

#undef CATMODULE
#define CATMODULE "fserve"

#define BUFSIZE 4096

67
static fserve_t *active_list = NULL;
68
static volatile fserve_t *pending_list = NULL;
69 70

static mutex_t pending_lock;
71
static avl_tree *mimetypes = NULL;
72

73
static thread_type *fserv_thread;
74 75
static int run_fserv = 0;
static unsigned int fserve_clients;
76 77
static int client_tree_changed=0;

78
#ifdef HAVE_POLL
79
static struct pollfd *ufds = NULL;
80 81
#else
static fd_set fds;
82
static sock_t fd_max = SOCK_ERROR;
83
#endif
84

85 86 87 88 89
typedef struct {
    char *ext;
    char *type;
} mime_type;

90
static void fserve_client_destroy(fserve_t *fclient);
91 92
static int _delete_mapping(void *mapping);
static void *fserv_thread_function(void *arg);
93 94 95

void fserve_initialize(void)
{
96
    ice_config_t *config = config_get_config();
97

98
    mimetypes = NULL;
99
    thread_mutex_create (&pending_lock);
100

101 102 103
    fserve_recheck_mime_types (config);
    config_release_config();

104
    run_fserv = 1;
105
    stats_event (NULL, "file_connections", "0");
106 107 108 109 110 111 112

    fserv_thread = thread_create("File Serving Thread", 
            fserv_thread_function, NULL, THREAD_ATTACHED);
}

void fserve_shutdown(void)
{
113 114 115
    if(!run_fserv)
        return;

116 117
    run_fserv = 0;
    thread_join(fserv_thread);
118
    INFO0("file serving thread stopped");
119 120
    if (mimetypes)
        avl_tree_free (mimetypes, _delete_mapping);
121 122
}

123
#ifdef HAVE_POLL
124 125 126 127
int fserve_client_waiting (void)
{
    fserve_t *fclient;
    unsigned int i = 0;
128

129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
    /* only rebuild ufds if there are clients added/removed */
    if (client_tree_changed)
    {
        client_tree_changed = 0;
        ufds = realloc(ufds, fserve_clients * sizeof(struct pollfd));
        fclient = active_list;
        while (fclient)
        {
            ufds[i].fd = fclient->client->con->sock;
            ufds[i].events = POLLOUT;
            ufds[i].revents = 0;
            fclient = fclient->next;
            i++;
        }
    }
144
    if (!ufds)
145
        thread_sleep(200000);
146
    else if (poll(ufds, fserve_clients, 200) > 0)
147 148 149 150 151 152 153 154 155 156 157 158 159
    {
        /* mark any clients that are ready */
        fclient = active_list;
        for (i=0; i<fserve_clients; i++)
        {
            if (ufds[i].revents & (POLLOUT|POLLHUP|POLLERR))
                fclient->ready = 1;
            fclient = fclient->next;
        }
        return 1;
    }
    return 0;
}
160
#else
161 162 163 164 165 166 167 168 169
int fserve_client_waiting (void)
{
    fserve_t *fclient;
    fd_set realfds;

    /* only rebuild fds if there are clients added/removed */
    if(client_tree_changed) {
        client_tree_changed = 0;
        FD_ZERO(&fds);
170
        fd_max = SOCK_ERROR;
171 172 173
        fclient = active_list;
        while (fclient) {
            FD_SET (fclient->client->con->sock, &fds);
174
            if (fclient->client->con->sock > fd_max || fd_max == SOCK_ERROR)
175 176 177 178 179
                fd_max = fclient->client->con->sock;
            fclient = fclient->next;
        }
    }
    /* hack for windows, select needs at least 1 descriptor */
180
    if (fd_max == SOCK_ERROR)
181 182 183
        thread_sleep (200000);
    else
    {
184 185 186
        struct timeval tv;
        tv.tv_sec = 0;
        tv.tv_usec = 200000;
187 188
        /* make a duplicate of the set so we do not have to rebuild it
         * each time around */
189 190
        memcpy(&realfds, &fds, sizeof(fd_set));
        if(select(fd_max+1, NULL, &realfds, NULL, &tv) > 0)
191 192 193 194 195 196 197 198 199 200 201 202 203 204
        {
            /* mark any clients that are ready */
            fclient = active_list;
            while (fclient)
            {
                if (FD_ISSET (fclient->client->con->sock, &realfds))
                    fclient->ready = 1;
                fclient = fclient->next;
            }
            return 1;
        }
    }
    return 0;
}
205
#endif
206

207
static void wait_for_fds(void) {
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
    fserve_t *fclient;

    while (run_fserv)
    {
        /* add any new clients here */
        if (pending_list)
        {
            thread_mutex_lock (&pending_lock);

            fclient = (fserve_t*)pending_list;
            while (fclient)
            {
                fserve_t *to_move = fclient;
                fclient = fclient->next;
                to_move->next = active_list;
                active_list = to_move;
                client_tree_changed = 1;
                fserve_clients++;
            }
            pending_list = NULL;
            thread_mutex_unlock (&pending_lock);
229
        }
230
        /* drop out of here if someone is ready */
231
        if (fserve_client_waiting())
232
            break;
233 234 235
    }
}

236
static void *fserv_thread_function(void *arg)
237
{
238
    fserve_t *fclient, **trail;
239
    size_t bytes;
240

241
    INFO0("file serving thread started");
242
    while (run_fserv) {
243 244
        wait_for_fds();

245 246 247 248 249 250 251 252
        fclient = active_list;
        trail = &active_list;

        while (fclient)
        {
            /* process this client, if it is ready */
            if (fclient->ready)
            {
253 254
                client_t *client = fclient->client;
                refbuf_t *refbuf = client->refbuf;
255
                fclient->ready = 0;
256 257
                if (client->pos == refbuf->len)
                {
258
                    /* Grab a new chunk */
259 260 261 262
                    if (fclient->file)
                        bytes = fread (refbuf->data, 1, BUFSIZE, fclient->file);
                    else
                        bytes = 0;
263 264
                    if (bytes == 0)
                    {
265 266 267 268 269 270 271 272 273 274 275 276 277
                        if (refbuf->next == NULL)
                        {
                            fserve_t *to_go = fclient;
                            fclient = fclient->next;
                            *trail = fclient;
                            fserve_client_destroy (to_go);
                            fserve_clients--;
                            client_tree_changed = 1;
                            continue;
                        }
                        client_set_queue (client, refbuf->next);
                        refbuf = client->refbuf;
                        bytes = refbuf->len;
278
                    }
279
                    refbuf->len = (unsigned int)bytes;
280
                    client->pos = 0;
281
                }
282

283
                /* Now try and send current chunk. */
284
                format_generic_write_to_client (client);
285

286
                if (client->con->error)
287 288 289 290 291
                {
                    fserve_t *to_go = fclient;
                    fclient = fclient->next;
                    *trail = fclient;
                    fserve_clients--;
292
                    fserve_client_destroy (to_go);
293
                    client_tree_changed = 1;
294 295 296
                    continue;
                }
            }
297 298
            trail = &fclient->next;
            fclient = fclient->next;
299 300 301 302
        }
    }

    /* Shutdown path */
303 304 305 306 307
    thread_mutex_lock (&pending_lock);
    while (pending_list)
    {
        fserve_t *to_go = (fserve_t *)pending_list;
        pending_list = to_go->next;
308

309
        fserve_client_destroy (to_go);
310 311
    }
    thread_mutex_unlock (&pending_lock);
312

313 314 315 316
    while (active_list)
    {
        fserve_t *to_go = active_list;
        active_list = to_go->next;
317
        fserve_client_destroy (to_go);
318
    }
319 320 321 322

    return NULL;
}

323
/* string returned needs to be free'd */
324
char *fserve_content_type (const char *path)
325 326
{
    char *ext = util_get_extension(path);
327
    mime_type exttype = {ext, NULL};
328
    void *result;
329
    char *type;
330

331 332
    thread_mutex_lock (&pending_lock);
    if (mimetypes && !avl_get_by_key (mimetypes, &exttype, &result))
333 334
    {
        mime_type *mime = result;
335
        type = strdup (mime->type);
336
    }
337 338 339
    else {
        /* Fallbacks for a few basic ones */
        if(!strcmp(ext, "ogg"))
340
            type = strdup ("application/ogg");
341
        else if(!strcmp(ext, "mp3"))
342
            type = strdup ("audio/mpeg");
343
        else if(!strcmp(ext, "html"))
344
            type = strdup ("text/html");
345
        else if(!strcmp(ext, "css"))
346
            type = strdup ("text/css");
347
        else if(!strcmp(ext, "txt"))
348
            type = strdup ("text/plain");
349
        else if(!strcmp(ext, "jpg"))
350
            type = strdup ("image/jpeg");
351
        else if(!strcmp(ext, "png"))
352
            type = strdup ("image/png");
353
        else if(!strcmp(ext, "m3u"))
354 355 356
            type = strdup ("audio/x-mpegurl");
        else if(!strcmp(ext, "aac"))
            type = strdup ("audio/aac");
357
        else
358
            type = strdup ("application/octet-stream");
359
    }
360 361
    thread_mutex_unlock (&pending_lock);
    return type;
362 363
}

364
static void fserve_client_destroy(fserve_t *fclient)
365
{
366 367 368 369 370
    if (fclient)
    {
        if (fclient->file)
            fclose (fclient->file);

371 372 373 374 375
        if (fclient->callback)
            fclient->callback (fclient->client, fclient->arg);
        else
            if (fclient->client)
                client_destroy (fclient->client);
376
        free (fclient);
377 378 379
    }
}

380

381 382 383 384
/* client has requested a file, so check for it and send the file.  Do not
 * refer to the client_t afterwards.  return 0 for success, -1 on error.
 */
int fserve_client_create (client_t *httpclient, const char *path)
385
{
386
    int bytes;
387
    struct stat file_buf;
388
    const char *range = NULL;
389 390
    off_t new_content_len = 0;
    off_t rangenumber = 0, content_length;
391 392
    int rangeproblem = 0;
    int ret = 0;
393 394
    char *fullpath;
    int m3u_requested = 0, m3u_file_available = 1;
395
    int xspf_requested = 0, xspf_file_available = 1;
396 397 398 399 400 401 402 403 404
    ice_config_t *config;
    FILE *file;

    fullpath = util_get_path_from_normalised_uri (path);
    INFO2 ("checking for file %s (%s)", path, fullpath);

    if (strcmp (util_get_extension (fullpath), "m3u") == 0)
        m3u_requested = 1;

405 406 407
    if (strcmp (util_get_extension (fullpath), "xspf") == 0)
        xspf_requested = 1;

408 409 410 411
    /* check for the actual file */
    if (stat (fullpath, &file_buf) != 0)
    {
        /* the m3u can be generated, but send an m3u file if available */
412
        if (m3u_requested == 0 && xspf_requested == 0)
413
        {
414 415
            WARN2 ("req for file \"%s\" %s", fullpath, strerror (errno));
            client_send_404 (httpclient, "The file you requested could not be found");
416
            free (fullpath);
417
            return -1;
418 419
        }
        m3u_file_available = 0;
420
        xspf_file_available = 0;
421 422
    }

423
    httpclient->refbuf->len = PER_CLIENT_REFBUF_SIZE;
424 425 426

    if (m3u_requested && m3u_file_available == 0)
    {
427
        const char *host = httpp_getvar (httpclient->parser, "host");
428 429
        char *sourceuri = strdup (path);
        char *dot = strrchr(sourceuri, '.');
430 431 432 433 434 435 436

        /* at least a couple of players (fb2k/winamp) are reported to send a 
         * host header but without the port number. So if we are missing the
         * port then lets treat it as if no host line was sent */
        if (host && strchr (host, ':') == NULL)
            host = NULL;

437 438 439 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
        *dot = 0;
        httpclient->respcode = 200;
        if (host == NULL)
        {
            config = config_get_config();
            snprintf (httpclient->refbuf->data, BUFSIZE,
                    "HTTP/1.0 200 OK\r\n"
                    "Content-Type: audio/x-mpegurl\r\n\r\n"
                    "http://%s:%d%s\r\n", 
                    config->hostname, config->port,
                    sourceuri
                    );
            config_release_config();
        }
        else
        {
            snprintf (httpclient->refbuf->data, BUFSIZE,
                    "HTTP/1.0 200 OK\r\n"
                    "Content-Type: audio/x-mpegurl\r\n\r\n"
                    "http://%s%s\r\n", 
                    host, 
                    sourceuri
                    );
        }
        httpclient->refbuf->len = strlen (httpclient->refbuf->data);
        fserve_add_client (httpclient, NULL);
        free (sourceuri);
        free (fullpath);
465
        return 0;
466
    }
467 468 469 470 471 472 473 474 475 476 477 478 479
    if (xspf_requested && xspf_file_available == 0)
    {
        xmlDocPtr doc;
        char *reference = strdup (path);
        char *eol = strrchr (reference, '.');
        if (eol)
            *eol = '\0';
        stats_get_xml (&doc, 0, reference);
        free (reference);
        admin_send_response (doc, httpclient, TRANSFORMED, "xspf.xsl");
        xmlFreeDoc(doc);
        return 0;
    }
480

481 482 483
    /* on demand file serving check */
    config = config_get_config();
    if (config->fileserve == 0)
484
    {
485
        DEBUG1 ("on demand file \"%s\" refused", fullpath);
486
        client_send_404 (httpclient, "The file you requested could not be found");
487 488
        config_release_config();
        free (fullpath);
489
        return -1;
490
    }
491
    config_release_config();
492

493
    if (S_ISREG (file_buf.st_mode) == 0)
494
    {
495 496 497
        client_send_404 (httpclient, "The file you requested could not be found");
        WARN1 ("found requested file but there is no handler for it: %s", fullpath);
        free (fullpath);
498
        return -1;
499
    }
500 501 502

    file = fopen (fullpath, "rb");
    if (file == NULL)
503
    {
504
        WARN1 ("Problem accessing file \"%s\"", fullpath);
505
        client_send_404 (httpclient, "File not readable");
506
        free (fullpath);
507
        return -1;
508
    }
509
    free (fullpath);
510

511
    content_length = file_buf.st_size;
512
    range = httpp_getvar (httpclient->parser, "range");
513

514
    /* full http range handling is currently not done but we deal with the common case */
515
    if (range != NULL) {
516 517
        ret = 0;
        if (strncasecmp (range, "bytes=", 6) == 0)
518
            ret = sscanf (range+6, "%" SCNdMAX "-", &rangenumber);
519

520 521 522 523 524 525 526 527 528
        if (ret != 1) {
            /* format not correct, so lets just assume
               we start from the beginning */
            rangeproblem = 1;
        }
        if (rangenumber < 0) {
            rangeproblem = 1;
        }
        if (!rangeproblem) {
529
            ret = fseeko (file, rangenumber, SEEK_SET);
530
            if (ret != -1) {
531
                new_content_len = content_length - rangenumber;
532 533 534 535 536 537 538 539 540 541 542 543
                if (new_content_len < 0) {
                    rangeproblem = 1;
                }
            }
            else {
                rangeproblem = 1;
            }
            if (!rangeproblem) {
                /* Date: is required on all HTTP1.1 responses */
                char currenttime[50];
                time_t now;
                int strflen;
544
                struct tm result;
545
                off_t endpos = rangenumber+new_content_len-1;
546 547
                char *type;

548 549 550
                if (endpos < 0) {
                    endpos = 0;
                }
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
551
                time(&now);
552
                strflen = strftime(currenttime, 50, "%a, %d-%b-%Y %X GMT",
553
                                   gmtime_r(&now, &result));
554
                httpclient->respcode = 206;
555
                type = fserve_content_type (path);
556
                bytes = snprintf (httpclient->refbuf->data, BUFSIZE,
557 558
                    "HTTP/1.1 206 Partial Content\r\n"
                    "Date: %s\r\n"
559
                    "Accept-Ranges: bytes\r\n"
560 561 562
                    "Content-Length: %" PRIdMAX "\r\n"
                    "Content-Range: bytes %" PRIdMAX \
                    "-%" PRIdMAX "/%" PRIdMAX "\r\n"
563 564 565 566
                    "Content-Type: %s\r\n\r\n",
                    currenttime,
                    new_content_len,
                    rangenumber,
567
                    endpos,
568
                    content_length,
569 570
                    type);
                free (type);
571 572
            }
            else {
573
                goto fail;
574 575 576
            }
        }
        else {
577
            goto fail;
578 579 580
        }
    }
    else {
581
        char *type = fserve_content_type(path);
582
        httpclient->respcode = 200;
583
        bytes = snprintf (httpclient->refbuf->data, BUFSIZE,
584
            "HTTP/1.0 200 OK\r\n"
585
            "Accept-Ranges: bytes\r\n"
586
            "Content-Length: %" PRIdMAX "\r\n"
587
            "Content-Type: %s\r\n\r\n",
588
            content_length,
589 590
            type);
        free (type);
591
    }
592
    httpclient->refbuf->len = bytes;
593 594
    httpclient->pos = 0;

595
    stats_event_inc (NULL, "file_connections");
596 597
    fserve_add_client (httpclient, file);

598
    return 0;
599 600 601 602 603 604 605 606

fail:
    fclose (file);
    httpclient->respcode = 416;
    sock_write (httpclient->con->sock, 
            "HTTP/1.0 416 Request Range Not Satisfiable\r\n\r\n");
    client_destroy (httpclient);
    return -1;
607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626
}


/* Add client to fserve thread, client needs to have refbuf set and filled
 * but may provide a NULL file if no data needs to be read
 */
int fserve_add_client (client_t *client, FILE *file)
{
    fserve_t *fclient = calloc (1, sizeof(fserve_t));

    DEBUG0 ("Adding client to file serving engine");
    if (fclient == NULL)
    {
        client_send_404 (client, "memory exhausted");
        return -1;
    }
    fclient->file = file;
    fclient->client = client;
    fclient->ready = 0;

627
    thread_mutex_lock (&pending_lock);
628 629
    fclient->next = (fserve_t *)pending_list;
    pending_list = fclient;
630
    thread_mutex_unlock (&pending_lock);
631

632
    return 0;
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
/* add client to file serving engine, but just write out the buffer contents,
 * then pass the client to the callback with the provided arg
 */
void fserve_add_client_callback (client_t *client, fserve_callback_t callback, void *arg)
{
    fserve_t *fclient = calloc (1, sizeof(fserve_t));

    DEBUG0 ("Adding client to file serving engine");
    if (fclient == NULL)
    {
        client_send_404 (client, "memory exhausted");
        return;
    }
    fclient->file = NULL;
    fclient->client = client;
    fclient->ready = 0;
    fclient->callback = callback;
    fclient->arg = arg;

    thread_mutex_lock (&pending_lock);
    fclient->next = (fserve_t *)pending_list;
    pending_list = fclient;
    thread_mutex_unlock (&pending_lock);
}


662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677
static int _delete_mapping(void *mapping) {
    mime_type *map = mapping;
    free(map->ext);
    free(map->type);
    free(map);

    return 1;
}

static int _compare_mappings(void *arg, void *a, void *b)
{
    return strcmp(
            ((mime_type *)a)->ext,
            ((mime_type *)b)->ext);
}

678 679 680
void fserve_recheck_mime_types (ice_config_t *config)
{
    FILE *mimefile;
681 682
    char line[4096];
    char *type, *ext, *cur;
683
    mime_type *mapping;
684
    avl_tree *new_mimetypes;
685

686 687 688
    if (config->mimetypes_fn == NULL)
        return;
    mimefile = fopen (config->mimetypes_fn, "r");
689 690
    if (mimefile == NULL)
    {
691
        WARN1 ("Cannot open mime types file %s", config->mimetypes_fn);
692
        return;
693
    }
694

695 696
    new_mimetypes = avl_tree_new(_compare_mappings, NULL);

697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724
    while(fgets(line, 4096, mimefile))
    {
        line[4095] = 0;

        if(*line == 0 || *line == '#')
            continue;

        type = line;

        cur = line;

        while(*cur != ' ' && *cur != '\t' && *cur)
            cur++;
        if(*cur == 0)
            continue;

        *cur++ = 0;

        while(1) {
            while(*cur == ' ' || *cur == '\t')
                cur++;
            if(*cur == 0)
                break;

            ext = cur;
            while(*cur != ' ' && *cur != '\t' && *cur != '\n' && *cur)
                cur++;
            *cur++ = 0;
725 726 727
            if(*ext)
            {
                void *tmp;
728 729 730 731
                /* Add a new extension->type mapping */
                mapping = malloc(sizeof(mime_type));
                mapping->ext = strdup(ext);
                mapping->type = strdup(type);
732 733 734
                if (!avl_get_by_key (new_mimetypes, mapping, &tmp))
                    avl_delete (new_mimetypes, mapping, _delete_mapping);
                avl_insert (new_mimetypes, mapping);
735 736 737 738
            }
        }
    }
    fclose(mimefile);
739 740 741 742 743 744

    thread_mutex_lock (&pending_lock);
    if (mimetypes)
        avl_tree_free (mimetypes, _delete_mapping);
    mimetypes = new_mimetypes;
    thread_mutex_unlock (&pending_lock);
745 746
}