fserve.c 19.7 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
#define snprintf _snprintf
36
#define S_ISREG(mode)  ((mode) & _S_IFREG)
37 38
#endif

Karl Heyes's avatar
Karl Heyes committed
39 40 41 42
#include "thread/thread.h"
#include "avl/avl.h"
#include "httpp/httpp.h"
#include "net/sock.h"
43 44 45 46 47 48 49 50

#include "connection.h"
#include "global.h"
#include "refbuf.h"
#include "client.h"
#include "stats.h"
#include "format.h"
#include "logging.h"
51
#include "cfgfile.h"
52
#include "util.h"
53
#include "admin.h"
54
#include "compat.h"
55 56 57 58 59 60 61 62

#include "fserve.h"

#undef CATMODULE
#define CATMODULE "fserve"

#define BUFSIZE 4096

63
static fserve_t *active_list = NULL;
64
static volatile fserve_t *pending_list = NULL;
65 66

static mutex_t pending_lock;
67
static avl_tree *mimetypes = NULL;
68

69
static thread_type *fserv_thread;
70 71
static int run_fserv = 0;
static unsigned int fserve_clients;
72 73
static int client_tree_changed=0;

74
#ifdef HAVE_POLL
75
static struct pollfd *ufds = NULL;
76 77
#else
static fd_set fds;
78
static int fd_max = -1;
79
#endif
80

81 82 83 84 85
typedef struct {
    char *ext;
    char *type;
} mime_type;

86
static void fserve_client_destroy(fserve_t *fclient);
87 88
static int _delete_mapping(void *mapping);
static void *fserv_thread_function(void *arg);
89 90 91

void fserve_initialize(void)
{
92
    ice_config_t *config = config_get_config();
93

94
    mimetypes = NULL;
95
    thread_mutex_create (&pending_lock);
96

97 98 99
    fserve_recheck_mime_types (config);
    config_release_config();

100
    run_fserv = 1;
101
    stats_event (NULL, "file_connections", "0");
102 103 104 105 106 107 108

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

void fserve_shutdown(void)
{
109 110 111
    if(!run_fserv)
        return;

112 113
    run_fserv = 0;
    thread_join(fserv_thread);
114
    INFO0("file serving thread stopped");
115 116
    if (mimetypes)
        avl_tree_free (mimetypes, _delete_mapping);
117 118
}

119
#ifdef HAVE_POLL
120 121 122 123
int fserve_client_waiting (void)
{
    fserve_t *fclient;
    unsigned int i = 0;
124

125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
    /* 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++;
        }
    }
140
    if (!ufds)
141
        thread_sleep(200000);
142
    else if (poll(ufds, fserve_clients, 200) > 0)
143 144 145 146 147 148 149 150 151 152 153 154 155
    {
        /* 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;
}
156
#else
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
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);
        fd_max = -1;
        fclient = active_list;
        while (fclient) {
            FD_SET (fclient->client->con->sock, &fds);
            if (fclient->client->con->sock > fd_max)
                fd_max = fclient->client->con->sock;
            fclient = fclient->next;
        }
    }
    /* hack for windows, select needs at least 1 descriptor */
    if (fd_max == -1)
        thread_sleep (200000);
    else
    {
180 181 182
        struct timeval tv;
        tv.tv_sec = 0;
        tv.tv_usec = 200000;
183 184
        /* make a duplicate of the set so we do not have to rebuild it
         * each time around */
185 186
        memcpy(&realfds, &fds, sizeof(fd_set));
        if(select(fd_max+1, NULL, &realfds, NULL, &tv) > 0)
187 188 189 190 191 192 193 194 195 196 197 198 199 200
        {
            /* 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;
}
201
#endif
202

203
static void wait_for_fds(void) {
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
    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);
225
        }
226
        /* drop out of here if someone is ready */
227
        if (fserve_client_waiting())
228
            break;
229 230 231
    }
}

232
static void *fserv_thread_function(void *arg)
233
{
234
    fserve_t *fclient, **trail;
235
    int bytes;
236

237
    INFO0("file serving thread started");
238
    while (run_fserv) {
239 240
        wait_for_fds();

241 242 243 244 245 246 247 248
        fclient = active_list;
        trail = &active_list;

        while (fclient)
        {
            /* process this client, if it is ready */
            if (fclient->ready)
            {
249 250
                client_t *client = fclient->client;
                refbuf_t *refbuf = client->refbuf;
251
                fclient->ready = 0;
252 253
                if (client->pos == refbuf->len)
                {
254
                    /* Grab a new chunk */
255 256 257 258
                    if (fclient->file)
                        bytes = fread (refbuf->data, 1, BUFSIZE, fclient->file);
                    else
                        bytes = 0;
259 260 261 262 263
                    if (bytes == 0)
                    {
                        fserve_t *to_go = fclient;
                        fclient = fclient->next;
                        *trail = fclient;
264
                        fserve_client_destroy (to_go);
265 266 267 268
                        fserve_clients--;
                        client_tree_changed = 1;
                        continue;
                    }
269 270
                    refbuf->len = bytes;
                    client->pos = 0;
271
                }
272

273
                /* Now try and send current chunk. */
274
                format_generic_write_to_client (client);
275

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

    /* Shutdown path */
293 294 295 296 297
    thread_mutex_lock (&pending_lock);
    while (pending_list)
    {
        fserve_t *to_go = (fserve_t *)pending_list;
        pending_list = to_go->next;
298

299
        fserve_client_destroy (to_go);
300 301
    }
    thread_mutex_unlock (&pending_lock);
302

303 304 305 306
    while (active_list)
    {
        fserve_t *to_go = active_list;
        active_list = to_go->next;
307
        fserve_client_destroy (to_go);
308
    }
309 310 311 312

    return NULL;
}

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

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

354
static void fserve_client_destroy(fserve_t *fclient)
355
{
356 357 358 359 360
    if (fclient)
    {
        if (fclient->file)
            fclose (fclient->file);

361 362 363 364 365
        if (fclient->callback)
            fclient->callback (fclient->client, fclient->arg);
        else
            if (fclient->client)
                client_destroy (fclient->client);
366
        free (fclient);
367 368 369
    }
}

370

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

395 396 397
    if (strcmp (util_get_extension (fullpath), "xspf") == 0)
        xspf_requested = 1;

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

413
    httpclient->refbuf->len = PER_CLIENT_REFBUF_SIZE;
414 415 416 417 418 419

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

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

427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454
        *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);
455
        return 0;
456
    }
457 458 459 460 461 462 463 464 465 466 467 468 469
    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;
    }
470

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

483
    if (S_ISREG (file_buf.st_mode) == 0)
484
    {
485 486 487
        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);
488
        return -1;
489
    }
490 491 492

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

501 502
    content_length = (int64_t)file_buf.st_size;
    range = httpp_getvar (httpclient->parser, "range");
503 504 505 506 507 508 509 510 511 512 513 514

    if (range != NULL) {
        ret = sscanf(range, "bytes=" FORMAT_INT64 "-", &rangenumber);
        if (ret != 1) {
            /* format not correct, so lets just assume
               we start from the beginning */
            rangeproblem = 1;
        }
        if (rangenumber < 0) {
            rangeproblem = 1;
        }
        if (!rangeproblem) {
515
            ret = fseek (file, rangenumber, SEEK_SET);
516
            if (ret != -1) {
517
                new_content_len = content_length - rangenumber;
518 519 520 521 522 523 524 525 526 527 528 529
                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;
530 531
                struct tm result;
                int64_t endpos = rangenumber+new_content_len-1;
532 533
                char *type;

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

579
    stats_event_inc (NULL, "file_connections");
580 581
    fserve_add_client (httpclient, file);

582
    return 0;
583 584 585 586 587 588 589 590

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;
591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610
}


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

611
    thread_mutex_lock (&pending_lock);
612 613
    fclient->next = (fserve_t *)pending_list;
    pending_list = fclient;
614
    thread_mutex_unlock (&pending_lock);
615

616
    return 0;
617 618 619
}


620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645
/* 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);
}


646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661
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);
}

662 663 664
void fserve_recheck_mime_types (ice_config_t *config)
{
    FILE *mimefile;
665 666
    char line[4096];
    char *type, *ext, *cur;
667
    mime_type *mapping;
668
    avl_tree *new_mimetypes;
669

670 671 672
    if (config->mimetypes_fn == NULL)
        return;
    mimefile = fopen (config->mimetypes_fn, "r");
673 674
    if (mimefile == NULL)
    {
675
        WARN1 ("Cannot open mime types file %s", config->mimetypes_fn);
676
        return;
677
    }
678

679 680
    new_mimetypes = avl_tree_new(_compare_mappings, NULL);

681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708
    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;
709 710 711
            if(*ext)
            {
                void *tmp;
712 713 714 715
                /* Add a new extension->type mapping */
                mapping = malloc(sizeof(mime_type));
                mapping->ext = strdup(ext);
                mapping->type = strdup(type);
716 717 718
                if (!avl_get_by_key (new_mimetypes, mapping, &tmp))
                    avl_delete (new_mimetypes, mapping, _delete_mapping);
                avl_insert (new_mimetypes, mapping);
719 720 721 722
            }
        }
    }
    fclose(mimefile);
723 724 725 726 727 728

    thread_mutex_lock (&pending_lock);
    if (mimetypes)
        avl_tree_free (mimetypes, _delete_mapping);
    mimetypes = new_mimetypes;
    thread_mutex_unlock (&pending_lock);
729 730
}