fserve.c 20.1 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

Karl Heyes's avatar
Karl Heyes committed
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++;
        }
    }
brendan's avatar
brendan committed
144
    if (!ufds)
145
        thread_sleep(200000);
brendan's avatar
brendan committed
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
    int 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 265 266 267
                    if (bytes == 0)
                    {
                        fserve_t *to_go = fclient;
                        fclient = fclient->next;
                        *trail = fclient;
268
                        fserve_client_destroy (to_go);
269 270 271 272
                        fserve_clients--;
                        client_tree_changed = 1;
                        continue;
                    }
273 274
                    refbuf->len = bytes;
                    client->pos = 0;
275
                }
276

277
                /* Now try and send current chunk. */
278
                format_generic_write_to_client (client);
279

280
                if (client->con->error)
281 282 283 284 285
                {
                    fserve_t *to_go = fclient;
                    fclient = fclient->next;
                    *trail = fclient;
                    fserve_clients--;
286
                    fserve_client_destroy (to_go);
287
                    client_tree_changed = 1;
288 289 290
                    continue;
                }
            }
291 292
            trail = &fclient->next;
            fclient = fclient->next;
293 294 295 296
        }
    }

    /* Shutdown path */
297 298 299 300 301
    thread_mutex_lock (&pending_lock);
    while (pending_list)
    {
        fserve_t *to_go = (fserve_t *)pending_list;
        pending_list = to_go->next;
302

303
        fserve_client_destroy (to_go);
304 305
    }
    thread_mutex_unlock (&pending_lock);
306

307 308 309 310
    while (active_list)
    {
        fserve_t *to_go = active_list;
        active_list = to_go->next;
311
        fserve_client_destroy (to_go);
312
    }
313 314 315 316

    return NULL;
}

317
/* string returned needs to be free'd */
318
char *fserve_content_type (const char *path)
319 320
{
    char *ext = util_get_extension(path);
321
    mime_type exttype = {ext, NULL};
322
    void *result;
323
    char *type;
324

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

358
static void fserve_client_destroy(fserve_t *fclient)
359
{
360 361 362 363 364
    if (fclient)
    {
        if (fclient->file)
            fclose (fclient->file);

365 366 367 368 369
        if (fclient->callback)
            fclient->callback (fclient->client, fclient->arg);
        else
            if (fclient->client)
                client_destroy (fclient->client);
370
        free (fclient);
371 372 373
    }
}

374

375 376 377 378
/* 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)
379
{
380
    int bytes;
381
    struct stat file_buf;
382
    const char *range = NULL;
383 384
    off_t new_content_len = 0;
    off_t rangenumber = 0, content_length;
385 386
    int rangeproblem = 0;
    int ret = 0;
387 388
    char *fullpath;
    int m3u_requested = 0, m3u_file_available = 1;
389
    int xspf_requested = 0, xspf_file_available = 1;
390 391 392 393 394 395 396 397 398
    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;

399 400 401
    if (strcmp (util_get_extension (fullpath), "xspf") == 0)
        xspf_requested = 1;

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

417
    httpclient->refbuf->len = PER_CLIENT_REFBUF_SIZE;
418 419 420

    if (m3u_requested && m3u_file_available == 0)
    {
421
        const char *host = httpp_getvar (httpclient->parser, "host");
422 423
        char *sourceuri = strdup (path);
        char *dot = strrchr(sourceuri, '.');
424 425 426 427 428 429 430

        /* 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;

431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458
        *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);
459
        return 0;
460
    }
461 462 463 464 465 466 467 468 469 470 471 472 473
    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;
    }
Michael Smith's avatar
Michael Smith committed
474

475 476 477
    /* on demand file serving check */
    config = config_get_config();
    if (config->fileserve == 0)
478
    {
479
        DEBUG1 ("on demand file \"%s\" refused", fullpath);
480
        client_send_404 (httpclient, "The file you requested could not be found");
481 482
        config_release_config();
        free (fullpath);
483
        return -1;
484
    }
485
    config_release_config();
486

487
    if (S_ISREG (file_buf.st_mode) == 0)
488
    {
489 490 491
        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);
492
        return -1;
493
    }
494 495 496

    file = fopen (fullpath, "rb");
    if (file == NULL)
497
    {
498
        WARN1 ("Problem accessing file \"%s\"", fullpath);
499
        client_send_404 (httpclient, "File not readable");
500
        free (fullpath);
501
        return -1;
502
    }
503
    free (fullpath);
504

505
    content_length = file_buf.st_size;
506
    range = httpp_getvar (httpclient->parser, "range");
507

508
    /* full http range handling is currently not done but we deal with the common case */
509
    if (range != NULL) {
510 511
        ret = 0;
        if (strncasecmp (range, "bytes=", 6) == 0)
512
            ret = sscanf (range+6, "%" SCNdMAX "-", &rangenumber);
513

514 515 516 517 518 519 520 521 522
        if (ret != 1) {
            /* format not correct, so lets just assume
               we start from the beginning */
            rangeproblem = 1;
        }
        if (rangenumber < 0) {
            rangeproblem = 1;
        }
        if (!rangeproblem) {
523
            ret = fseeko (file, rangenumber, SEEK_SET);
524
            if (ret != -1) {
525
                new_content_len = content_length - rangenumber;
526 527 528 529 530 531 532 533 534 535 536 537
                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;
538
                struct tm result;
539
                off_t endpos = rangenumber+new_content_len-1;
540 541
                char *type;

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

589
    stats_event_inc (NULL, "file_connections");
590 591
    fserve_add_client (httpclient, file);

592
    return 0;
593 594 595 596 597 598 599 600

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;
601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620
}


/* 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;

621
    thread_mutex_lock (&pending_lock);
622 623
    fclient->next = (fserve_t *)pending_list;
    pending_list = fclient;
624
    thread_mutex_unlock (&pending_lock);
625

626
    return 0;
627 628 629
}


630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655
/* 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);
}


656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671
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);
}

672 673 674
void fserve_recheck_mime_types (ice_config_t *config)
{
    FILE *mimefile;
675 676
    char line[4096];
    char *type, *ext, *cur;
677
    mime_type *mapping;
678
    avl_tree *new_mimetypes;
679

680 681 682
    if (config->mimetypes_fn == NULL)
        return;
    mimefile = fopen (config->mimetypes_fn, "r");
683 684
    if (mimefile == NULL)
    {
685
        WARN1 ("Cannot open mime types file %s", config->mimetypes_fn);
686
        return;
687
    }
688

689 690
    new_mimetypes = avl_tree_new(_compare_mappings, NULL);

691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718
    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;
719 720 721
            if(*ext)
            {
                void *tmp;
722 723 724 725
                /* Add a new extension->type mapping */
                mapping = malloc(sizeof(mime_type));
                mapping->ext = strdup(ext);
                mapping->type = strdup(type);
726 727 728
                if (!avl_get_by_key (new_mimetypes, mapping, &tmp))
                    avl_delete (new_mimetypes, mapping, _delete_mapping);
                avl_insert (new_mimetypes, mapping);
729 730 731 732
            }
        }
    }
    fclose(mimefile);
733 734 735 736 737 738

    thread_mutex_lock (&pending_lock);
    if (mimetypes)
        avl_tree_free (mimetypes, _delete_mapping);
    mimetypes = new_mimetypes;
    thread_mutex_unlock (&pending_lock);
739 740
}