fserve.c 18.9 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 64 65 66 67 68
#ifdef _WIN32
#define MIMETYPESFILE ".\\mime.types"
#else
#define MIMETYPESFILE "/etc/mime.types"
#endif

69
static fserve_t *active_list = NULL;
70
static volatile fserve_t *pending_list = NULL;
71 72

static mutex_t pending_lock;
73
static avl_tree *mimetypes = NULL;
74

75
static thread_type *fserv_thread;
76 77
static int run_fserv = 0;
static unsigned int fserve_clients;
78 79
static int client_tree_changed=0;

80
#ifdef HAVE_POLL
81
static struct pollfd *ufds = NULL;
82 83
#else
static fd_set fds;
84
static int fd_max = -1;
85
#endif
86

87 88 89 90 91
typedef struct {
    char *ext;
    char *type;
} mime_type;

92
static void fserve_client_destroy(fserve_t *fclient);
93 94
static int _delete_mapping(void *mapping);
static void *fserv_thread_function(void *arg);
95
static void create_mime_mappings(const char *fn);
96 97 98

void fserve_initialize(void)
{
99 100
    create_mime_mappings(MIMETYPESFILE);

101
    thread_mutex_create (&pending_lock);
102 103

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

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

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

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

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

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

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

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

239
    INFO0("file serving thread started");
240
    while (run_fserv) {
241 242
        wait_for_fds();

243 244 245 246 247 248 249 250
        fclient = active_list;
        trail = &active_list;

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

275
                /* Now try and send current chunk. */
276
                format_generic_write_to_client (client);
277

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

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

301
        fserve_client_destroy (to_go);
302 303
    }
    thread_mutex_unlock (&pending_lock);
304

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

    return NULL;
}

315
char *fserve_content_type (const char *path)
316 317
{
    char *ext = util_get_extension(path);
318
    mime_type exttype = {ext, NULL};
319
    void *result;
320

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

349
static void fserve_client_destroy(fserve_t *fclient)
350
{
351 352 353 354 355
    if (fclient)
    {
        if (fclient->file)
            fclose (fclient->file);

356 357 358 359 360
        if (fclient->callback)
            fclient->callback (fclient->client, fclient->arg);
        else
            if (fclient->client)
                client_destroy (fclient->client);
361
        free (fclient);
362 363 364
    }
}

365

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

390 391 392
    if (strcmp (util_get_extension (fullpath), "xspf") == 0)
        xspf_requested = 1;

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

408
    httpclient->refbuf->len = PER_CLIENT_REFBUF_SIZE;
409 410 411 412 413 414

    if (m3u_requested && m3u_file_available == 0)
    {
        char *host = httpp_getvar (httpclient->parser, "host");
        char *sourceuri = strdup (path);
        char *dot = strrchr(sourceuri, '.');
415 416 417 418 419 420 421

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

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

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

478
    if (S_ISREG (file_buf.st_mode) == 0)
479
    {
480 481 482
        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);
483
        return -1;
484
    }
485 486 487

    file = fopen (fullpath, "rb");
    if (file == NULL)
488
    {
489
        WARN1 ("Problem accessing file \"%s\"", fullpath);
490
        client_send_404 (httpclient, "File not readable");
491
        free (fullpath);
492
        return -1;
493
    }
494
    free (fullpath);
495

496 497
    content_length = (int64_t)file_buf.st_size;
    range = httpp_getvar (httpclient->parser, "range");
498 499 500 501 502 503 504 505 506 507 508 509

    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) {
510
            ret = fseek (file, rangenumber, SEEK_SET);
511
            if (ret != -1) {
512
                new_content_len = content_length - rangenumber;
513 514 515 516 517 518 519 520 521 522 523 524
                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;
525 526 527 528 529
                struct tm result;
                int64_t endpos = rangenumber+new_content_len-1;
                if (endpos < 0) {
                    endpos = 0;
                }
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
530
                time(&now);
531
                strflen = strftime(currenttime, 50, "%a, %d-%b-%Y %X GMT",
532
                                   gmtime_r(&now, &result));
533
                httpclient->respcode = 206;
534
                bytes = snprintf (httpclient->refbuf->data, BUFSIZE,
535 536
                    "HTTP/1.1 206 Partial Content\r\n"
                    "Date: %s\r\n"
537
                    "Content-Length: " FORMAT_INT64 "\r\n"
538 539 540 541 542 543
                    "Content-Range: bytes " FORMAT_INT64 \
                    "-" FORMAT_INT64 "/" FORMAT_INT64 "\r\n"
                    "Content-Type: %s\r\n\r\n",
                    currenttime,
                    new_content_len,
                    rangenumber,
544
                    endpos,
545
                    content_length,
546 547 548
                    fserve_content_type(path));
            }
            else {
549
                goto fail;
550 551 552
            }
        }
        else {
553
            goto fail;
554 555 556 557 558
        }
    }
    else {

        httpclient->respcode = 200;
559
        bytes = snprintf (httpclient->refbuf->data, BUFSIZE,
560
            "HTTP/1.0 200 OK\r\n"
561
            "Content-Length: " FORMAT_INT64 "\r\n"
562
            "Content-Type: %s\r\n\r\n",
563
            content_length,
564
            fserve_content_type(path));
565
    }
566
    httpclient->refbuf->len = bytes;
567 568
    httpclient->pos = 0;

569
    stats_event_inc (NULL, "file_connections");
570 571
    fserve_add_client (httpclient, file);

572
    return 0;
573 574 575 576 577 578 579 580

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;
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600
}


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

601
    thread_mutex_lock (&pending_lock);
602 603
    fclient->next = (fserve_t *)pending_list;
    pending_list = fclient;
604
    thread_mutex_unlock (&pending_lock);
605

606
    return 0;
607 608 609
}


610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635
/* 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);
}


636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651
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);
}

652
static void create_mime_mappings(const char *fn) {
653 654 655
    FILE *mimefile = fopen(fn, "r");
    char line[4096];
    char *type, *ext, *cur;
656
    mime_type *mapping;
657 658 659

    mimetypes = avl_tree_new(_compare_mappings, NULL);

660 661 662
    if (mimefile == NULL)
    {
        WARN1 ("Cannot open mime type file %s", fn);
663
        return;
664
    }
665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693

    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;
694 695 696
            if(*ext)
            {
                void *tmp;
697 698 699 700
                /* Add a new extension->type mapping */
                mapping = malloc(sizeof(mime_type));
                mapping->ext = strdup(ext);
                mapping->type = strdup(type);
701
                if(!avl_get_by_key(mimetypes, mapping, &tmp))
702 703 704 705 706 707 708 709 710
                    avl_delete(mimetypes, mapping, _delete_mapping);
                avl_insert(mimetypes, mapping);
            }
        }
    }

    fclose(mimefile);
}